From fd890a60fbdf4bf59be81be6a39d2c68e6b35bae Mon Sep 17 00:00:00 2001 From: Shimi Bornstein Date: Mon, 27 Feb 2023 10:49:51 +0200 Subject: [PATCH 01/23] Support Strings comparison operators (Equal and NotEqual) --- src/calculate.rs | 18 ++++++++++++++++++ tests/test.rs | 2 ++ 2 files changed, 20 insertions(+) diff --git a/src/calculate.rs b/src/calculate.rs index 60ff081..50fac32 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -1136,6 +1136,9 @@ fn calculate_operation( (types::Value::Date(l), types::Value::Date(r)) => { compare_dates(l, r, |d1, d2| d1 == d2) } + (types::Value::Text(l), types::Value::Text(r)) => { + compare_strings(l, r, |s1, s2| s1 == s2) + } _ => calculate_comparison_operator(value1, value2, |n1, n2| (n1 - n2).abs() == 0.0), } } @@ -1145,6 +1148,9 @@ fn calculate_operation( (types::Value::Date(l), types::Value::Date(r)) => { compare_dates(l, r, |d1, d2| d1 != d2) } + (types::Value::Text(l), types::Value::Text(r)) => { + compare_strings(l, r, |s1, s2| s1 != s2) + } _ => calculate_comparison_operator(value1, value2, |n1, n2| (n1 - n2).abs() > 0.0), } } @@ -1200,6 +1206,18 @@ fn compare_dates( } } +fn compare_strings( + string1: String, + string2: String, + f: fn(s1: String, s2: String) -> bool, +) -> types::Value { + if f(string1, string2) { + types::Value::Boolean(types::Boolean::True) + } else { + types::Value::Boolean(types::Boolean::False) + } +} + /// Evaluates a string that was parsed and stored in Expression Struct. /// Takes an optional closure with the trait bound Fn(String) -> types::Value. pub fn calculate_formula( diff --git a/tests/test.rs b/tests/test.rs index 6f07dde..d3977ea 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -418,6 +418,8 @@ fn it_evaluate_comparison_operators() { assert_eq!(evaluate_formula_string(&"=1*1/1+2<1^1"), "FALSE",); assert_eq!(evaluate_formula_string(&"=2>=1"), "TRUE",); assert_eq!(evaluate_formula_string(&"=11<=3"), "FALSE",); + assert_eq!(evaluate_formula_string(&"=\"Joshu\"=\"Joshu\""), "TRUE",); + assert_eq!(evaluate_formula_string(&"=\"Joshu\"<>\"NotJoshu\""), "TRUE",); } #[test] From cb68ff96e3ac5adac3bd4fb4740d22df11d2087c Mon Sep 17 00:00:00 2001 From: Shimi Bornstein Date: Mon, 27 Feb 2023 10:50:47 +0200 Subject: [PATCH 02/23] Support case insensitive functions names --- src/grammar.pest | 30 +++++++++++++++--------------- tests/test.rs | 7 +++++++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/grammar.pest b/src/grammar.pest index 3087ecd..1215ed2 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -37,23 +37,23 @@ logical_operator = _{ equal | not_equal | greater_or_equal | greater less_or_equal = { "<=" } function = _{ abs | sum | product | average | negate | days | right | left | iff | isblank | custom_function } - abs = { "ABS" ~ "(" ~ expr ~ ")" } - sum = { "SUM" ~ function_param_with_atomic_expr} - product = { "PRODUCT" ~ function_param_with_atomic_expr} - average = { "AVERAGE" ~ function_param_with_atomic_expr} - negate = { "-" ~ (function_param | iterator | reference) } - days = { "DAYS" ~ function_param } - right = { "RIGHT" ~ function_param } - left = { "LEFT" ~ function_param } - iff = { "IF" ~ three_params } - isblank = { "ISBLANK" ~ function_param} + abs = { ^"ABS" ~ "(" ~ expr ~ ")" } + sum = { ^"SUM" ~ function_param_with_atomic_expr} + product = { ^"PRODUCT" ~ function_param_with_atomic_expr} + average = { ^"AVERAGE" ~ function_param_with_atomic_expr} + negate = { ^"-" ~ (function_param | iterator | reference) } + days = { ^"DAYS" ~ function_param } + right = { ^"RIGHT" ~ function_param } + left = { ^"LEFT" ~ function_param } + iff = { ^"IF" ~ three_params } + isblank = { ^"ISBLANK" ~ function_param} custom_function = { reference ~ (function_param | empty_param) } - + logical_function = _{ or | and | xor | not } - or = { "OR" ~ function_param_with_atomic_expr } - and = { "AND" ~ function_param_with_atomic_expr } - xor = { "XOR" ~ function_param_with_atomic_expr } - not = { "NOT" ~ "(" ~ expr ~ ")" } + or = { ^"OR" ~ function_param_with_atomic_expr } + and = { ^"AND" ~ function_param_with_atomic_expr } + xor = { ^"XOR" ~ function_param_with_atomic_expr } + not = { ^"NOT" ~ "(" ~ expr ~ ")" } function_param = _{ "(" ~ expr ~ ("," ~ expr)*? ~ ")" } empty_param = { "(" ~ ")" } diff --git a/tests/test.rs b/tests/test.rs index d3977ea..50eec38 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -503,6 +503,9 @@ fn it_evaluate_boolean_not() { assert_eq!(evaluate_formula_string(&"=NOT(\"false\")"), "TRUE",); assert_eq!(evaluate_formula_string(&"=NOT(\"test\")"), "#CAST!",); assert_eq!(evaluate_formula_string(&"=NOT(0)"), "TRUE",); + assert_eq!(evaluate_formula_string(&"=not(11<=3)"), "TRUE",); + assert_eq!(evaluate_formula_string(&"=Not(11<=3)"), "TRUE",); + assert_eq!(evaluate_formula_string(&"=nOT(11<=3)"), "TRUE",); } //////////////////////////// References ////////////////////////////////// @@ -709,6 +712,10 @@ fn it_evaluate_date() -> Result<(), ParseError> { evaluate_formula_number_with_reference(&"=DAYS(end, start)", Some(&data_function)), 182.00 ); + assert_eq!( + evaluate_formula_number_with_reference(&"=days(end, start)", Some(&data_function)), + 182.00 + ); assert_eq!( evaluate_formula_date_with_reference(&"=start + 1", Some(&data_function)), (start + Duration::days(1)).to_string() From 3c77954432320f8fc7c9824b5d5c5973a36a2bf1 Mon Sep 17 00:00:00 2001 From: Shimi Bornstein Date: Tue, 7 Mar 2023 16:53:16 +0200 Subject: [PATCH 03/23] return error if an operation (AND, for example) is imssing a reference. also, when casting text to boolean Excel returns a #VALUE! error --- src/calculate.rs | 5 ----- tests/test.rs | 24 +++++++++++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/calculate.rs b/src/calculate.rs index 50fac32..80c8530 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -393,10 +393,6 @@ fn calculate_boolean_operator_rhs_boolean( types::Value::Boolean(types::Boolean::False) } } - types::Value::Error(_) => match l { - types::Boolean::True => types::Value::Boolean(types::Boolean::True), - types::Boolean::False => types::Value::Boolean(types::Boolean::False), - }, types::Value::Iterator(mut value_vec) => { if let Some(mut temp) = value_vec.pop() { while let Some(top) = value_vec.pop() { @@ -483,7 +479,6 @@ fn calculate_boolean_operator( types::Value::Boolean(l) => { calculate_boolean_operator_rhs_boolean(l, cast_value_to_boolean(rhs), f) } - types::Value::Error(_) => calculate_boolean_operator_rhs_error(cast_value_to_boolean(rhs)), types::Value::Iterator(lhs_vec) => { calculate_boolean_operator_rhs_iterator(cast_value_to_boolean(rhs), lhs_vec, f) } diff --git a/tests/test.rs b/tests/test.rs index 50eec38..4d1380d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -452,19 +452,19 @@ fn it_evaluate_boolean_and() { assert_eq!(evaluate_formula_string(&"=AND(1)"), "TRUE",); assert_eq!( evaluate_formula_string(&"=AND(\"test\", \"test\")"), - "#CAST!", + "#VALUE!", ); assert_eq!( evaluate_formula_string(&"=AND(\"test\",\"True\", 1, true) "), - "TRUE", + "#VALUE!", ); assert_eq!( evaluate_formula_string(&"=AND(\"True\",\"test\", 1, true) "), - "TRUE", + "#VALUE!", ); assert_eq!( evaluate_formula_string(&"=AND(\"True\", 1, true, \"test\")"), - "TRUE", + "#VALUE!", ); assert_eq!(evaluate_formula_string(&"=AND(1, )"), "FALSE",); assert_eq!(evaluate_formula_string(&"=AND(1,,,1)"), "FALSE",); @@ -595,7 +595,7 @@ fn it_evaluate_references_error_value_formulas() { }; assert_eq!( evaluate_formula_boolean_with_reference(&"=AND(A,B)", Some(&data_function)), - "TRUE" + "#VALUE!" ); } @@ -1114,6 +1114,20 @@ fn it_evaluates_blanks_in_boolean_operations() { ); } +#[test] +fn evaluate_invalid_reference_in_functions() { + let data_function = |s: String| match s.as_str() { + _ => types::Value::Error(types::Error::Value), + }; + // test with a non existing reference + let formula = parse_formula::parse_string_to_formula("=AND(T, NO_REFERENCE)".as_ref(), None::); + assert_eq!(calculate::calculate_formula(formula, Some(&data_function)), types::Value::Error(types::Error::Value)); + assert_eq!( + evaluate_formula_boolean_with_reference(&"=AND(T, NO_REFERENCE)", Some(&data_function)), + "#VALUE!" + ); +} + #[test] fn it_evaluates_blanks_in_comparison_operators() { let data_function = |s: String| match s.as_str() { From af0f194f7ed6b44913a8933bdee8d8271180812c Mon Sep 17 00:00:00 2001 From: Shimi Bornstein Date: Tue, 14 Mar 2023 15:45:06 +0200 Subject: [PATCH 04/23] allow whitespace in all functions parameters --- src/grammar.pest | 5 ++--- src/parse_formula.rs | 1 - tests/test.rs | 36 ++++++++++++++++++++++++++---------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/grammar.pest b/src/grammar.pest index 1215ed2..987e046 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -62,8 +62,7 @@ three_params = _{ "(" ~ ")" | "(" ~ first_param ~ param ~ param ~ ")" } -atomic_expr = ${ expr } -param_inner = _{ atomic_expr | expr} +param_inner = _{ expr} first_param = { param_inner? } param = { ","? ~ param_inner? } param_with_blank = _{ param1 | blank } @@ -81,7 +80,7 @@ iterator = { "{" ~ expr ~ ("," ~ expr)*? ~ "}" } expr = { term ~ (operator ~ term)* } term = _{ number | "(" ~ expr ~ ")" | string_double_quote - | string_single_quote | logical_function | function | boolean | reference | iterator } + | string_single_quote | logical_function | function | boolean | reference | iterator } term_constant = _{ string_constant } formula = _{ SOI ~ "=" ~ expr ~ EOI | SOI ~ term_constant ~ EOI } diff --git a/src/parse_formula.rs b/src/parse_formula.rs index 6dff083..88c811c 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -343,7 +343,6 @@ fn build_formula_with_climber( Rule::custom_function => build_formula_custom_function(pair, f), Rule::iff => build_formula_iff(pair, f), Rule::isblank => build_formula_collective_operator(Rule::isblank, pair, f), - Rule::atomic_expr => build_formula_with_climber(pair.into_inner(), f), _ => unreachable!(), }, |lhs: types::Formula, op: pest::iterators::Pair, rhs: types::Formula| match op diff --git a/tests/test.rs b/tests/test.rs index 4d1380d..d24593b 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1120,8 +1120,14 @@ fn evaluate_invalid_reference_in_functions() { _ => types::Value::Error(types::Error::Value), }; // test with a non existing reference - let formula = parse_formula::parse_string_to_formula("=AND(T, NO_REFERENCE)".as_ref(), None::); - assert_eq!(calculate::calculate_formula(formula, Some(&data_function)), types::Value::Error(types::Error::Value)); + let formula = parse_formula::parse_string_to_formula( + "=AND(T, NO_REFERENCE)".as_ref(), + None::, + ); + assert_eq!( + calculate::calculate_formula(formula, Some(&data_function)), + types::Value::Error(types::Error::Value) + ); assert_eq!( evaluate_formula_boolean_with_reference(&"=AND(T, NO_REFERENCE)", Some(&data_function)), "#VALUE!" @@ -1518,17 +1524,27 @@ fn it_evaluates_isblank_function() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_string_with_reference( - &"=ISBLANK(ReferenceKey)", - Some(&data_function) - ), + evaluate_formula_string_with_reference(&"=ISBLANK(ReferenceKey)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_string_with_reference( - &"=ISBLANK(ReferenceName)", + evaluate_formula_string_with_reference(&"=ISBLANK(ReferenceName)", Some(&data_function)), + "FALSE" + ); +} + +#[test] +fn test_inner_function_with_whitespace() { + let data_function = |s: String| match s.as_str() { + "any_dropdowns" => types::Value::Number(1.0), + "primary_carrier" => types::Value::Text("Allianz*".to_owned()), + _ => types::Value::Error(types::Error::Value), + }; + assert_eq!( + evaluate_formula_boolean_with_reference( + &"=AND(RIGHT(primary_carrier, 1)=\"*\", 1 > 0)", Some(&data_function) ), - "FALSE" + "TRUE" ); -} \ No newline at end of file +} From 9c444cc6bed9ec36c828ae7a90f70540194ffffe Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Thu, 8 Jun 2023 13:01:39 +0300 Subject: [PATCH 05/23] 2021 edition, use chrono without default features --- Cargo.lock | 48 ------------------------------------------------ Cargo.toml | 16 ++++++++-------- 2 files changed, 8 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7cfb600..f3a292a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,11 +53,8 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ - "libc", "num-integer", "num-traits", - "time", - "winapi", ] [[package]] @@ -84,12 +81,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "libc" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" - [[package]] name = "maplit" version = "1.0.2" @@ -205,17 +196,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi", - "winapi", -] - [[package]] name = "typenum" version = "1.11.2" @@ -234,34 +214,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "xlformula_engine" version = "0.1.18" diff --git a/Cargo.toml b/Cargo.toml index ef5e4e7..01eb731 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [package] -name = "xlformula_engine" -version = "0.1.18" authors = ["Jirada Herbst "] -edition = "2018" -license = "MIT OR Apache-2.0" description = "A tool to parse and evaluate Excel formulas" -readme = "README.md" +edition = "2021" homepage = "https://github.com/jiradaherbst/XLFormula-Engine" -repository = "https://github.com/jiradaherbst/XLFormula-Engine" keywords = ["formula", "parser", "excel", "pest"] +license = "MIT OR Apache-2.0" +name = "xlformula_engine" +readme = "README.md" +repository = "https://github.com/jiradaherbst/XLFormula-Engine" +version = "0.1.18" [dependencies] +assert_approx_eq = "1.1.0" +chrono = {version = "0.4.19", default-features = false} pest = "2.1.3" pest_derive = "2.1.0" -assert_approx_eq = "1.1.0" -chrono = "0.4.19" From cb701133ddc75f6ea6148b607efe8cb8b5a675bc Mon Sep 17 00:00:00 2001 From: Shimi Bornstein Date: Fri, 9 Jun 2023 01:31:48 +0300 Subject: [PATCH 06/23] Fixed Blank (null) comparison to Numbers and Strings --- src/calculate.rs | 18 ++---------------- tests/test.rs | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/calculate.rs b/src/calculate.rs index 80c8530..fe94684 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -339,23 +339,11 @@ fn calculate_comparison_operator( types::Value::Boolean(types::Boolean::False) } } - types::Value::Blank => { - if f(l, 0.0) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } + types::Value::Blank => types::Value::Boolean(types::Boolean::False), _ => types::Value::Error(types::Error::Value), }, types::Value::Blank => match rhs { - types::Value::Number(r) => { - if f(0.0, r) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } + types::Value::Number(_) => types::Value::Boolean(types::Boolean::False), types::Value::Text(r) => { if r.is_empty() { types::Value::Boolean(types::Boolean::True) @@ -1050,9 +1038,7 @@ fn calculate_isblank(value: types::Value) -> types::Value { } types::Value::Error(types::Error::Value) => types::Value::Boolean(types::Boolean::True), types::Value::Error(types::Error::Reference) => types::Value::Boolean(types::Boolean::True), - _ => types::Value::Boolean(types::Boolean::False), - } } diff --git a/tests/test.rs b/tests/test.rs index d24593b..4e5e540 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1154,7 +1154,7 @@ fn it_evaluates_blanks_in_comparison_operators() { ); assert_eq!( evaluate_formula_string_with_reference(&"=1>B", Some(&data_function)), - "TRUE", + "FALSE", ); assert_eq!( evaluate_formula_string_with_reference(&"=B>1", Some(&data_function)), @@ -1162,11 +1162,23 @@ fn it_evaluates_blanks_in_comparison_operators() { ); assert_eq!( evaluate_formula_string_with_reference(&"=0=B", Some(&data_function)), - "TRUE", + "FALSE", ); assert_eq!( evaluate_formula_string_with_reference(&"=B=0", Some(&data_function)), - "TRUE", + "FALSE", + ); + assert_eq!( + evaluate_formula_boolean_with_reference(&"=B=\"\"", Some(&data_function)), + "TRUE" + ); + assert_eq!( + evaluate_formula_boolean_with_reference(&"=\"\"=B", Some(&data_function)), + "TRUE" + ); + assert_eq!( + evaluate_formula_boolean_with_reference(&"=\"something\"=B", Some(&data_function)), + "FALSE" ); assert_eq!( evaluate_formula_string_with_reference(&"=B='test'", Some(&data_function)), @@ -1191,11 +1203,11 @@ fn it_evaluates_blanks_in_comparison_operators_with_references() { ); assert_eq!( evaluate_formula_string_with_reference(&"=B>=A", Some(&data_function)), - "TRUE", + "FALSE", ); assert_eq!( evaluate_formula_string_with_reference(&"=A Date: Tue, 5 Nov 2024 21:31:38 +0200 Subject: [PATCH 07/23] Add test for RIGHT function --- tests/test.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test.rs b/tests/test.rs index 4e5e540..1af8fcf 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1560,3 +1560,18 @@ fn test_inner_function_with_whitespace() { "TRUE" ); } + +#[test] +fn test_zero_padding() { + let data_function = |s: String| match s.as_str() { + "num" => types::Value::Number(91.0), + _ => types::Value::Error(types::Error::Value), + }; + assert_eq!( + evaluate_formula_boolean_with_reference( + &"=RIGHT(\"00000000\" & num, 8)", + Some(&data_function) + ), + "00000091" + ); +} From 5a4ad798f70958daa4103a65a74088b9b39e8843 Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Tue, 5 Nov 2024 21:39:21 +0200 Subject: [PATCH 08/23] Clippy fixes --- src/calculate.rs | 13 +- src/main.rs | 93 +++--- src/parse_formula.rs | 19 +- tests/test.rs | 775 +++++++++++++++++++++---------------------- 4 files changed, 434 insertions(+), 466 deletions(-) diff --git a/src/calculate.rs b/src/calculate.rs index fe94684..7990e9a 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -32,11 +32,11 @@ fn calculate_string_operation_rhs( match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Number(r) => types::Value::Text(f(&l, &r.to_string())), - types::Value::Text(r) => types::Value::Text(f(&l, &r)), + types::Value::Number(r) => types::Value::Text(f(l, &r.to_string())), + types::Value::Text(r) => types::Value::Text(f(l, &r)), types::Value::Iterator(_) => types::Value::Error(types::Error::Value), types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Text(f(&l, "")), + types::Value::Blank => types::Value::Text(f(l, "")), } } @@ -238,10 +238,9 @@ fn calculate_average_operator_rhs_number( types::Value::Number(r) => types::Value::Number(f(l, r)), types::Value::Iterator(mut value_vec) => { if let Some(mut temp) = value_vec.pop() { - match temp { - types::Value::Blank => *element_count -= 1, - _ => (), - }; + if temp == types::Value::Blank { + *element_count -= 1; + } while let Some(top) = value_vec.pop() { temp = calculate_numeric_operator(temp, top.clone(), f); match top { diff --git a/src/main.rs b/src/main.rs index a08b570..422a6b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use xlformula_engine::NoCustomFunction; use xlformula_engine::NoReference; fn main() -> Result<(), ParseError> { - let formula = parse_formula::parse_string_to_formula(&"=1+2", None::); + let formula = parse_formula::parse_string_to_formula("=1+2", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); @@ -18,103 +18,99 @@ fn main() -> Result<(), ParseError> { "C" => types::Value::Text("=1+A".to_string()), _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula(&"=A+B", None::); + let formula = parse_formula::parse_string_to_formula("=A+B", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=SUM(A,B,C)", None::); + let formula = parse_formula::parse_string_to_formula("=SUM(A,B,C)", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=1+2", None::); + let formula = parse_formula::parse_string_to_formula("=1+2", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=(1*(2+3))*2", None::); + let formula = parse_formula::parse_string_to_formula("=(1*(2+3))*2", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=1+3/0", None::); // error (#DIV/0!) + let formula = parse_formula::parse_string_to_formula("=1+3/0", None::); // error (#DIV/0!) let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( - &"=\"Hello \" & \" World!\"", + r#"="Hello " & " World!""#, None::, ); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"=1 + \"Hello\"", None::); // error (#CAST!) + parse_formula::parse_string_to_formula(r#"=1 + "Hello""#, None::); // error (#CAST!) let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"1.2", None::); + let formula = parse_formula::parse_string_to_formula("1.2", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"Hello World", None::); + let formula = parse_formula::parse_string_to_formula("Hello World", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=ABS(-1)", None::); + let formula = parse_formula::parse_string_to_formula("=ABS(-1)", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"=SUM(1,2,\"3\")", None::); + parse_formula::parse_string_to_formula(r#"=SUM(1,2,"3")"#, None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( - &"=PRODUCT(ABS(1),2*1, 3,4*1)", + "=PRODUCT(ABS(1),2*1, 3,4*1)", None::, ); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=2>=1", None::); + let formula = parse_formula::parse_string_to_formula("=2>=1", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = - parse_formula::parse_string_to_formula(&"=OR(1>1,1<>1)", None::); + let formula = parse_formula::parse_string_to_formula("=OR(1>1,1<>1)", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( - &"=AND(\"test\",\"True\", 1, true) ", + r#"=AND("test","True", 1, true) "#, None::, ); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( - &"=SUM({1,2,3}, 4, {5,6,7})", + "=SUM({1,2,3}, 4, {5,6,7})", None::, ); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula( - &"=AVERAGE({1,2,3},1,2,3)", - None::, - ); + let formula = + parse_formula::parse_string_to_formula("=AVERAGE({1,2,3},1,2,3)", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = - parse_formula::parse_string_to_formula(&"=XOR({0,0,0})", None::); + let formula = parse_formula::parse_string_to_formula("=XOR({0,0,0})", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"={1,2,3}+{1,2,3}", None::); + parse_formula::parse_string_to_formula("={1,2,3}+{1,2,3}", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"={0,0}+{1,2,3}", None::); + parse_formula::parse_string_to_formula("={0,0}+{1,2,3}", None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); // error (#ARG!) @@ -127,15 +123,15 @@ fn main() -> Result<(), ParseError> { }; let formula = - parse_formula::parse_string_to_formula(&"=DAYS(end, start)", None::); + parse_formula::parse_string_to_formula("=DAYS(end, start)", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=start+1", None::); + let formula = parse_formula::parse_string_to_formula("=start+1", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=end-3", None::); + let formula = parse_formula::parse_string_to_formula("=end-3", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); @@ -146,18 +142,16 @@ fn main() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; - let formula = - parse_formula::parse_string_to_formula(&"=Increase(1)+1", Some(&custom_functions)); + let formula = parse_formula::parse_string_to_formula("=Increase(1)+1", Some(&custom_functions)); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let formula = - parse_formula::parse_string_to_formula(&"=EqualFive()+1", Some(&custom_functions)); + let formula = parse_formula::parse_string_to_formula("=EqualFive()+1", Some(&custom_functions)); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"=SimpleSum(1,2)", Some(&custom_functions)); + parse_formula::parse_string_to_formula("=SimpleSum(1,2)", Some(&custom_functions)); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); @@ -166,35 +160,35 @@ fn main() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula(&"=EqualFive()", Some(&custom_function)); + let formula = parse_formula::parse_string_to_formula("=EqualFive()", Some(&custom_function)); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); ///////////// RIGHT function let formula = - parse_formula::parse_string_to_formula(&"=RIGHT(\"apple\", 3)", None::); + parse_formula::parse_string_to_formula(r#"=RIGHT("apple", 3)"#, None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"=RIGHT(\"apple\")", None::); + parse_formula::parse_string_to_formula(r#"=RIGHT("apple")"#, None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( - &"=\"P\"&RIGHT(\"000\"&1,3)", + r#"="P"&RIGHT("000"&1,3)"#, None::, ); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); ///////////// LEFT function let formula = - parse_formula::parse_string_to_formula(&"=LEFT(\"apple\", 3)", None::); + parse_formula::parse_string_to_formula(r#"=LEFT("apple", 3)"#, None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"=LEFT(\"apple\")", None::); + parse_formula::parse_string_to_formula(r#"=LEFT("apple")"#, None::); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); @@ -210,12 +204,12 @@ fn main() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula(&"=SUM(B, 1)", None::); + let formula = parse_formula::parse_string_to_formula("=SUM(B, 1)", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"=SUM(BLANK(), 1)", Some(&custom_functions)); + parse_formula::parse_string_to_formula("=SUM(BLANK(), 1)", Some(&custom_functions)); let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); @@ -226,18 +220,17 @@ fn main() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula(&"=OR({F,B})", None::); + let formula = parse_formula::parse_string_to_formula("=OR({F,B})", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(&"=SUM(1, 2, , 3)", None::); + parse_formula::parse_string_to_formula("=SUM(1, 2, , 3)", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); ///////////// IF function - let formula = - parse_formula::parse_string_to_formula(&"=IF(TRUE,1,0)", None::); + let formula = parse_formula::parse_string_to_formula("=IF(TRUE,1,0)", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); @@ -248,7 +241,7 @@ fn main() -> Result<(), ParseError> { }; let formula = parse_formula::parse_string_to_formula( - &"=IF(ReferenceKey=\"10\",\"\",ReferenceKey&\" - \")&ReferenceName", + r#"=IF(ReferenceKey="10","",ReferenceKey&" - ")&ReferenceName"#, None::, ); @@ -261,11 +254,13 @@ fn main() -> Result<(), ParseError> { "ReferenceName" => types::Value::Text("Test".to_string()), _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula(&"=ISBLANK(ReferenceKey)", None::,); + let formula = + parse_formula::parse_string_to_formula("=ISBLANK(ReferenceKey)", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=ISBLANK(ReferenceName)", None::,); + let formula = + parse_formula::parse_string_to_formula("=ISBLANK(ReferenceName)", None::); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); diff --git a/src/parse_formula.rs b/src/parse_formula.rs index 88c811c..d419c28 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -42,7 +42,7 @@ pub fn parse_string_to_formula( s: &str, f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { - match parse_string(&s) { + match parse_string(s) { Some(parse_result) => match parse_result.as_rule() { Rule::expr => build_formula_with_climber(parse_result.into_inner(), f), Rule::string_constant => parse_string_constant(parse_result), @@ -235,19 +235,12 @@ fn build_formula_custom_function( ) -> types::Formula { let mut vec = Vec::new(); for field in pair.clone().into_inner() { - match field.as_rule() { - Rule::expr => - // vec.push(field.into_inner().as_str().parse::().unwrap()), - // if custom formula is undefined, then don't have anything to push into vec - { - let x = field.into_inner().as_str(); - let y = x.parse::(); - match y { - Ok(_) => vec.push(y.unwrap()), - Err(_) => (), - } + if field.as_rule() == Rule::expr { + let x = field.into_inner().as_str(); + let y = x.parse::(); + if let Ok(y) = y { + vec.push(y); } - _ => (), } } let mut ref_string = String::new(); diff --git a/tests/test.rs b/tests/test.rs index 1af8fcf..d62ce6e 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -45,8 +45,7 @@ fn evaluate_formula_number_with_reference_no_conversion( f: Option<&impl Fn(String) -> types::Value>, ) -> types::Value { let formula = parse_formula::parse_string_to_formula(s, None::); - let result = calculate::calculate_formula(formula, f); - result + calculate::calculate_formula(formula, f) //calculate::result_to_string(result).parse::().unwrap() } @@ -101,141 +100,141 @@ fn _evaluate_formula_number_with_custom_function_and_reference( /////////////////// Simple math operators with floats and integer /////////////////// #[test] fn it_evaluate_add_operator_simple_addition() { - assert_eq!(evaluate_formula_number(&"=1+2"), 3.0,); + assert_eq!(evaluate_formula_number("=1+2"), 3.0,); } #[test] fn it_evaluate_add_operator_spaces_between_operators() { - assert_eq!(evaluate_formula_number(&"=1 + 2"), 3.0,); + assert_eq!(evaluate_formula_number("=1 + 2"), 3.0,); } #[test] fn it_evaluate_add_operator_spaces_before_number() { - assert_eq!(evaluate_formula_number(&"= 1+2"), 3.0,); + assert_eq!(evaluate_formula_number("= 1+2"), 3.0,); } #[test] fn it_evaluate_add_operator_with_large_numbers() { assert_eq!( - evaluate_formula_number(&"=1234567890 + 1234567890"), + evaluate_formula_number("=1234567890 + 1234567890"), 2469135780.0 ); } #[test] fn it_evaluate_add_operator_with_negative_numbers() { - assert_eq!(evaluate_formula_number(&"=-1 + -2"), -3.0); + assert_eq!(evaluate_formula_number("=-1 + -2"), -3.0); } #[test] fn it_evaluate_minus_operator1() { - assert_eq!(evaluate_formula_number(&"=123 - 23"), 100.0,); + assert_eq!(evaluate_formula_number("=123 - 23"), 100.0,); } #[test] fn it_evaluate_minus_operator_with_negative_numbers() { - assert_eq!(evaluate_formula_number(&"=-12--6"), -6.0,); + assert_eq!(evaluate_formula_number("=-12--6"), -6.0,); } #[test] fn it_evaluate_multiply_operator() { - assert_eq!(evaluate_formula_number(&"=3 * 2"), 6.0,); + assert_eq!(evaluate_formula_number("=3 * 2"), 6.0,); } #[test] fn it_evaluate_divide_operator() { - assert_eq!(evaluate_formula_number(&"=6 / 3"), 2.0,); + assert_eq!(evaluate_formula_number("=6 / 3"), 2.0,); } #[test] fn it_evaluate_divide_operator_divsion_by_zero() { - assert_eq!(evaluate_formula_string(&"=6 / 0"), "#DIV/0!"); + assert_eq!(evaluate_formula_string("=6 / 0"), "#DIV/0!"); } #[test] fn it_evaluate_negative() { - assert_eq!(evaluate_formula_number(&"=-1 * -5"), 5.0,); + assert_eq!(evaluate_formula_number("=-1 * -5"), 5.0,); } #[test] fn it_evaluate_power_int() { - assert_eq!(evaluate_formula_number(&"=2^3"), 8.0,); + assert_eq!(evaluate_formula_number("=2^3"), 8.0,); } #[test] fn it_evaluate_float() { - assert_eq!(evaluate_formula_number(&"=1.2+0.5"), 1.7,); + assert_eq!(evaluate_formula_number("=1.2+0.5"), 1.7,); } #[test] fn it_evaluate_negative_float() { - assert_approx_eq!(evaluate_formula_number(&"=-1.2+0.5"), -0.7); // left: `-0.70000005`, right: `-0.7`' + assert_approx_eq!(evaluate_formula_number("=-1.2+0.5"), -0.7); // left: `-0.70000005`, right: `-0.7`' } #[test] fn it_evaluate_power_float() { - assert_eq!(evaluate_formula_number(&"=4^0.5"), 2.0,); + assert_eq!(evaluate_formula_number("=4^0.5"), 2.0,); } #[test] fn it_evaluate_multiple_operations() { - assert_eq!(evaluate_formula_number(&"=1+2+3"), 6.0,); + assert_eq!(evaluate_formula_number("=1+2+3"), 6.0,); } #[test] fn it_evaluate_multiple_operations2() { - assert_eq!(evaluate_formula_number(&"=1+2-3"), 0.0,); + assert_eq!(evaluate_formula_number("=1+2-3"), 0.0,); } #[test] fn it_evaluate_multiple_operations_in_right_order() { - assert_eq!(evaluate_formula_number(&"=1+2*3"), 7.0,); + assert_eq!(evaluate_formula_number("=1+2*3"), 7.0,); } #[test] fn it_evaluate_multiple_operations_in_right_order2() { - assert_eq!(evaluate_formula_number(&"=1+3/3"), 2.0,); + assert_eq!(evaluate_formula_number("=1+3/3"), 2.0,); } #[test] fn it_evaluate_multiple_operations_with_errors() { - assert_eq!(evaluate_formula_string(&"=1+3/0"), "#DIV/0!",); + assert_eq!(evaluate_formula_string("=1+3/0"), "#DIV/0!",); } #[test] fn it_evaluate_parens() { - assert_eq!(evaluate_formula_number(&"=(1+2)"), 3.0,); + assert_eq!(evaluate_formula_number("=(1+2)"), 3.0,); } #[test] fn it_evaluate_multiple_parens() { - assert_eq!(evaluate_formula_number(&"=(1+2)+(3+4)"), 10.0,); + assert_eq!(evaluate_formula_number("=(1+2)+(3+4)"), 10.0,); } #[test] fn it_evaluate_nested_parens() { - assert_eq!(evaluate_formula_number(&"=(1*(2+3))*2"), 10.0,); + assert_eq!(evaluate_formula_number("=(1*(2+3))*2"), 10.0,); } /////////////////// Strings /////////////////// #[test] fn it_evaluate_strings() { - assert_eq!(evaluate_formula_string(&"=\"Hello! \""), "Hello! ",); + assert_eq!(evaluate_formula_string("=\"Hello! \""), "Hello! ",); } #[test] fn it_evaluate_strings_in_numeric_operator() { - assert_eq!(evaluate_formula_string(&"=\"Hello\"+1"), "#CAST!",); + assert_eq!(evaluate_formula_string("=\"Hello\"+1"), "#CAST!",); } #[test] fn it_evaluate_strings_in_numeric_operator2() { - assert_eq!(evaluate_formula_string(&"=1 + \"Hello\""), "#CAST!",); + assert_eq!(evaluate_formula_string("=1 + \"Hello\""), "#CAST!",); } #[test] fn it_evaluate_concat_operator1() { assert_eq!( - evaluate_formula_string(&"=\"Hello\" & \"World!\""), + evaluate_formula_string("=\"Hello\" & \"World!\""), "HelloWorld!", ); } @@ -243,30 +242,30 @@ fn it_evaluate_concat_operator1() { #[test] fn it_evaluate_concat_operator3() { assert_eq!( - evaluate_formula_string(&"=\"Hello \" & \" World!\""), + evaluate_formula_string("=\"Hello \" & \" World!\""), "Hello World!", ); } #[test] fn it_evaluate_concat_operator_with_casting() { - assert_eq!(evaluate_formula_string(&"=\"Hello\"&1"), "Hello1",); + assert_eq!(evaluate_formula_string("=\"Hello\"&1"), "Hello1",); } #[test] fn it_evaluate_concat_operator_with_casting2() { - assert_eq!(evaluate_formula_string(&"=\"Hello \"&1.2"), "Hello 1.2",); + assert_eq!(evaluate_formula_string("=\"Hello \"&1.2"), "Hello 1.2",); } #[test] fn it_evaluate_concat_operator_with_numberic() { - assert_eq!(evaluate_formula_string(&"=1 & 2"), "12",); + assert_eq!(evaluate_formula_string("=1 & 2"), "12",); } #[test] fn it_evaluate_strings_with_quoted_quotes1() { assert_eq!( - evaluate_formula_string(&"=\"Hello 'World'\""), + evaluate_formula_string("=\"Hello 'World'\""), "Hello 'World'", ); } @@ -274,7 +273,7 @@ fn it_evaluate_strings_with_quoted_quotes1() { #[test] fn it_evaluate_strings_with_quoted_quotes() { assert_eq!( - evaluate_formula_string(&"=\"Hello \"\"World\"\"\""), + evaluate_formula_string("=\"Hello \"\"World\"\"\""), "Hello \"World\"", ); } @@ -282,7 +281,7 @@ fn it_evaluate_strings_with_quoted_quotes() { #[test] fn it_evaluate_strings_with_single_quotes() { assert_eq!( - evaluate_formula_string(&"=\"Hello \"&'World'"), + evaluate_formula_string("=\"Hello \"&'World'"), "Hello World", ); } @@ -290,7 +289,7 @@ fn it_evaluate_strings_with_single_quotes() { #[test] fn it_evaluate_strings_with_quotes() { assert_eq!( - evaluate_formula_string(&"='Hello \"World\"'"), + evaluate_formula_string("='Hello \"World\"'"), "Hello \"World\"", ); } @@ -298,7 +297,7 @@ fn it_evaluate_strings_with_quotes() { #[test] fn it_evaluate_strings_with_quotes2() { assert_eq!( - evaluate_formula_string(&"='Hello'"), // '& 'World' + evaluate_formula_string("='Hello'"), // '& 'World' "Hello", ); } @@ -306,206 +305,203 @@ fn it_evaluate_strings_with_quotes2() { /////////////////// Constants /////////////////// #[test] fn it_evaluate_constant_number() { - assert_eq!(evaluate_formula_number(&"1"), 1.0,); + assert_eq!(evaluate_formula_number("1"), 1.0,); } #[test] fn it_evaluate_constant_number_float() { - assert_eq!(evaluate_formula_number(&"1.2"), 1.2,); + assert_eq!(evaluate_formula_number("1.2"), 1.2,); } #[test] fn it_evaluate_constant_text() { - assert_eq!(evaluate_formula_string(&"Hello World"), "Hello World",); + assert_eq!(evaluate_formula_string("Hello World"), "Hello World",); } #[test] fn it_evaluate_constant_text_with_quotes() { - assert_eq!(evaluate_formula_string(&"Hello \"World'"), "Hello \"World'",); + assert_eq!(evaluate_formula_string("Hello \"World'"), "Hello \"World'",); } #[test] fn it_evaluate_constant_starting_with_equal() { - assert_eq!(evaluate_formula_string(&"'="), "=",); - assert_eq!(evaluate_formula_string(&"'=hello"), "=hello",); + assert_eq!(evaluate_formula_string("'="), "=",); + assert_eq!(evaluate_formula_string("'=hello"), "=hello",); } /////////////////// Formulas /////////////////// #[test] fn it_support_basic_math_function() { - assert_eq!(evaluate_formula_number(&"=ABS(-1)"), 1.0,); + assert_eq!(evaluate_formula_number("=ABS(-1)"), 1.0,); } #[test] fn it_support_basic_math_function_with_nested_formulas() { - assert_eq!(evaluate_formula_number(&"=ABS(-1-4)"), 5.0,); + assert_eq!(evaluate_formula_number("=ABS(-1-4)"), 5.0,); } #[test] fn it_support_basic_math_function_with_nested_functions() { - assert_eq!(evaluate_formula_number(&"=ABS(ABS(-1))"), 1.0,); + assert_eq!(evaluate_formula_number("=ABS(ABS(-1))"), 1.0,); } #[test] fn it_evaluate_functions_sum() { assert_eq!( - evaluate_formula_number(&"=SUM(1*1, ABS(2), ABS(2+1), 4)"), + evaluate_formula_number("=SUM(1*1, ABS(2), ABS(2+1), 4)"), 10.0, ); - assert_eq!(evaluate_formula_number(&"=SUM(1, 2, , 3)"), 6.0,); - assert_eq!(evaluate_formula_number(&"=SUM( 1 , 2,,3,)"), 6.0,); - assert_eq!(evaluate_formula_number(&"=SUM( 1 , 2,,,3,)"), 6.0,); - assert_eq!(evaluate_formula_number(&"=SUM(,)"), 0.0,); + assert_eq!(evaluate_formula_number("=SUM(1, 2, , 3)"), 6.0,); + assert_eq!(evaluate_formula_number("=SUM( 1 , 2,,3,)"), 6.0,); + assert_eq!(evaluate_formula_number("=SUM( 1 , 2,,,3,)"), 6.0,); + assert_eq!(evaluate_formula_number("=SUM(,)"), 0.0,); } #[test] fn it_evaluate_functions_avg() { - assert_eq!(evaluate_formula_number(&"=AVERAGE(1,2)"), 1.5,); - assert_eq!(evaluate_formula_number(&"=AVERAGE({1,2,3})"), 2.0,); - assert_eq!(evaluate_formula_number(&"=AVERAGE({1,2,3},1,2,3)"), 2.0,); - assert_eq!(evaluate_formula_number(&"=AVERAGE(3,1,2,3)"), 2.25,); - assert_eq!(evaluate_formula_number(&"=AVERAGE(1,2,3,4,5,1,2,3)"), 2.625,); + assert_eq!(evaluate_formula_number("=AVERAGE(1,2)"), 1.5,); + assert_eq!(evaluate_formula_number("=AVERAGE({1,2,3})"), 2.0,); + assert_eq!(evaluate_formula_number("=AVERAGE({1,2,3},1,2,3)"), 2.0,); + assert_eq!(evaluate_formula_number("=AVERAGE(3,1,2,3)"), 2.25,); + assert_eq!(evaluate_formula_number("=AVERAGE(1,2,3,4,5,1,2,3)"), 2.625,); assert_eq!( - evaluate_formula_number(&"=AVERAGE({1,2,3,4,5},1,2,3)"), + evaluate_formula_number("=AVERAGE({1,2,3,4,5},1,2,3)"), 2.625, ); assert_eq!( - evaluate_formula_number(&"=AVERAGE(AVERAGE({1,2,3,4,5}),1,2,3)"), + evaluate_formula_number("=AVERAGE(AVERAGE({1,2,3,4,5}),1,2,3)"), 2.25, ); - assert_eq!(evaluate_formula_number(&"=AVERAGE({100,200})"), 150.0,); - assert_eq!(evaluate_formula_number(&"=AVERAGE( 1 , 2,,3)"), 1.5,); - assert_eq!(evaluate_formula_number(&"=AVERAGE( 1 , )"), 0.5,); - assert_eq!(evaluate_formula_number(&"=AVERAGE(,)"), 0.0,); - assert_eq!(evaluate_formula_number(&"=AVERAGE(1,,2,3,)",), 1.2); + assert_eq!(evaluate_formula_number("=AVERAGE({100,200})"), 150.0,); + assert_eq!(evaluate_formula_number("=AVERAGE( 1 , 2,,3)"), 1.5,); + assert_eq!(evaluate_formula_number("=AVERAGE( 1 , )"), 0.5,); + assert_eq!(evaluate_formula_number("=AVERAGE(,)"), 0.0,); + assert_eq!(evaluate_formula_number("=AVERAGE(1,,2,3,)",), 1.2); //assert_eq!(evaluate_formula_number(&"=AVERAGE({{100,200}})"), 150.0); } #[test] fn it_evaluate_functions_product() { - assert_eq!( - evaluate_formula_number(&"=PRODUCT(ABS(1),2*1, 3,4*1)"), - 24.0, - ); - assert_eq!(evaluate_formula_number(&"=PRODUCT( 1 , 2,,3)"), 6.0,); - assert_eq!(evaluate_formula_number(&"=PRODUCT(,)"), 0.0,); + assert_eq!(evaluate_formula_number("=PRODUCT(ABS(1),2*1, 3,4*1)"), 24.0,); + assert_eq!(evaluate_formula_number("=PRODUCT( 1 , 2,,3)"), 6.0,); + assert_eq!(evaluate_formula_number("=PRODUCT(,)"), 0.0,); } #[test] fn it_evaluate_operators_with_casting() { - assert_eq!(evaluate_formula_number(&"=\"1\"+2+\"3\""), 6.0,); + assert_eq!(evaluate_formula_number("=\"1\"+2+\"3\""), 6.0,); } #[test] fn it_evaluate_functions_with_casting() { - assert_eq!(evaluate_formula_number(&"=SUM(1,2,\"3\")"), 6.0,); + assert_eq!(evaluate_formula_number("=SUM(1,2,\"3\")"), 6.0,); } /////////////////////// Parse error ////////////////////////////////// #[test] fn it_evaluate_wrong_parens1() { - assert_eq!(evaluate_formula_string(&"=(2+3"), "#PARSE!",); - assert_eq!(evaluate_formula_string(&"=\"Hello World"), "#PARSE!",); - assert_eq!(evaluate_formula_string(&"=Hello World"), "#PARSE!",); + assert_eq!(evaluate_formula_string("=(2+3"), "#PARSE!",); + assert_eq!(evaluate_formula_string("=\"Hello World"), "#PARSE!",); + assert_eq!(evaluate_formula_string("=Hello World"), "#PARSE!",); } //////////////////////////// Boolean ////////////////////////////////// #[test] fn it_evaluate_comparison_operators() { - assert_eq!(evaluate_formula_string(&"=1*1=1/1"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=1^1<>1"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=1*2>1"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=1*1/1+2<1^1"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=2>=1"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=11<=3"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=\"Joshu\"=\"Joshu\""), "TRUE",); - assert_eq!(evaluate_formula_string(&"=\"Joshu\"<>\"NotJoshu\""), "TRUE",); + assert_eq!(evaluate_formula_string("=1*1=1/1"), "TRUE",); + assert_eq!(evaluate_formula_string("=1^1<>1"), "FALSE",); + assert_eq!(evaluate_formula_string("=1*2>1"), "TRUE",); + assert_eq!(evaluate_formula_string("=1*1/1+2<1^1"), "FALSE",); + assert_eq!(evaluate_formula_string("=2>=1"), "TRUE",); + assert_eq!(evaluate_formula_string("=11<=3"), "FALSE",); + assert_eq!(evaluate_formula_string("=\"Joshu\"=\"Joshu\""), "TRUE",); + assert_eq!(evaluate_formula_string("=\"Joshu\"<>\"NotJoshu\""), "TRUE",); } #[test] fn it_evaluate_boolean_or() { - assert_eq!(evaluate_formula_string(&"=OR(1>1,1<>1)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=OR(1=1,2<=4)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=OR(\"True\")"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=OR(True)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=OR(1)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=OR(\"test\")"), "#CAST!",); - assert_eq!( - evaluate_formula_string(&"=OR(\"false\",\"FALSE\", 1, FALSE)"), + assert_eq!(evaluate_formula_string("=OR(1>1,1<>1)"), "FALSE",); + assert_eq!(evaluate_formula_string("=OR(1=1,2<=4)"), "TRUE",); + assert_eq!(evaluate_formula_string("=OR(\"True\")"), "TRUE",); + assert_eq!(evaluate_formula_string("=OR(True)"), "TRUE",); + assert_eq!(evaluate_formula_string("=OR(1)"), "TRUE",); + assert_eq!(evaluate_formula_string("=OR(\"test\")"), "#CAST!",); + assert_eq!( + evaluate_formula_string("=OR(\"false\",\"FALSE\", 1, FALSE)"), "TRUE", ); assert_eq!( - evaluate_formula_string(&"=OR(\"True\",1,\"test\", true) "), + evaluate_formula_string("=OR(\"True\",1,\"test\", true) "), "TRUE", ); - assert_eq!(evaluate_formula_string(&"=OR(1, )"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=OR(1,,, )"), "TRUE",); + assert_eq!(evaluate_formula_string("=OR(1, )"), "TRUE",); + assert_eq!(evaluate_formula_string("=OR(1,,, )"), "TRUE",); } #[test] fn it_evaluate_boolean_and() { - assert_eq!(evaluate_formula_string(&"=AND(1>1,1=1)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=AND(1=1,2<=4)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=AND(\"true\", 0)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=AND(\"True\")"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=AND(false)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=AND(1)"), "TRUE",); - assert_eq!( - evaluate_formula_string(&"=AND(\"test\", \"test\")"), + assert_eq!(evaluate_formula_string("=AND(1>1,1=1)"), "FALSE",); + assert_eq!(evaluate_formula_string("=AND(1=1,2<=4)"), "TRUE",); + assert_eq!(evaluate_formula_string("=AND(\"true\", 0)"), "FALSE",); + assert_eq!(evaluate_formula_string("=AND(\"True\")"), "TRUE",); + assert_eq!(evaluate_formula_string("=AND(false)"), "FALSE",); + assert_eq!(evaluate_formula_string("=AND(1)"), "TRUE",); + assert_eq!( + evaluate_formula_string("=AND(\"test\", \"test\")"), "#VALUE!", ); assert_eq!( - evaluate_formula_string(&"=AND(\"test\",\"True\", 1, true) "), + evaluate_formula_string("=AND(\"test\",\"True\", 1, true) "), "#VALUE!", ); assert_eq!( - evaluate_formula_string(&"=AND(\"True\",\"test\", 1, true) "), + evaluate_formula_string("=AND(\"True\",\"test\", 1, true) "), "#VALUE!", ); assert_eq!( - evaluate_formula_string(&"=AND(\"True\", 1, true, \"test\")"), + evaluate_formula_string("=AND(\"True\", 1, true, \"test\")"), "#VALUE!", ); - assert_eq!(evaluate_formula_string(&"=AND(1, )"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=AND(1,,,1)"), "FALSE",); + assert_eq!(evaluate_formula_string("=AND(1, )"), "FALSE",); + assert_eq!(evaluate_formula_string("=AND(1,,,1)"), "FALSE",); } #[test] fn it_evaluate_boolean_xor() { - assert_eq!(evaluate_formula_string(&"=XOR(2=2,1=1)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=XOR(1=1,2>4)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=XOR(\"True\")"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=XOR(False)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=XOR(1)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=XOR(1=1,\"test\")"), "TRUE",); - assert_eq!( - evaluate_formula_string(&"=XOR(TRUE, TRUE, TRUE, TRUE)"), + assert_eq!(evaluate_formula_string("=XOR(2=2,1=1)"), "FALSE",); + assert_eq!(evaluate_formula_string("=XOR(1=1,2>4)"), "TRUE",); + assert_eq!(evaluate_formula_string("=XOR(\"True\")"), "TRUE",); + assert_eq!(evaluate_formula_string("=XOR(False)"), "FALSE",); + assert_eq!(evaluate_formula_string("=XOR(1)"), "TRUE",); + assert_eq!(evaluate_formula_string("=XOR(1=1,\"test\")"), "TRUE",); + assert_eq!( + evaluate_formula_string("=XOR(TRUE, TRUE, TRUE, TRUE)"), "FALSE" ); - assert_eq!(evaluate_formula_string(&"=XOR(TRUE, TRUE)"), "FALSE"); + assert_eq!(evaluate_formula_string("=XOR(TRUE, TRUE)"), "FALSE"); assert_eq!( - evaluate_formula_string(&"=XOR(false, FALSE, \"false\", false)"), + evaluate_formula_string("=XOR(false, FALSE, \"false\", false)"), "FALSE" ); - assert_eq!(evaluate_formula_string(&"=XOR(true)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=XOR(false)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=XOR(1, )"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=XOR(1,, )"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=XOR(1,,1 )"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=XOR(1,,1, )"), "FALSE",); + assert_eq!(evaluate_formula_string("=XOR(true)"), "TRUE",); + assert_eq!(evaluate_formula_string("=XOR(false)"), "FALSE",); + assert_eq!(evaluate_formula_string("=XOR(1, )"), "TRUE",); + assert_eq!(evaluate_formula_string("=XOR(1,, )"), "TRUE",); + assert_eq!(evaluate_formula_string("=XOR(1,,1 )"), "FALSE",); + assert_eq!(evaluate_formula_string("=XOR(1,,1, )"), "FALSE",); } #[test] fn it_evaluate_boolean_not() { - assert_eq!(evaluate_formula_string(&"=NOT(11<=3)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=NOT(1=1)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=NOT(True)"), "FALSE",); - assert_eq!(evaluate_formula_string(&"=NOT(\"false\")"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=NOT(\"test\")"), "#CAST!",); - assert_eq!(evaluate_formula_string(&"=NOT(0)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=not(11<=3)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=Not(11<=3)"), "TRUE",); - assert_eq!(evaluate_formula_string(&"=nOT(11<=3)"), "TRUE",); + assert_eq!(evaluate_formula_string("=NOT(11<=3)"), "TRUE",); + assert_eq!(evaluate_formula_string("=NOT(1=1)"), "FALSE",); + assert_eq!(evaluate_formula_string("=NOT(True)"), "FALSE",); + assert_eq!(evaluate_formula_string("=NOT(\"false\")"), "TRUE",); + assert_eq!(evaluate_formula_string("=NOT(\"test\")"), "#CAST!",); + assert_eq!(evaluate_formula_string("=NOT(0)"), "TRUE",); + assert_eq!(evaluate_formula_string("=not(11<=3)"), "TRUE",); + assert_eq!(evaluate_formula_string("=Not(11<=3)"), "TRUE",); + assert_eq!(evaluate_formula_string("=nOT(11<=3)"), "TRUE",); } //////////////////////////// References ////////////////////////////////// @@ -520,42 +516,40 @@ fn it_evaluate_references() { "D" => types::Value::Number(1.0), "F" => types::Value::Text("=D+1".to_string()), "G" => types::Value::Text("=F+1+D+1".to_string()), - "Test" => { - let mut vec = Vec::new(); - vec.push(types::Value::Number(100.0)); - vec.push(types::Value::Number(200.0)); - vec.push(types::Value::Number(300.0)); - types::Value::Iterator(vec) - } + "Test" => types::Value::Iterator(vec![ + types::Value::Number(100.0), + types::Value::Number(200.0), + types::Value::Number(300.0), + ]), _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=A+B", Some(&data_function)), + evaluate_formula_number_with_reference("=A+B", Some(&data_function)), 3.0, ); assert_eq!( - evaluate_formula_number_with_reference(&"=(A*(B+C))*B", Some(&data_function)), + evaluate_formula_number_with_reference("=(A*(B+C))*B", Some(&data_function)), 10.0, ); assert_eq!( - evaluate_formula_number_with_reference(&"=fix_rate*input.", Some(&data_function)), + evaluate_formula_number_with_reference("=fix_rate*input.", Some(&data_function)), 20.0, ); assert_eq!( - evaluate_formula_number_with_reference(&"=D+F", Some(&data_function)), + evaluate_formula_number_with_reference("=D+F", Some(&data_function)), 3.0, ); assert_eq!( - evaluate_formula_number_with_reference(&"=G+F", Some(&data_function)), + evaluate_formula_number_with_reference("=G+F", Some(&data_function)), 7.0, ); assert_eq!( - evaluate_formula_number_with_reference(&"=SUM(A,B,C)", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM(A,B,C)", Some(&data_function)), 6.0, ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE(Test)", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE(Test)", Some(&data_function)), 200.0, ); } @@ -568,7 +562,7 @@ fn it_evaluate_references_other_formulas() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=A+B", Some(&data_function)), + evaluate_formula_number_with_reference("=A+B", Some(&data_function)), 7.0 ); } @@ -581,7 +575,7 @@ fn it_evaluate_references_boolean_formulas() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND(A,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND(A,B)", Some(&data_function)), "FALSE" ); } @@ -594,7 +588,7 @@ fn it_evaluate_references_error_value_formulas() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND(A,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND(A,B)", Some(&data_function)), "#VALUE!" ); } @@ -607,35 +601,32 @@ fn it_evaluate_references_with_dot() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=A.B+B.C", Some(&data_function)), + evaluate_formula_number_with_reference("=A.B+B.C", Some(&data_function)), 3.0, ); } #[test] fn it_evaluate_iterators() { - assert_eq!(evaluate_formula_number(&"=SUM({1,2,3})"), 6.0,); - assert_eq!(evaluate_formula_number(&"=PRODUCT({1,2,3})"), 6.0,); - assert_eq!(evaluate_formula_number(&"=SUM({1,2,3}, {5,6})"), 17.0,); + assert_eq!(evaluate_formula_number("=SUM({1,2,3})"), 6.0,); + assert_eq!(evaluate_formula_number("=PRODUCT({1,2,3})"), 6.0,); + assert_eq!(evaluate_formula_number("=SUM({1,2,3}, {5,6})"), 17.0,); } #[test] fn it_evaluate_iterators_and_scalars() { - assert_eq!(evaluate_formula_number(&"=SUM({1,2,3}, 4)"), 10.0,); - assert_eq!(evaluate_formula_number(&"=PRODUCT({1,2,3}, 4)"), 24.0,); + assert_eq!(evaluate_formula_number("=SUM({1,2,3}, 4)"), 10.0,); + assert_eq!(evaluate_formula_number("=PRODUCT({1,2,3}, 4)"), 24.0,); } #[test] fn it_evaluate_multiple_iterators_and_scalars() { - assert_eq!(evaluate_formula_number(&"=SUM({1,2,3})"), 6.0,); - assert_eq!(evaluate_formula_number(&"=SUM({ 1,2,3}, 4, {5, 6})"), 21.0,); - assert_eq!(evaluate_formula_number(&"=SUM({1,2,3},4,{5,6})"), 21.0,); - assert_eq!( - evaluate_formula_number(&"=SUM({1+1,2,3-3},4,{5,6*1})"), - 19.0, - ); + assert_eq!(evaluate_formula_number("=SUM({1,2,3})"), 6.0,); + assert_eq!(evaluate_formula_number("=SUM({ 1,2,3}, 4, {5, 6})"), 21.0,); + assert_eq!(evaluate_formula_number("=SUM({1,2,3},4,{5,6})"), 21.0,); + assert_eq!(evaluate_formula_number("=SUM({1+1,2,3-3},4,{5,6*1})"), 19.0,); assert_eq!( - evaluate_formula_number(&"=PRODUCT({1+1,2,3-2},4, {5,6*1})"), + evaluate_formula_number("=PRODUCT({1+1,2,3-2},4, {5,6*1})"), 480.0, ); } @@ -648,55 +639,55 @@ fn it_evaluate_references_iterator() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=SUM({A.B,B.C})", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM({A.B,B.C})", Some(&data_function)), 3.0, ); } #[test] fn it_evaluate_iterator_operators() { - assert_eq!(evaluate_formula_string(&"={1,2,3}+{1,2,3}"), "{2,4,6}"); - assert_eq!(evaluate_formula_string(&"={3,2,1}-{1,2,3}"), "{2,0,-2}"); - assert_eq!(evaluate_formula_string(&"={1,2,3}*{1,2,3}"), "{1,4,9}"); - assert_eq!(evaluate_formula_string(&"={1,2,3}/{1,2,3}"), "{1,1,1}"); + assert_eq!(evaluate_formula_string("={1,2,3}+{1,2,3}"), "{2,4,6}"); + assert_eq!(evaluate_formula_string("={3,2,1}-{1,2,3}"), "{2,0,-2}"); + assert_eq!(evaluate_formula_string("={1,2,3}*{1,2,3}"), "{1,4,9}"); + assert_eq!(evaluate_formula_string("={1,2,3}/{1,2,3}"), "{1,1,1}"); assert_eq!( - evaluate_formula_string(&"={1,2,3}/{0,0,0}"), + evaluate_formula_string("={1,2,3}/{0,0,0}"), "{#DIV/0!,#DIV/0!,#DIV/0!}" ); - assert_eq!(evaluate_formula_string(&"=-{1,2,3}"), "{-1,-2,-3}"); - assert_eq!(evaluate_formula_string(&"=-({1,2,3})"), "{-1,-2,-3}"); + assert_eq!(evaluate_formula_string("=-{1,2,3}"), "{-1,-2,-3}"); + assert_eq!(evaluate_formula_string("=-({1,2,3})"), "{-1,-2,-3}"); } #[test] fn it_evaluate_iterator_in_logic_functions() { - assert_eq!(evaluate_formula_string(&"=AND(0,{0,0,0},0)"), "FALSE"); - assert_eq!(evaluate_formula_string(&"=AND(0,{0,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string(&"=AND({0,0,0},0)"), "FALSE"); - assert_eq!(evaluate_formula_string(&"=OR(1,{1,0,0},0,1)"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=OR(0,{0,1,0})"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=OR({0,0,0},1)"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=AND({0,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string(&"=AND({1,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string(&"=AND({1,1,1})"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=OR({0,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string(&"=OR({1,0,0})"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=OR({0,1,1})"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=OR({1,0,1})"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=OR({1,1,1})"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=XOR({1,0,1})"), "FALSE"); - assert_eq!(evaluate_formula_string(&"=XOR({0,1,0})"), "TRUE"); - assert_eq!(evaluate_formula_string(&"=XOR({0,0,0})"), "FALSE"); + assert_eq!(evaluate_formula_string("=AND(0,{0,0,0},0)"), "FALSE"); + assert_eq!(evaluate_formula_string("=AND(0,{0,0,0})"), "FALSE"); + assert_eq!(evaluate_formula_string("=AND({0,0,0},0)"), "FALSE"); + assert_eq!(evaluate_formula_string("=OR(1,{1,0,0},0,1)"), "TRUE"); + assert_eq!(evaluate_formula_string("=OR(0,{0,1,0})"), "TRUE"); + assert_eq!(evaluate_formula_string("=OR({0,0,0},1)"), "TRUE"); + assert_eq!(evaluate_formula_string("=AND({0,0,0})"), "FALSE"); + assert_eq!(evaluate_formula_string("=AND({1,0,0})"), "FALSE"); + assert_eq!(evaluate_formula_string("=AND({1,1,1})"), "TRUE"); + assert_eq!(evaluate_formula_string("=OR({0,0,0})"), "FALSE"); + assert_eq!(evaluate_formula_string("=OR({1,0,0})"), "TRUE"); + assert_eq!(evaluate_formula_string("=OR({0,1,1})"), "TRUE"); + assert_eq!(evaluate_formula_string("=OR({1,0,1})"), "TRUE"); + assert_eq!(evaluate_formula_string("=OR({1,1,1})"), "TRUE"); + assert_eq!(evaluate_formula_string("=XOR({1,0,1})"), "FALSE"); + assert_eq!(evaluate_formula_string("=XOR({0,1,0})"), "TRUE"); + assert_eq!(evaluate_formula_string("=XOR({0,0,0})"), "FALSE"); } #[test] fn it_evaluate_iterator_with_diffrent_number_of_entries() { - assert_eq!(evaluate_formula_string(&"={0,0}+{1,2,3}"), "{1,2,#ARG!}"); - assert_eq!(evaluate_formula_string(&"={0,0}*{1,2,3}"), "{0,0,#ARG!}"); + assert_eq!(evaluate_formula_string("={0,0}+{1,2,3}"), "{1,2,#ARG!}"); + assert_eq!(evaluate_formula_string("={0,0}*{1,2,3}"), "{0,0,#ARG!}"); assert_eq!( - evaluate_formula_string(&"={1,2,3}/{0,0}"), + evaluate_formula_string("={1,2,3}/{0,0}"), "{#DIV/0!,#DIV/0!,#ARG!}" ); - assert_eq!(evaluate_formula_string(&"={0,0}+{1,\"Hi\"}"), "{1,#CAST!}"); + assert_eq!(evaluate_formula_string("={0,0}+{1,\"Hi\"}"), "{1,#CAST!}"); } #[test] @@ -709,19 +700,19 @@ fn it_evaluate_date() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=DAYS(end, start)", Some(&data_function)), + evaluate_formula_number_with_reference("=DAYS(end, start)", Some(&data_function)), 182.00 ); assert_eq!( - evaluate_formula_number_with_reference(&"=days(end, start)", Some(&data_function)), + evaluate_formula_number_with_reference("=days(end, start)", Some(&data_function)), 182.00 ); assert_eq!( - evaluate_formula_date_with_reference(&"=start + 1", Some(&data_function)), + evaluate_formula_date_with_reference("=start + 1", Some(&data_function)), (start + Duration::days(1)).to_string() ); assert_eq!( - evaluate_formula_date_with_reference(&"=end-3", Some(&data_function)), + evaluate_formula_date_with_reference("=end-3", Some(&data_function)), (end - Duration::days(3)).to_string() ); Ok(()) @@ -739,31 +730,28 @@ fn it_evaluate_custom_functions_() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_custom_function(&"=Increase(1)", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=Increase(1)", Some(&custom_functions)), 2.0 ); assert_eq!( - evaluate_formula_number_with_custom_function(&"=SimpleSum(1,2)", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=SimpleSum(1,2)", Some(&custom_functions)), 3.0 ); assert_eq!( - evaluate_formula_number_with_custom_function(&"=CustomSum(1,2,3)", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=CustomSum(1,2,3)", Some(&custom_functions)), 6.0 ); assert_eq!( - evaluate_formula_number_with_custom_function(&"=EqualFive()+1", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=EqualFive()+1", Some(&custom_functions)), 6.0 ); assert_eq!( - evaluate_formula_string_with_custom_function( - &"=\"P\"&CountText()", - Some(&custom_functions) - ), + evaluate_formula_string_with_custom_function("=\"P\"&CountText()", Some(&custom_functions)), "P10" ); assert_eq!( evaluate_formula_string_with_custom_function( - &"=\"P\"&CountNumber()", + "=\"P\"&CountNumber()", Some(&custom_functions) ), "P20" @@ -772,17 +760,14 @@ fn it_evaluate_custom_functions_() { #[test] fn it_evaluate_left_and_right_functions() { - assert_eq!(evaluate_formula_string(&"=RIGHT(\"apple\", 3)"), "ple",); - assert_eq!(evaluate_formula_string(&"=RIGHT(\"apple\")"), "e",); + assert_eq!(evaluate_formula_string("=RIGHT(\"apple\", 3)"), "ple",); + assert_eq!(evaluate_formula_string("=RIGHT(\"apple\")"), "e",); - assert_eq!( - evaluate_formula_string(&"=\"P\"&RIGHT(\"000\"&1,3)"), - "P001", - ); - assert_eq!(evaluate_formula_string(&"=LEFT(\"apple\", 3)"), "app",); - assert_eq!(evaluate_formula_string(&"=LEFT(\"apple\")"), "a",); + assert_eq!(evaluate_formula_string("=\"P\"&RIGHT(\"000\"&1,3)"), "P001",); + assert_eq!(evaluate_formula_string("=LEFT(\"apple\", 3)"), "app",); + assert_eq!(evaluate_formula_string("=LEFT(\"apple\")"), "a",); - assert_eq!(evaluate_formula_string(&"=\"P\"&LEFT(\"000\"&1,3)"), "P000",); + assert_eq!(evaluate_formula_string("=\"P\"&LEFT(\"000\"&1,3)"), "P000",); } #[test] @@ -793,39 +778,39 @@ fn it_evaluates_blanks() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=SUM(A,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM(A,B)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=A+B", Some(&data_function)), + evaluate_formula_number_with_reference("=A+B", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference_no_conversion(&"=SUM(A,C)", Some(&data_function)), + evaluate_formula_number_with_reference_no_conversion("=SUM(A,C)", Some(&data_function)), types::Value::Error(types::Error::Value) ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE(A,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE(A,B)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=SUM(A,B,2,3,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM(A,B,2,3,B)", Some(&data_function)), 6.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE(A,B,2,3,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE(A,B,2,3,B)", Some(&data_function)), 2.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(A,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(A,B)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(1,B,B,B,A,2)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(1,B,B,B,A,2)", Some(&data_function)), 2.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=1+B+B+B", Some(&data_function)), + evaluate_formula_number_with_reference("=1+B+B+B", Some(&data_function)), 1.0 ); } @@ -837,23 +822,23 @@ fn it_evaluates_blanks_only() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=SUM(B)", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM(B)", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_string_with_reference(&"=AVERAGE(B)", Some(&data_function)), + evaluate_formula_string_with_reference("=AVERAGE(B)", Some(&data_function)), "#DIV/0!" ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(B)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(B)", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B", Some(&data_function)), + evaluate_formula_number_with_reference("=B", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=-B", Some(&data_function)), + evaluate_formula_number_with_reference("=-B", Some(&data_function)), 0.0 ); } @@ -866,43 +851,43 @@ fn it_evaluates_blanks_when_blank_in_first_position() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=SUM(B,A)", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM(B,A)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B+A", Some(&data_function)), + evaluate_formula_number_with_reference("=B+A", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE(B,A)", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE(B,A)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=SUM(B,2,3,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM(B,2,3,B)", Some(&data_function)), 5.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE(B,2,3,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE(B,2,3,B)", Some(&data_function)), 2.5 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(B,A)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(B,A)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(B,A,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(B,A,B)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(B,B,B)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(B,B,B)", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(B,B,B,A,2)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(B,B,B,A,2)", Some(&data_function)), 2.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B+B+B", Some(&data_function)), + evaluate_formula_number_with_reference("=B+B+B", Some(&data_function)), 0.0 ); } @@ -914,11 +899,11 @@ fn it_evaluates_blanks_in_abs_function() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=ABS(B)", Some(&data_function)), + evaluate_formula_number_with_reference("=ABS(B)", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=ABS(-B)", Some(&data_function)), + evaluate_formula_number_with_reference("=ABS(-B)", Some(&data_function)), 0.0 ); } @@ -934,15 +919,15 @@ fn it_evaluates_blanks_in_days_function() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=DAYS(B, B)", Some(&data_function)), + evaluate_formula_number_with_reference("=DAYS(B, B)", Some(&data_function)), 0.00 ); assert_eq!( - evaluate_formula_number_with_reference(&"=DAYS(start, B)", Some(&data_function)), + evaluate_formula_number_with_reference("=DAYS(start, B)", Some(&data_function)), 43495.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=DAYS(B, start)", Some(&data_function)), + evaluate_formula_number_with_reference("=DAYS(B, start)", Some(&data_function)), -43495.0 ); } @@ -954,43 +939,43 @@ fn it_evaluates_blanks_with_operators() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=1-B", Some(&data_function)), + evaluate_formula_number_with_reference("=1-B", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B-1", Some(&data_function)), + evaluate_formula_number_with_reference("=B-1", Some(&data_function)), -1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=1+B", Some(&data_function)), + evaluate_formula_number_with_reference("=1+B", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B+1", Some(&data_function)), + evaluate_formula_number_with_reference("=B+1", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_string_with_reference(&"=1/B", Some(&data_function)), + evaluate_formula_string_with_reference("=1/B", Some(&data_function)), "#DIV/0!" ); assert_eq!( - evaluate_formula_number_with_reference(&"=B/1", Some(&data_function)), + evaluate_formula_number_with_reference("=B/1", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=1*B", Some(&data_function)), + evaluate_formula_number_with_reference("=1*B", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B*1", Some(&data_function)), + evaluate_formula_number_with_reference("=B*1", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=1^B", Some(&data_function)), + evaluate_formula_number_with_reference("=1^B", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B^1", Some(&data_function)), + evaluate_formula_number_with_reference("=B^1", Some(&data_function)), 0.0 ); } @@ -1003,43 +988,43 @@ fn it_evaluates_blanks_with_operators_and_reference() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=A-B", Some(&data_function)), + evaluate_formula_number_with_reference("=A-B", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B-A", Some(&data_function)), + evaluate_formula_number_with_reference("=B-A", Some(&data_function)), -1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=A+B", Some(&data_function)), + evaluate_formula_number_with_reference("=A+B", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B+A", Some(&data_function)), + evaluate_formula_number_with_reference("=B+A", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_string_with_reference(&"=A/B", Some(&data_function)), + evaluate_formula_string_with_reference("=A/B", Some(&data_function)), "#DIV/0!" ); assert_eq!( - evaluate_formula_number_with_reference(&"=B/A", Some(&data_function)), + evaluate_formula_number_with_reference("=B/A", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=A*B", Some(&data_function)), + evaluate_formula_number_with_reference("=A*B", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B*A", Some(&data_function)), + evaluate_formula_number_with_reference("=B*A", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=A^B", Some(&data_function)), + evaluate_formula_number_with_reference("=A^B", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=B^A", Some(&data_function)), + evaluate_formula_number_with_reference("=B^A", Some(&data_function)), 0.0 ); } @@ -1053,83 +1038,79 @@ fn it_evaluates_blanks_in_boolean_operations() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND(T,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND(T,B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR(T,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR(T,B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=NOT(B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=NOT(B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND(F,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND(F,B)", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR(F,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR(F,B)", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR(T,B,F,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR(T,B,F,B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR(F,B,T,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR(F,B,T,B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND(T,B,F,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND(T,B,F,B)", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND(F,B,T,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND(F,B,T,B)", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR(T,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR(T,B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR(F,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR(F,B)", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR(T,B,F,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR(T,B,F,B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR(F,B,T,B)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR(F,B,T,B)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR(T,B,F,B,T)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR(T,B,F,B,T)", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR(F,B,T,B,F)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR(F,B,T,B,F)", Some(&data_function)), "TRUE" ); } #[test] fn evaluate_invalid_reference_in_functions() { - let data_function = |s: String| match s.as_str() { - _ => types::Value::Error(types::Error::Value), - }; + let data_function = |_s: String| types::Value::Error(types::Error::Value); // test with a non existing reference - let formula = parse_formula::parse_string_to_formula( - "=AND(T, NO_REFERENCE)".as_ref(), - None::, - ); + let formula = + parse_formula::parse_string_to_formula("=AND(T, NO_REFERENCE)", None::); assert_eq!( calculate::calculate_formula(formula, Some(&data_function)), types::Value::Error(types::Error::Value) ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND(T, NO_REFERENCE)", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND(T, NO_REFERENCE)", Some(&data_function)), "#VALUE!" ); } @@ -1141,51 +1122,51 @@ fn it_evaluates_blanks_in_comparison_operators() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_string_with_reference(&"=B=B", Some(&data_function)), + evaluate_formula_string_with_reference("=B=B", Some(&data_function)), "TRUE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=1=B", Some(&data_function)), + evaluate_formula_string_with_reference("=1=B", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=B=1", Some(&data_function)), + evaluate_formula_string_with_reference("=B=1", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=1>B", Some(&data_function)), + evaluate_formula_string_with_reference("=1>B", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=B>1", Some(&data_function)), + evaluate_formula_string_with_reference("=B>1", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=0=B", Some(&data_function)), + evaluate_formula_string_with_reference("=0=B", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=B=0", Some(&data_function)), + evaluate_formula_string_with_reference("=B=0", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=B=\"\"", Some(&data_function)), + evaluate_formula_boolean_with_reference("=B=\"\"", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=\"\"=B", Some(&data_function)), + evaluate_formula_boolean_with_reference("=\"\"=B", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=\"something\"=B", Some(&data_function)), + evaluate_formula_boolean_with_reference("=\"something\"=B", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_string_with_reference(&"=B='test'", Some(&data_function)), + evaluate_formula_string_with_reference("=B='test'", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"='test'=B", Some(&data_function)), + evaluate_formula_string_with_reference("='test'=B", Some(&data_function)), "FALSE", ); } @@ -1198,19 +1179,19 @@ fn it_evaluates_blanks_in_comparison_operators_with_references() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_string_with_reference(&"=A>=B", Some(&data_function)), + evaluate_formula_string_with_reference("=A>=B", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=B>=A", Some(&data_function)), + evaluate_formula_string_with_reference("=B>=A", Some(&data_function)), "FALSE", ); assert_eq!( - evaluate_formula_string_with_reference(&"=A types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_string_with_reference(&"=\"Hello\"&B", Some(&data_function)), + evaluate_formula_string_with_reference("=\"Hello\"&B", Some(&data_function)), "Hello", ); assert_eq!( - evaluate_formula_string_with_reference(&"=B&\"Hello\"", Some(&data_function)), + evaluate_formula_string_with_reference("=B&\"Hello\"", Some(&data_function)), "Hello", ); assert_eq!( - evaluate_formula_string_with_reference(&"=B&B", Some(&data_function)), + evaluate_formula_string_with_reference("=B&B", Some(&data_function)), "", ); assert_eq!( - evaluate_formula_string_with_reference(&"=RIGHT(B)", Some(&data_function)), + evaluate_formula_string_with_reference("=RIGHT(B)", Some(&data_function)), "", ); assert_eq!( - evaluate_formula_string_with_reference(&"=LEFT(B)", Some(&data_function)), + evaluate_formula_string_with_reference("=LEFT(B)", Some(&data_function)), "", ); } @@ -1251,33 +1232,33 @@ fn it_evaluates_blank_constructors() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_custom_function(&"=SUM(BLANK())", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=SUM(BLANK())", Some(&custom_functions)), 0.0 ); assert_eq!( - evaluate_formula_number_with_custom_function(&"=SUM(BLANK(), 1)", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=SUM(BLANK(), 1)", Some(&custom_functions)), 1.0 ); assert_eq!( evaluate_formula_number_with_custom_function( - &"=PRODUCT(BLANK(), 1)", + "=PRODUCT(BLANK(), 1)", Some(&custom_functions) ), 1.0 ); assert_eq!( evaluate_formula_number_with_custom_function( - &"=AVERAGE(BLANK(), 1)", + "=AVERAGE(BLANK(), 1)", Some(&custom_functions) ), 1.0 ); assert_eq!( - evaluate_formula_number_with_custom_function(&"=BLANK()+1", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=BLANK()+1", Some(&custom_functions)), 1.0 ); assert_eq!( - evaluate_formula_number_with_custom_function(&"=BLANK()*1", Some(&custom_functions)), + evaluate_formula_number_with_custom_function("=BLANK()*1", Some(&custom_functions)), 0.0 ); } @@ -1295,51 +1276,51 @@ fn it_evaluates_blank_in_iterators() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE({A, B, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE({A, B, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE({A, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE({A, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE({B, A, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE({B, A, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=AVERAGE(Array)", Some(&data_function)), + evaluate_formula_number_with_reference("=AVERAGE(Array)", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=SUM({A, B, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM({A, B, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=SUM({A, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM({A, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=SUM({B, A, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM({B, A, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=SUM(Array)", Some(&data_function)), + evaluate_formula_number_with_reference("=SUM(Array)", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT({A, B, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT({A, B, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT({A, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT({A, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT({B, A, B})", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT({B, A, B})", Some(&data_function)), 100.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=PRODUCT(Array)", Some(&data_function)), + evaluate_formula_number_with_reference("=PRODUCT(Array)", Some(&data_function)), 100.0 ); } @@ -1353,64 +1334,64 @@ fn it_evaluates_blank_with_iterators_in_boolean_operations() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND({T,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND({T,B})", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR({T,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR({T,B})", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND({F,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND({F,B})", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR({F,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR({F,B})", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR({T,B,F,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR({T,B,F,B})", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=OR({F,B,T,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=OR({F,B,T,B})", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND({T,B,F,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND({T,B,F,B})", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=AND({F,B,T,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=AND({F,B,T,B})", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR({T,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR({T,B})", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR({F,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR({F,B})", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR({T,B,F,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR({T,B,F,B})", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR({F,B,T,B})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR({F,B,T,B})", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR({T,B,F,B,T})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR({T,B,F,B,T})", Some(&data_function)), "FALSE" ); assert_eq!( - evaluate_formula_boolean_with_reference(&"=XOR({F,B,T,B,F})", Some(&data_function)), + evaluate_formula_boolean_with_reference("=XOR({F,B,T,B,F})", Some(&data_function)), "TRUE" ); assert_eq!( evaluate_formula_boolean_with_reference( - &"=XOR({F,B,T,B,F,{F,B,T,B,F}})", + "=XOR({F,B,T,B,F,{F,B,T,B,F}})", Some(&data_function) ), "FALSE" @@ -1419,64 +1400,64 @@ fn it_evaluates_blank_with_iterators_in_boolean_operations() { #[test] fn it_evaluates_formulas_with_3_params() { - assert_eq!(evaluate_formula_number(&"=IF()"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF( )"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF( ,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(, )"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF( , )"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(,,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(, ,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(,,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(,, )"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(1,,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(1,2,)"), 2.0); - assert_eq!(evaluate_formula_number(&"=IF(1,2)"), 2.0); - assert_eq!(evaluate_formula_number(&"=IF(0,2,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(0,2)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(0,,3)"), 3.0); - assert_eq!(evaluate_formula_number(&"=IF(1,,3)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(0,2,3)"), 3.0); - assert_eq!(evaluate_formula_number(&"=IF(,2,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(,2)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(,2,3)"), 3.0); - assert_eq!(evaluate_formula_number(&"=IF(,,3)"), 3.0); + assert_eq!(evaluate_formula_number("=IF()"), 0.0); + assert_eq!(evaluate_formula_number("=IF( )"), 0.0); + assert_eq!(evaluate_formula_number("=IF(,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF( ,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(, )"), 0.0); + assert_eq!(evaluate_formula_number("=IF( , )"), 0.0); + assert_eq!(evaluate_formula_number("=IF(,,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(, ,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(,,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(,, )"), 0.0); + assert_eq!(evaluate_formula_number("=IF(1,,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(1,2,)"), 2.0); + assert_eq!(evaluate_formula_number("=IF(1,2)"), 2.0); + assert_eq!(evaluate_formula_number("=IF(0,2,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(0,2)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(0,,3)"), 3.0); + assert_eq!(evaluate_formula_number("=IF(1,,3)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(0,2,3)"), 3.0); + assert_eq!(evaluate_formula_number("=IF(,2,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(,2)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(,2,3)"), 3.0); + assert_eq!(evaluate_formula_number("=IF(,,3)"), 3.0); } #[test] fn it_evaluates_if_formulas() -> Result<(), ParseError> { - assert_eq!(evaluate_formula_number(&"=IF(TRUE,1,0)"), 1.0); - assert_eq!(evaluate_formula_number(&"=IF(FALSE,1,0)"), 0.0); - assert_eq!(evaluate_formula_string(&"=IF(TRUE,\"a\",0)"), "a"); - assert_eq!(evaluate_formula_number(&"=IF(FALSE,\"a\",0)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(1=1,1,0)"), 1.0); - assert_eq!(evaluate_formula_number(&"=IF(1=2,1,0)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(AND(TRUE,FALSE),1,0)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(TRUE,IF(FALSE,1,2),0)"), 2.0); - assert_eq!(evaluate_formula_number(&"=IF(2,1,0)"), 1.0); - assert_eq!(evaluate_formula_number(&"=IF(-1,1,0)"), 1.0); - assert_eq!(evaluate_formula_number(&"=IF(0,1,0)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(TRUE,1+2+3,0)"), 6.0); - assert_eq!(evaluate_formula_number(&"=IF(FALSE,1)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(FALSE,1,)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(TRUE,,1)"), 0.0); - assert_eq!(evaluate_formula_number(&"=IF(TRUE, ,1)"), 0.0); - assert_eq!(evaluate_formula_string(&"=IF(TRUE,TRUE,FALSE)"), "TRUE"); - assert_eq!( - evaluate_formula_string(&"=IF( TRUE , TRUE , FALSE )"), + assert_eq!(evaluate_formula_number("=IF(TRUE,1,0)"), 1.0); + assert_eq!(evaluate_formula_number("=IF(FALSE,1,0)"), 0.0); + assert_eq!(evaluate_formula_string("=IF(TRUE,\"a\",0)"), "a"); + assert_eq!(evaluate_formula_number("=IF(FALSE,\"a\",0)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(1=1,1,0)"), 1.0); + assert_eq!(evaluate_formula_number("=IF(1=2,1,0)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(AND(TRUE,FALSE),1,0)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(TRUE,IF(FALSE,1,2),0)"), 2.0); + assert_eq!(evaluate_formula_number("=IF(2,1,0)"), 1.0); + assert_eq!(evaluate_formula_number("=IF(-1,1,0)"), 1.0); + assert_eq!(evaluate_formula_number("=IF(0,1,0)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(TRUE,1+2+3,0)"), 6.0); + assert_eq!(evaluate_formula_number("=IF(FALSE,1)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(FALSE,1,)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(TRUE,,1)"), 0.0); + assert_eq!(evaluate_formula_number("=IF(TRUE, ,1)"), 0.0); + assert_eq!(evaluate_formula_string("=IF(TRUE,TRUE,FALSE)"), "TRUE"); + assert_eq!( + evaluate_formula_string("=IF( TRUE , TRUE , FALSE )"), "TRUE" ); - assert_eq!(evaluate_formula_string(&"=IF(TRUE , TRUE, FALSE )"), "TRUE"); - assert_eq!(evaluate_formula_number(&"=IF(1,IF(FALSE,1,2),0)"), 2.0); + assert_eq!(evaluate_formula_string("=IF(TRUE , TRUE, FALSE )"), "TRUE"); + assert_eq!(evaluate_formula_number("=IF(1,IF(FALSE,1,2),0)"), 2.0); assert_eq!( - evaluate_formula_number(&"=IF(1=0,IF(FALSE,1,2),IF(TRUE,1,2))"), + evaluate_formula_number("=IF(1=0,IF(FALSE,1,2),IF(TRUE,1,2))"), 1.0 ); assert_eq!( - evaluate_formula_string(&"=IF(1/0,IF(FALSE,1,2),0)"), + evaluate_formula_string("=IF(1/0,IF(FALSE,1,2),0)"), "#DIV/0!", ); - assert_eq!(evaluate_formula_string(&"=IF(\"text\",1,0)"), "#VALUE!",); + assert_eq!(evaluate_formula_string("=IF(\"text\",1,0)"), "#VALUE!",); let date1: DateTime = DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z")?; let date2: DateTime = DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z")?; @@ -1486,27 +1467,27 @@ fn it_evaluates_if_formulas() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_number_with_reference(&"=IF(date1=date2,1,0)", Some(&data_function)), + evaluate_formula_number_with_reference("=IF(date1=date2,1,0)", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=IF(date1<>date2,1,0)", Some(&data_function)), + evaluate_formula_number_with_reference("=IF(date1<>date2,1,0)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=IF(date1date2,1,0)", Some(&data_function)), + evaluate_formula_number_with_reference("=IF(date1>date2,1,0)", Some(&data_function)), 0.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=IF(date1<=date2,1,0)", Some(&data_function)), + evaluate_formula_number_with_reference("=IF(date1<=date2,1,0)", Some(&data_function)), 1.0 ); assert_eq!( - evaluate_formula_number_with_reference(&"=IF(date1>=date2,1,0)", Some(&data_function)), + evaluate_formula_number_with_reference("=IF(date1>=date2,1,0)", Some(&data_function)), 0.0 ); Ok(()) @@ -1521,7 +1502,7 @@ fn it_evaluates_if_formulas_with_text() { }; assert_eq!( evaluate_formula_string_with_reference( - &"=IF(ReferenceKey=\"10\",\"\",ReferenceKey&\" - \")&ReferenceName", + "=IF(ReferenceKey=\"10\",\"\",ReferenceKey&\" - \")&ReferenceName", Some(&data_function) ), "100 - Test" @@ -1536,11 +1517,11 @@ fn it_evaluates_isblank_function() { _ => types::Value::Error(types::Error::Value), }; assert_eq!( - evaluate_formula_string_with_reference(&"=ISBLANK(ReferenceKey)", Some(&data_function)), + evaluate_formula_string_with_reference("=ISBLANK(ReferenceKey)", Some(&data_function)), "TRUE" ); assert_eq!( - evaluate_formula_string_with_reference(&"=ISBLANK(ReferenceName)", Some(&data_function)), + evaluate_formula_string_with_reference("=ISBLANK(ReferenceName)", Some(&data_function)), "FALSE" ); } @@ -1554,7 +1535,7 @@ fn test_inner_function_with_whitespace() { }; assert_eq!( evaluate_formula_boolean_with_reference( - &"=AND(RIGHT(primary_carrier, 1)=\"*\", 1 > 0)", + "=AND(RIGHT(primary_carrier, 1)=\"*\", 1 > 0)", Some(&data_function) ), "TRUE" @@ -1569,7 +1550,7 @@ fn test_zero_padding() { }; assert_eq!( evaluate_formula_boolean_with_reference( - &"=RIGHT(\"00000000\" & num, 8)", + "=RIGHT(\"00000000\" & num, 8)", Some(&data_function) ), "00000091" From 9a096725afcd57d92b56c1e257ec801c6c30edc8 Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Mon, 17 Feb 2025 17:18:33 +0200 Subject: [PATCH 09/23] Bump version to v0.1.19 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3a292a..b04d13a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,7 +216,7 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "xlformula_engine" -version = "0.1.18" +version = "0.1.19" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 01eb731..6dc7e60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.1.18" +version = "0.1.19" [dependencies] assert_approx_eq = "1.1.0" From fc0f3a816781d5f4b2debf2209694df701fb132e Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Thu, 19 Jun 2025 11:55:18 +0300 Subject: [PATCH 10/23] Fix README.md warnings --- README.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 844cc40..fca4b09 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # XLFormula Engine + XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. It currently works with f32 types. ## Features + It supports: * Any numbers, negative and positive, as float or integer @@ -14,17 +16,20 @@ It supports: * Operations on lists of values (one dimensional range) * Add or subtract dates and excel funtion DAYS() * Custom functions with number arguments -* Handle blank/null values in calculation +* Handle blank/null values in calculation * Handle empty/missing parameters of function calls as blank values ## Installation Add the corresponding entry to your Cargo.toml dependency list: + ```toml [dependencies] xlformula_engine = "0.1.18" ``` + and add this to your crate root: + ```rust extern crate xlformula_engine; ``` @@ -32,6 +37,7 @@ extern crate xlformula_engine; ## Examples Here are simple examples of parsing an Excel formula string and evaluating to a result: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -53,9 +59,11 @@ let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); } ``` + The last string is evaluated to #DIV/0!. Concatenating strings: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -73,9 +81,11 @@ let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); } ``` + Concatenating number and string results in a #CAST! error. Constants ( i.e. a string without '=' ): + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -95,6 +105,7 @@ println!("Result is {}", calculate::result_to_string(result)); ``` Excel functions: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -134,6 +145,7 @@ println!("Result is {}", calculate::result_to_string(result)); ``` Logical expressions: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -157,6 +169,7 @@ println!("Result is {}", calculate::result_to_string(result)); ``` References: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -178,6 +191,7 @@ println!("Result is {}", calculate::result_to_string(result)); ``` List: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -201,6 +215,7 @@ println!("Result is {}", calculate::result_to_string(result)); ``` Date: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -237,6 +252,7 @@ Ok(()) ``` Custom Function: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -270,6 +286,7 @@ println!("Result is {}", calculate::result_to_string(result)); ``` Handle blank in calculation: + ```rust extern crate xlformula_engine; use xlformula_engine::calculate; @@ -314,9 +331,10 @@ fn main() -> { } ``` - ## License -Licensed under MIT License (see the [LICENSE](https://github.com/jiradaherbst/XLFormula-Engine/blob/master/LICENSE) file for the full text). + +Licensed under MIT License (see the [LICENSE](https://github.com/jiradaherbst/XLFormula-Engine/blob/master/LICENSE) file for the full text). ## Contact -Please feel free to contact us at jirada.herbst@data2impact.com. \ No newline at end of file + +Please feel free to contact us at jirada.herbst@data2impact.com. From 36471d9d26ac3c86b27053d448cb8d0bbf9dd1ba Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Thu, 19 Jun 2025 11:56:25 +0300 Subject: [PATCH 11/23] Fix clippy warning --- src/calculate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculate.rs b/src/calculate.rs index 7990e9a..7d1425f 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -1098,7 +1098,7 @@ fn calculate_operation( types::Operator::Divide => { let (value2, value1) = get_values(exp, f); match value2 { - types::Value::Number(x) if x == 0.0 => types::Value::Error(types::Error::Div0), + types::Value::Number(0.0) => types::Value::Error(types::Error::Div0), _ => calculate_numeric_operator(value1, value2, calculate_divide_operator), } } From 612e57eae876e75706a80176274407e5c5cbd2da Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Sun, 22 Jun 2025 15:13:31 +0300 Subject: [PATCH 12/23] Add f64 feature --- Cargo.lock | 19 +++--------- Cargo.toml | 13 ++++++-- README.md | 18 ++++++----- src/calculate.rs | 71 +++++++++++++++++++++++--------------------- src/lib.rs | 12 +++++--- src/main.rs | 20 ++++++------- src/parse_formula.rs | 36 +++++++++++----------- src/types.rs | 11 +++++-- tests/test.rs | 69 ++++++++++++++++++++++++------------------ 9 files changed, 144 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b04d13a..c119e36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "assert_approx_eq" @@ -49,11 +49,10 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ - "num-integer", "num-traits", ] @@ -87,16 +86,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.14" @@ -216,7 +205,7 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "xlformula_engine" -version = "0.1.19" +version = "0.1.20" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 6dc7e60..cb650a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,17 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.1.19" +version = "0.1.20" + +[features] +default = ["f32"] +f32 = [] +f64 = [] [dependencies] -assert_approx_eq = "1.1.0" -chrono = {version = "0.4.19", default-features = false} +chrono = { version = "0.4.41", default-features = false } pest = "2.1.3" pest_derive = "2.1.0" + +[dev-dependencies] +assert_approx_eq = "1.1.0" diff --git a/README.md b/README.md index fca4b09..4cfd579 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # XLFormula Engine -XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. It currently works with f32 types. +XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. + +It works with f32 by default (the `f32` feature) or with `f64` by enabling the `f64` feature and disabling default features. ## Features @@ -25,13 +27,14 @@ Add the corresponding entry to your Cargo.toml dependency list: ```toml [dependencies] -xlformula_engine = "0.1.18" +xlformula_engine = "0.1.19" ``` -and add this to your crate root: +Or use the following for using `f64`: -```rust -extern crate xlformula_engine; +```toml +[dependencies] +xlformula_engine = { version = "0.1.19", default-features = false, features = ["f64"] } ``` ## Examples @@ -258,10 +261,11 @@ extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::types; +use xlformula_engine::types::XlNum; use xlformula_engine::NoReference; fn main() { -let custom_functions = |s: String, params: Vec| match s.as_str() { +let custom_functions = |s: String, params: Vec| match s.as_str() { "Increase" => types::Value::Number(params[0] + 1.0), "SimpleSum" => types::Value::Number(params[0] + params[1]), "EqualFive" => types::Value::Number(5.0), @@ -303,7 +307,7 @@ fn main() -> { _ => types::Value::Error(types::Error::Value), }; - let custom_functions = |s: String, params: Vec| match s.as_str() { + let custom_functions = |s: String, params: Vec| match s.as_str() { "BLANK" => types::Value::Blank, _ => types::Value::Error(types::Error::Value), }; diff --git a/src/calculate.rs b/src/calculate.rs index 7d1425f..3ebc697 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -1,18 +1,21 @@ -use crate::parse_formula; -use crate::types; +use crate::{ + parse_formula, + types::{self, XlNum}, +}; use chrono::{DateTime, Duration, FixedOffset}; -type NoCustomFunction<'a> = &'a fn(String, Vec) -> types::Value; -fn calculate_divide_operator(num1: f32, num2: f32) -> f32 { +type NoCustomFunction<'a> = &'a fn(String, Vec) -> types::Value; + +fn calculate_divide_operator(num1: XlNum, num2: XlNum) -> XlNum { num1 / num2 } -fn is_float_int(num: f32) -> bool { - //((num as i32) as f32) == num - (((num as i32) as f32) - num).abs() == 0.0 +fn is_float_int(num: XlNum) -> bool { + //((num as i32) as XlNum) == num + (((num as i32) as XlNum) - num).abs() == 0.0 } -fn calculate_power_operator(num1: f32, num2: f32) -> f32 { +fn calculate_power_operator(num1: XlNum, num2: XlNum) -> XlNum { if is_float_int(num2) { num1.powi(num2 as i32) } else { @@ -59,13 +62,13 @@ fn calculate_string_operator( fn calcualte_numeric_operator_rhs_text( t: String, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { - match t.parse::() { + match t.parse::() { Ok(nl) => match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(nl, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -79,15 +82,15 @@ fn calcualte_numeric_operator_rhs_text( } fn calculate_numeric_operator_rhs_number( - l: f32, + l: XlNum, lhs: types::Value, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(l, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -108,15 +111,15 @@ fn calculate_numeric_operator_rhs_number( } fn calculate_numeric_operator_product_rhs_number( - l: f32, + l: XlNum, lhs: types::Value, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(l, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -142,7 +145,7 @@ fn calculate_numeric_operator_product_rhs_number( fn calculate_numeric_operator_rhs_iterator( mut lhs_vec: Vec, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { match rhs { types::Value::Number(_) => { @@ -190,7 +193,7 @@ fn subtract_days_from_date(d: DateTime, rhs: types::Value) -> types fn calculate_numeric_operator( lhs: types::Value, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { //println!("{:?}::{:?}", lhs, rhs); match lhs { @@ -207,7 +210,7 @@ fn calculate_numeric_operator( fn calculate_numeric_product_operator( lhs: types::Value, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { //println!("{:?}::{:?}", lhs, rhs); match lhs { @@ -223,15 +226,15 @@ fn calculate_numeric_product_operator( fn calculate_average_operator_rhs_number( element_count: &mut i32, - l: f32, + l: XlNum, lhs: types::Value, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(l, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -265,7 +268,7 @@ fn calculate_average_operator_rhs_iterator( element_count: &mut i32, mut lhs_vec: Vec, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { match rhs { types::Value::Number(_) => { @@ -287,7 +290,7 @@ fn calculate_average_operator( element_count: &mut i32, lhs: types::Value, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> f32, + f: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { match lhs { types::Value::Boolean(_) => lhs, @@ -310,7 +313,7 @@ fn calculate_average_operator( fn calculate_comparison_operator( lhs: types::Value, rhs: types::Value, - f: fn(num1: f32, num2: f32) -> bool, + f: fn(num1: XlNum, num2: XlNum) -> bool, ) -> types::Value { match lhs { types::Value::Text(l) => match rhs { @@ -891,7 +894,7 @@ fn calculate_collective_operator( mut collective_value: types::Value, mut exp: types::Expression, f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: f32, num2: f32) -> f32, + f_collective: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { while let Some(top) = exp.values.pop() { collective_value = @@ -904,7 +907,7 @@ fn calculate_collective_product_operator( mut collective_value: types::Value, mut exp: types::Expression, f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: f32, num2: f32) -> f32, + f_collective: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { while let Some(top) = exp.values.pop() { collective_value = calculate_numeric_product_operator( @@ -923,7 +926,7 @@ fn calculate_average( mut collective_value: types::Value, mut exp: types::Expression, f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: f32, num2: f32) -> f32, + f_collective: fn(num1: XlNum, num2: XlNum) -> XlNum, ) -> types::Value { let mut element_count = 0; while let Some(top) = exp.values.pop() { @@ -940,7 +943,7 @@ fn calculate_average( } else { calculate_numeric_operator( collective_value, - types::Value::Number(element_count as f32), + types::Value::Number(element_count as XlNum), calculate_divide_operator, ) } @@ -954,13 +957,13 @@ fn calculate_days(date_pair: (types::Value, types::Value)) -> types::Value { let (start, end) = date_pair; match (start, end) { (types::Value::Date(start), types::Value::Date(end)) => { - types::Value::Number((end - start).num_days() as f32) + types::Value::Number((end - start).num_days() as XlNum) } (types::Value::Blank, types::Value::Date(end)) => { - types::Value::Number((end - begin_of_date).num_days() as f32) + types::Value::Number((end - begin_of_date).num_days() as XlNum) } (types::Value::Date(start), types::Value::Blank) => { - types::Value::Number((begin_of_date - start).num_days() as f32) + types::Value::Number((begin_of_date - start).num_days() as XlNum) } (types::Value::Blank, types::Value::Blank) => types::Value::Number(0.0), _ => types::Value::Error(types::Error::Value), @@ -1225,7 +1228,7 @@ pub fn result_to_string(_value: types::Value) -> String { } } -fn show_number(number: f32) -> String { +fn show_number(number: XlNum) -> String { if number.is_infinite() { String::from("#DIV/0!") } else { diff --git a/src/lib.rs b/src/lib.rs index 56c0d06..2c246c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ //!# XLFormula Engine -//!XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. It currently works with f32 types. +//!XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. +//! +//!It works with f32 by default (the `f32` feature) or with `f64` by enabling the `f64` feature and disabling default features. //! //!## Features //!It supports: @@ -225,9 +227,10 @@ //!use xlformula_engine::calculate; //!use xlformula_engine::parse_formula; //!use xlformula_engine::types; +//!use xlformula_engine::types::XlNum; //!use xlformula_engine::NoReference; //! -//!let custom_functions = |s: String, params: Vec| match s.as_str() { +//!let custom_functions = |s: String, params: Vec| match s.as_str() { //!"Increase" => types::Value::Number(params[0] + 1.0), //!"SimpleSum" => types::Value::Number(params[0] + params[1]), //!"EqualFive" => types::Value::Number(5.0), @@ -256,6 +259,7 @@ //!use xlformula_engine::calculate; //!use xlformula_engine::parse_formula; //!use xlformula_engine::types; +//!use xlformula_engine::types::XlNum; //!use chrono::format::ParseError; //!use chrono::{DateTime, FixedOffset}; //!use xlformula_engine::NoReference; @@ -266,7 +270,7 @@ //!_ => types::Value::Error(types::Error::Value), //!}; //! -//!let custom_functions = |s: String, params: Vec| match s.as_str() { +//!let custom_functions = |s: String, params: Vec| match s.as_str() { //!"BLANK" => types::Value::Blank, //!_ => types::Value::Error(types::Error::Value), //!}; @@ -304,4 +308,4 @@ pub mod types; pub mod parse_formula; pub type NoReference<'a> = &'a fn(String) -> types::Value; -pub type NoCustomFunction<'a> = &'a fn(String, Vec) -> types::Value; +pub type NoCustomFunction<'a> = &'a fn(String, Vec) -> types::Value; diff --git a/src/main.rs b/src/main.rs index 422a6b7..9858d6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,9 @@ -extern crate xlformula_engine; -use chrono::format::ParseError; -use chrono::{DateTime, FixedOffset}; -use xlformula_engine::calculate; -use xlformula_engine::parse_formula; -use xlformula_engine::types; -use xlformula_engine::NoCustomFunction; -use xlformula_engine::NoReference; +use chrono::{format::ParseError, DateTime, FixedOffset}; +use xlformula_engine::{ + calculate, parse_formula, + types::{self, XlNum}, + NoCustomFunction, NoReference, +}; fn main() -> Result<(), ParseError> { let formula = parse_formula::parse_string_to_formula("=1+2", None::); @@ -135,7 +133,7 @@ fn main() -> Result<(), ParseError> { let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let custom_functions = |s: String, params: Vec| match s.as_str() { + let custom_functions = |s: String, params: Vec| match s.as_str() { "Increase" => types::Value::Number(params[0] + 1.0), "SimpleSum" => types::Value::Number(params[0] + params[1]), "EqualFive" => types::Value::Number(5.0), @@ -155,7 +153,7 @@ fn main() -> Result<(), ParseError> { let result = calculate::calculate_formula(formula, None::); println!("Result is {}", calculate::result_to_string(result)); - let custom_function = |s: String, _params: Vec| match s.as_str() { + let custom_function = |s: String, _params: Vec| match s.as_str() { "EqualFive" => types::Value::Number(5.0), _ => types::Value::Error(types::Error::Value), }; @@ -198,7 +196,7 @@ fn main() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; - let custom_functions = |s: String, params: Vec| match s.as_str() { + let custom_functions = |s: String, params: Vec| match s.as_str() { "Increase" => types::Value::Number(params[0] + 1.0), "BLANK" => types::Value::Blank, _ => types::Value::Error(types::Error::Value), diff --git a/src/parse_formula.rs b/src/parse_formula.rs index d419c28..33f479f 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -1,15 +1,13 @@ -extern crate pest; -use pest::Parser; +use crate::types::{self, XlNum}; +use pest::{ + prec_climber::{Assoc, Operator, PrecClimber}, + Parser, +}; + #[derive(Parser)] #[grammar = "grammar.pest"] pub struct GrammarParser; -use crate::types; - -use pest::prec_climber::Assoc; -use pest::prec_climber::Operator; -use pest::prec_climber::PrecClimber; - /// Use this function to catch a parse error. fn parse_string(s: &str) -> Option> { let parse_result = GrammarParser::parse(Rule::formula, s); @@ -40,7 +38,7 @@ fn parse_string_constant(parse_result: pest::iterators::Pair) -> types::Fo /// Parses a string and stores it in Formula Enum. pub fn parse_string_to_formula( s: &str, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { match parse_string(s) { Some(parse_result) => match parse_result.as_rule() { @@ -53,7 +51,7 @@ pub fn parse_string_to_formula( } fn build_formula_number(pair: pest::iterators::Pair) -> types::Formula { - let x = pair.as_str().parse::().unwrap(); + let x = pair.as_str().parse::().unwrap(); let value = types::Value::Number(x); types::Formula::Value(value) } @@ -81,7 +79,7 @@ fn build_formula_boolean(boolean_value: bool) -> types::Formula { fn build_formula_unary_operator( unary_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let op_type = match unary_operation { Rule::abs => types::Operator::Function(types::Function::Abs), @@ -103,7 +101,7 @@ fn build_formula_reference(pair: pest::iterators::Pair) -> types::Formula fn build_formula_iterator( pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let mut vec = Vec::new(); for term in pair.into_inner() { @@ -115,7 +113,7 @@ fn build_formula_iterator( fn build_formula_collective_operator( collective_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let mut vec = Vec::new(); for term in pair.into_inner() { @@ -157,7 +155,7 @@ fn rule_to_function_operator(collective_operation: Rule) -> types::Operator { fn build_formula_collective_operator_average( collective_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let mut vec = Vec::new(); for term in pair.into_inner() { @@ -182,7 +180,7 @@ fn build_formula_collective_operator_average( fn build_formula_collective_operator_and( collective_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let mut vec = Vec::new(); for term in pair.into_inner() { @@ -208,7 +206,7 @@ fn build_formula_collective_operator_and( fn build_formula_iff( pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let mut vec = Vec::new(); for term in pair.into_inner() { @@ -231,13 +229,13 @@ fn build_formula_iff( fn build_formula_custom_function( pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let mut vec = Vec::new(); for field in pair.clone().into_inner() { if field.as_rule() == Rule::expr { let x = field.into_inner().as_str(); - let y = x.parse::(); + let y = x.parse::(); if let Ok(y) = y { vec.push(y); } @@ -297,7 +295,7 @@ fn build_formula_binary_operator( /// Builds Formula Enum using a `pest-PrecClimber`. fn build_formula_with_climber( expression: pest::iterators::Pairs, - f: Option<&impl Fn(String, Vec) -> types::Value>, + f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { let climber = PrecClimber::new(vec![ Operator::new(Rule::concat, Assoc::Left), diff --git a/src/types.rs b/src/types.rs index ad9414b..a321fdf 100644 --- a/src/types.rs +++ b/src/types.rs @@ -54,13 +54,18 @@ pub enum Boolean { False, } +#[cfg(feature = "f32")] +pub type XlNum = f32; +#[cfg(feature = "f64")] +pub type XlNum = f64; + /// The result of an evaluation. #[derive(Debug, Clone, PartialEq)] pub enum Value { - Number(f32), + Number(XlNum), Text(String), Boolean(Boolean), - Iterator(Vec), + Iterator(Vec), Error(Error), Date(DateTime), Blank, @@ -72,7 +77,7 @@ pub enum Formula { Operation(Expression), Value(Value), Reference(String), - Iterator(Vec), + Iterator(Vec), } /// Struct that holds a parsed string. Formula enum and Expression Struct are defined recursively. diff --git a/tests/test.rs b/tests/test.rs index d62ce6e..ac2c946 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,19 +1,16 @@ -extern crate xlformula_engine; -use xlformula_engine::calculate; -use xlformula_engine::parse_formula; -use xlformula_engine::types; -use xlformula_engine::NoCustomFunction; -use xlformula_engine::NoReference; - -use chrono::format::ParseError; -use chrono::{DateTime, Duration, FixedOffset}; - -use assert_approx_eq::assert_approx_eq; - -fn evaluate_formula_number(s: &str) -> f32 { +use chrono::{format::ParseError, DateTime, Duration, FixedOffset}; +use xlformula_engine::{ + calculate, parse_formula, + types::{self, XlNum}, + NoCustomFunction, NoReference, +}; + +fn evaluate_formula_number(s: &str) -> XlNum { let formula = parse_formula::parse_string_to_formula(s, None::); let result = calculate::calculate_formula(formula, None::); - calculate::result_to_string(result).parse::().unwrap() + calculate::result_to_string(result) + .parse::() + .unwrap() } fn evaluate_formula_string(s: &str) -> String { @@ -34,10 +31,12 @@ fn evaluate_formula_string_with_reference( fn evaluate_formula_number_with_reference( s: &str, f: Option<&impl Fn(String) -> types::Value>, -) -> f32 { +) -> XlNum { let formula = parse_formula::parse_string_to_formula(s, None::); let result = calculate::calculate_formula(formula, f); - calculate::result_to_string(result).parse::().unwrap() + calculate::result_to_string(result) + .parse::() + .unwrap() } fn evaluate_formula_number_with_reference_no_conversion( @@ -46,7 +45,7 @@ fn evaluate_formula_number_with_reference_no_conversion( ) -> types::Value { let formula = parse_formula::parse_string_to_formula(s, None::); calculate::calculate_formula(formula, f) - //calculate::result_to_string(result).parse::().unwrap() + //calculate::result_to_string(result).parse::().unwrap() } fn evaluate_formula_boolean_with_reference( @@ -55,7 +54,7 @@ fn evaluate_formula_boolean_with_reference( ) -> String { let formula = parse_formula::parse_string_to_formula(s, None::); let result = calculate::calculate_formula(formula, f); - calculate::result_to_string(result) //.parse::().unwrap() + calculate::result_to_string(result) //.parse::().unwrap() } fn evaluate_formula_date_with_reference( @@ -69,17 +68,19 @@ fn evaluate_formula_date_with_reference( fn evaluate_formula_number_with_custom_function( s: &str, - custom_function: Option<&impl Fn(String, Vec) -> types::Value>, + custom_function: Option<&impl Fn(String, Vec) -> types::Value>, //reference: Option<&impl Fn(String) -> types::Value>, -) -> f32 { +) -> XlNum { let formula = parse_formula::parse_string_to_formula(s, custom_function); let result = calculate::calculate_formula(formula, None::); - calculate::result_to_string(result).parse::().unwrap() + calculate::result_to_string(result) + .parse::() + .unwrap() } fn evaluate_formula_string_with_custom_function( s: &str, - custom_function: Option<&impl Fn(String, Vec) -> types::Value>, + custom_function: Option<&impl Fn(String, Vec) -> types::Value>, //reference: Option<&impl Fn(String) -> types::Value>, ) -> String { let formula = parse_formula::parse_string_to_formula(s, custom_function); @@ -89,12 +90,14 @@ fn evaluate_formula_string_with_custom_function( fn _evaluate_formula_number_with_custom_function_and_reference( s: &str, - custom_function: Option<&impl Fn(String, Vec) -> types::Value>, + custom_function: Option<&impl Fn(String, Vec) -> types::Value>, reference: Option<&impl Fn(String) -> types::Value>, -) -> f32 { +) -> XlNum { let formula = parse_formula::parse_string_to_formula(s, custom_function); let result = calculate::calculate_formula(formula, reference); - calculate::result_to_string(result).parse::().unwrap() + calculate::result_to_string(result) + .parse::() + .unwrap() } /////////////////// Simple math operators with floats and integer /////////////////// @@ -167,8 +170,16 @@ fn it_evaluate_float() { } #[test] -fn it_evaluate_negative_float() { - assert_approx_eq!(evaluate_formula_number("=-1.2+0.5"), -0.7); // left: `-0.70000005`, right: `-0.7`' +#[cfg(feature = "f32")] +fn it_evaluate_negative_float_f32() { + // left: `-0.70000005`, right: `-0.7`' + assert_approx_eq::assert_approx_eq!(evaluate_formula_number("=-1.2+0.5"), -0.7); +} + +#[test] +#[cfg(feature = "f64")] +fn it_evaluate_negative_float_f64() { + assert_eq!(evaluate_formula_number("=-1.2+0.5"), -0.7); } #[test] @@ -720,7 +731,7 @@ fn it_evaluate_date() -> Result<(), ParseError> { #[test] fn it_evaluate_custom_functions_() { - let custom_functions = |s: String, params: Vec| match s.as_str() { + let custom_functions = |s: String, params: Vec| match s.as_str() { "Increase" => types::Value::Number(params[0] + 1.0), "SimpleSum" => types::Value::Number(params[0] + params[1]), "CustomSum" => types::Value::Number(params[0] + params[1] + params[2]), @@ -1227,7 +1238,7 @@ fn it_evaluates_blanks_string_operations() { #[test] fn it_evaluates_blank_constructors() { - let custom_functions = |s: String, _params: Vec| match s.as_str() { + let custom_functions = |s: String, _params: Vec| match s.as_str() { "BLANK" => types::Value::Blank, _ => types::Value::Error(types::Error::Value), }; From ca9608c17f9ebbc54eda582497e690fad86984db Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Sun, 22 Jun 2025 15:17:59 +0300 Subject: [PATCH 13/23] Add pull_request workflow --- .github/workflows/pr.yaml | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/pr.yaml diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml new file mode 100644 index 0000000..f8ae296 --- /dev/null +++ b/.github/workflows/pr.yaml @@ -0,0 +1,65 @@ +name: 🔃 Pull Request + +on: + pull_request: + +jobs: + test: + name: 🧪 Run Tests + runs-on: ubuntu-latest + strategy: + matrix: + features: + - default + - f32 + - f64 + phase: + - debug + - release + steps: + - name: 🛎 Checkout + uses: actions/checkout@v4 + + - name: 📦 Install Cargo + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: 🚩 Cargo flags + id: cargo_flags + run: | + flags="" + + if [ "${{ matrix.features}}" != "default" ] + then + flags="${flags} --no-default-features --features=${{ matrix.features }}" + fi + + if [ "${{ matrix.phase }}" = "release" ] + then + flags="${flags} --release" + fi + + echo "flags=${flags}" >> "${GITHUB_OUTPUT}" + + - name: 📦 Cargo clippy + run: | + cargo clippy ${{ steps.cargo_flags.outputs.flags }} -- -D warnings + + - name: 📦 Cargo test + run: | + cargo test ${{ steps.cargo_flags.outputs.flags }} + + - name: 📦 Cargo run + run: | + cargo run ${{ steps.cargo_flags.outputs.flags }} + + - name: 🥱 Ensure no changes + run: | + if [ ! -z "$(git status --porcelain)" ] + then + echo "Files have changed while testing:" + echo + git status + exit 1 + fi From 66227457bd3255c5ca48033eb1930cc4e0709b1f Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Sun, 22 Jun 2025 17:36:54 +0300 Subject: [PATCH 14/23] Bump version to 0.2.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c119e36..6ed55e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,7 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "xlformula_engine" -version = "0.1.20" +version = "0.2.0" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index cb650a3..3d9525d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.1.20" +version = "0.2.0" [features] default = ["f32"] From 3f09ec2ed191114e525908bfe4c0769025c5c352 Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Thu, 24 Jul 2025 14:31:12 +0300 Subject: [PATCH 15/23] Update pest to 2.8 --- Cargo.lock | 175 ++++++++++++++++++++++++------------------- Cargo.toml | 8 +- README.md | 8 +- src/lib.rs | 8 +- src/parse_formula.rs | 91 +++++++++++----------- 5 files changed, 154 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ed55e7..ab13420 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,42 +10,24 @@ checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd" [[package]] name = "autocfg" -version = "1.0.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "block-padding", - "byte-tools", - "byteorder", "generic-array", ] [[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.3.4" +name = "cfg-if" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" @@ -57,64 +39,81 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.8.1" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "typenum", ] [[package]] -name = "fake-simd" -version = "0.1.2" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] name = "generic-array" -version = "0.12.3" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", + "version_check", ] [[package]] -name = "maplit" -version = "1.0.2" +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "memchr" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "pest" -version = "2.1.3" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ + "memchr", + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -122,9 +121,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.2" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e5277315f6b4f27e0e6744feb5d5ba1891e7164871033d3c8344c6783b349a" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", @@ -135,77 +134,101 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "maplit", "pest", - "sha-1", + "sha2", ] [[package]] name = "proc-macro2" -version = "1.0.9" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.3" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "block-buffer", + "cfg-if", + "cpufeatures", "digest", - "fake-simd", - "opaque-debug", ] [[package]] name = "syn" -version = "1.0.16" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "syn", ] [[package]] name = "typenum" -version = "1.11.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" -version = "0.1.2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] -name = "unicode-xid" -version = "0.2.0" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "xlformula_engine" -version = "0.2.0" +version = "0.3.0" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 3d9525d..e8beb73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,17 +8,17 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.2.0" +version = "0.3.0" [features] -default = ["f32"] +default = ["f64"] f32 = [] f64 = [] [dependencies] chrono = { version = "0.4.41", default-features = false } -pest = "2.1.3" -pest_derive = "2.1.0" +pest = "2.8.1" +pest_derive = "2.8.1" [dev-dependencies] assert_approx_eq = "1.1.0" diff --git a/README.md b/README.md index 4cfd579..6bcfcdd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. -It works with f32 by default (the `f32` feature) or with `f64` by enabling the `f64` feature and disabling default features. +It works with f64 by default (the `f64` feature) or with `f32` by enabling the `f32` feature and disabling default features. ## Features @@ -27,14 +27,14 @@ Add the corresponding entry to your Cargo.toml dependency list: ```toml [dependencies] -xlformula_engine = "0.1.19" +xlformula_engine = "0.3.0" ``` -Or use the following for using `f64`: +Or use the following for using `f32`: ```toml [dependencies] -xlformula_engine = { version = "0.1.19", default-features = false, features = ["f64"] } +xlformula_engine = { version = "0.3.0", default-features = false, features = ["f32"] } ``` ## Examples diff --git a/src/lib.rs b/src/lib.rs index 2c246c4..5dfe8be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //!# XLFormula Engine //!XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. //! -//!It works with f32 by default (the `f32` feature) or with `f64` by enabling the `f64` feature and disabling default features. +//!It works with f64 by default (the `f64` feature) or with `f32` by enabling the `f32` feature and disabling default features. //! //!## Features //!It supports: @@ -24,11 +24,7 @@ //!Add the corresponding entry to your Cargo.toml dependency list: //!```toml //![dependencies] -//!xlformula_engine = "0.1.18" -//!``` -//!and add this to your crate root: -//!```rust -//!extern crate xlformula_engine; +//!xlformula_engine = "0.3.0" //!``` //! //!## Examples diff --git a/src/parse_formula.rs b/src/parse_formula.rs index 33f479f..9e419a8 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -1,6 +1,6 @@ use crate::types::{self, XlNum}; use pest::{ - prec_climber::{Assoc, Operator, PrecClimber}, + pratt_parser::{Assoc, Op, PrattParser}, Parser, }; @@ -42,7 +42,7 @@ pub fn parse_string_to_formula( ) -> types::Formula { match parse_string(s) { Some(parse_result) => match parse_result.as_rule() { - Rule::expr => build_formula_with_climber(parse_result.into_inner(), f), + Rule::expr => build_formula_with_parser(parse_result.into_inner(), f), Rule::string_constant => parse_string_constant(parse_result), _ => types::Formula::Value(types::Value::Error(types::Error::Parse)), }, @@ -89,7 +89,7 @@ fn build_formula_unary_operator( }; let operation = types::Expression { op: op_type, - values: vec![build_formula_with_climber(pair.into_inner(), f)], + values: vec![build_formula_with_parser(pair.into_inner(), f)], }; types::Formula::Operation(operation) } @@ -105,7 +105,7 @@ fn build_formula_iterator( ) -> types::Formula { let mut vec = Vec::new(); for term in pair.into_inner() { - vec.push(build_formula_with_climber(term.into_inner(), f)); + vec.push(build_formula_with_parser(term.into_inner(), f)); } types::Formula::Iterator(vec) } @@ -124,7 +124,7 @@ fn build_formula_collective_operator( { vec.push(types::Formula::Value(types::Value::Blank)) } else { - vec.push(build_formula_with_climber(term.into_inner(), f)) + vec.push(build_formula_with_parser(term.into_inner(), f)) } } let op_type = rule_to_function_operator(collective_operation); @@ -166,7 +166,7 @@ fn build_formula_collective_operator_average( { vec.push(types::Formula::Value(types::Value::Number(0.0))) } else { - vec.push(build_formula_with_climber(term.into_inner(), f)) + vec.push(build_formula_with_parser(term.into_inner(), f)) } } let op_type = rule_to_function_operator(collective_operation); @@ -193,7 +193,7 @@ fn build_formula_collective_operator_and( types::Boolean::False, ))) } else { - vec.push(build_formula_with_climber(term.into_inner(), f)) + vec.push(build_formula_with_parser(term.into_inner(), f)) } } let op_type = rule_to_function_operator(collective_operation); @@ -217,7 +217,7 @@ fn build_formula_iff( { vec.push(types::Formula::Value(types::Value::Blank)) } else { - vec.push(build_formula_with_climber(term.into_inner(), f)) + vec.push(build_formula_with_parser(term.into_inner(), f)) } } let operation = types::Expression { @@ -292,25 +292,22 @@ fn build_formula_binary_operator( types::Formula::Operation(operation) } -/// Builds Formula Enum using a `pest-PrecClimber`. -fn build_formula_with_climber( +fn build_formula_with_parser( expression: pest::iterators::Pairs, f: Option<&impl Fn(String, Vec) -> types::Value>, ) -> types::Formula { - let climber = PrecClimber::new(vec![ - Operator::new(Rule::concat, Assoc::Left), - Operator::new(Rule::equal, Assoc::Left) | Operator::new(Rule::not_equal, Assoc::Left), - Operator::new(Rule::greater, Assoc::Left) - | Operator::new(Rule::less, Assoc::Left) - | Operator::new(Rule::greater_or_equal, Assoc::Left) - | Operator::new(Rule::less_or_equal, Assoc::Left), - Operator::new(Rule::add, Assoc::Left) | Operator::new(Rule::subtract, Assoc::Left), - Operator::new(Rule::multiply, Assoc::Left) | Operator::new(Rule::divide, Assoc::Left), - Operator::new(Rule::power, Assoc::Right), - ]); - climber.climb( - expression, - |pair: pest::iterators::Pair| match pair.as_rule() { + let pratt = PrattParser::new() + .op(Op::infix(Rule::concat, Assoc::Left)) + .op(Op::infix(Rule::equal, Assoc::Left) | Op::infix(Rule::not_equal, Assoc::Left)) + .op(Op::infix(Rule::greater, Assoc::Left) + | Op::infix(Rule::less, Assoc::Left) + | Op::infix(Rule::greater_or_equal, Assoc::Left) + | Op::infix(Rule::less_or_equal, Assoc::Left)) + .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::subtract, Assoc::Left)) + .op(Op::infix(Rule::multiply, Assoc::Left) | Op::infix(Rule::divide, Assoc::Left)) + .op(Op::infix(Rule::power, Assoc::Right)); + pratt + .map_primary(|pair: pest::iterators::Pair| match pair.as_rule() { Rule::number => build_formula_number(pair), Rule::string_double_quote => build_formula_string_double_quote(pair), Rule::string_single_quote => build_formula_string_single_quote(pair), @@ -327,7 +324,7 @@ fn build_formula_with_climber( Rule::reference => build_formula_reference(pair), Rule::iterator => build_formula_iterator(pair, f), Rule::negate => build_formula_unary_operator(Rule::negate, pair, f), - Rule::expr => build_formula_with_climber(pair.into_inner(), f), + Rule::expr => build_formula_with_parser(pair.into_inner(), f), Rule::days => build_formula_collective_operator(Rule::days, pair, f), Rule::right => build_formula_collective_operator(Rule::right, pair, f), Rule::left => build_formula_collective_operator(Rule::left, pair, f), @@ -335,25 +332,27 @@ fn build_formula_with_climber( Rule::iff => build_formula_iff(pair, f), Rule::isblank => build_formula_collective_operator(Rule::isblank, pair, f), _ => unreachable!(), - }, - |lhs: types::Formula, op: pest::iterators::Pair, rhs: types::Formula| match op - .as_rule() - { - Rule::add => build_formula_binary_operator(Rule::add, lhs, rhs), - Rule::subtract => build_formula_binary_operator(Rule::subtract, lhs, rhs), - Rule::multiply => build_formula_binary_operator(Rule::multiply, lhs, rhs), - Rule::divide => build_formula_binary_operator(Rule::divide, lhs, rhs), - Rule::power => build_formula_binary_operator(Rule::power, lhs, rhs), - Rule::concat => build_formula_binary_operator(Rule::concat, lhs, rhs), - Rule::equal => build_formula_binary_operator(Rule::equal, lhs, rhs), - Rule::not_equal => build_formula_binary_operator(Rule::not_equal, lhs, rhs), - Rule::greater => build_formula_binary_operator(Rule::greater, lhs, rhs), - Rule::less => build_formula_binary_operator(Rule::less, lhs, rhs), - Rule::greater_or_equal => { - build_formula_binary_operator(Rule::greater_or_equal, lhs, rhs) - } - Rule::less_or_equal => build_formula_binary_operator(Rule::less_or_equal, lhs, rhs), - _ => unreachable!(), - }, - ) + }) + .map_infix( + |lhs: types::Formula, op: pest::iterators::Pair, rhs: types::Formula| match op + .as_rule() + { + Rule::add => build_formula_binary_operator(Rule::add, lhs, rhs), + Rule::subtract => build_formula_binary_operator(Rule::subtract, lhs, rhs), + Rule::multiply => build_formula_binary_operator(Rule::multiply, lhs, rhs), + Rule::divide => build_formula_binary_operator(Rule::divide, lhs, rhs), + Rule::power => build_formula_binary_operator(Rule::power, lhs, rhs), + Rule::concat => build_formula_binary_operator(Rule::concat, lhs, rhs), + Rule::equal => build_formula_binary_operator(Rule::equal, lhs, rhs), + Rule::not_equal => build_formula_binary_operator(Rule::not_equal, lhs, rhs), + Rule::greater => build_formula_binary_operator(Rule::greater, lhs, rhs), + Rule::less => build_formula_binary_operator(Rule::less, lhs, rhs), + Rule::greater_or_equal => { + build_formula_binary_operator(Rule::greater_or_equal, lhs, rhs) + } + Rule::less_or_equal => build_formula_binary_operator(Rule::less_or_equal, lhs, rhs), + _ => unreachable!(), + }, + ) + .parse(expression) } From 93aa0fa813bc47b0012115a129225c50c60ee63e Mon Sep 17 00:00:00 2001 From: Shimi Bornstein Date: Wed, 6 Aug 2025 17:38:30 +0300 Subject: [PATCH 16/23] DEV-6150 Added length protection to left and right functions --- src/calculate.rs | 8 ++++++-- tests/test.rs | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/calculate.rs b/src/calculate.rs index 3ebc697..e5c659e 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -980,7 +980,9 @@ fn calculate_right(number_string: (types::Value, types::Value)) -> types::Value let trimmed_string = match string { types::Value::Text(s) => { let temp: &'static str = Box::leak(s.into_boxed_str()); - &temp[(temp.len() - trim_length)..] + let len = temp.len(); + let start = if trim_length > len { len } else { trim_length }; + &temp[(len - start)..] } _ => "", }; @@ -997,7 +999,9 @@ fn calculate_left(number_string: (types::Value, types::Value)) -> types::Value { let trimmed_string = match string { types::Value::Text(s) => { let temp: &'static str = Box::leak(s.into_boxed_str()); - &temp[..trim_length] + let len = temp.len(); + let end = if trim_length > len { len } else { trim_length }; + &temp[..end] } _ => "", }; diff --git a/tests/test.rs b/tests/test.rs index ac2c946..3120e1d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -779,6 +779,9 @@ fn it_evaluate_left_and_right_functions() { assert_eq!(evaluate_formula_string("=LEFT(\"apple\")"), "a",); assert_eq!(evaluate_formula_string("=\"P\"&LEFT(\"000\"&1,3)"), "P000",); + + assert_eq!(evaluate_formula_string("=LEFT(\"apple\", 10)"), "apple",); + assert_eq!(evaluate_formula_string("=RIGHT(\"apple\", 10)"), "apple",); } #[test] From 767f6ccc2c8bcbe0e83540dc195c5dc407cbf8f8 Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Wed, 6 Aug 2025 18:16:03 +0300 Subject: [PATCH 17/23] Bump version to 0.3.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab13420..068012c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,7 +228,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "xlformula_engine" -version = "0.3.0" +version = "0.3.1" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index e8beb73..099e5a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.3.0" +version = "0.3.1" [features] default = ["f64"] From ce04adf566cab49afe6f71d17b4dfb52a28b9321 Mon Sep 17 00:00:00 2001 From: Zohar Zilberman Date: Sun, 10 Aug 2025 19:42:48 +0300 Subject: [PATCH 18/23] Move f32/f64 to be a type parameter of Value --- .github/workflows/pr.yaml | 9 - Cargo.lock | 3 +- Cargo.toml | 8 +- README.md | 223 ++-- src/calculate.rs | 672 ++++++++---- src/lib.rs | 301 +----- src/main.rs | 182 ++-- src/parse_formula.rs | 185 +++- src/types.rs | 46 +- tests/test.rs | 2135 ++++++++++++++++++++++--------------- 10 files changed, 2097 insertions(+), 1667 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index f8ae296..05ab7ab 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -9,10 +9,6 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - features: - - default - - f32 - - f64 phase: - debug - release @@ -30,11 +26,6 @@ jobs: run: | flags="" - if [ "${{ matrix.features}}" != "default" ] - then - flags="${flags} --no-default-features --features=${{ matrix.features }}" - fi - if [ "${{ matrix.phase }}" = "release" ] then flags="${flags} --release" diff --git a/Cargo.lock b/Cargo.lock index 068012c..c548f4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,10 +228,11 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "xlformula_engine" -version = "0.3.1" +version = "0.4.0" dependencies = [ "assert_approx_eq", "chrono", + "num-traits", "pest", "pest_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 099e5a9..cb60cc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,11 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.3.1" - -[features] -default = ["f64"] -f32 = [] -f64 = [] +version = "0.4.0" [dependencies] chrono = { version = "0.4.41", default-features = false } +num-traits = "0.2.19" pest = "2.8.1" pest_derive = "2.8.1" diff --git a/README.md b/README.md index 6bcfcdd..40176ee 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. -It works with f64 by default (the `f64` feature) or with `f32` by enabling the `f32` feature and disabling default features. +Functions and structs get either `::` or `::` to indicate the number type used in the formulas. ## Features @@ -27,14 +27,7 @@ Add the corresponding entry to your Cargo.toml dependency list: ```toml [dependencies] -xlformula_engine = "0.3.0" -``` - -Or use the following for using `f32`: - -```toml -[dependencies] -xlformula_engine = { version = "0.3.0", default-features = false, features = ["f32"] } +xlformula_engine = "0.4.0" ``` ## Examples @@ -42,25 +35,22 @@ xlformula_engine = { version = "0.3.0", default-features = false, features = ["f Here are simple examples of parsing an Excel formula string and evaluating to a result: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() { -let formula = parse_formula::parse_string_to_formula(&"=1+2", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula::(&"=1+2", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=(1*(2+3))*2", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula::(&"=(1*(2+3))*2", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=1+3/0", None::); // error (#DIV/0!) -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula::(&"=1+3/0", None::>); // error (#DIV/0!) +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -} ``` The last string is evaluated to #DIV/0!. @@ -68,21 +58,18 @@ The last string is evaluated to #DIV/0!. Concatenating strings: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() { -let formula = parse_formula::parse_string_to_formula(&"=\"Hello \" & \" World!\"", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=\"Hello \" & \" World!\"", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=1 + \"Hello\"", None::); // error (#CAST!) -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=1 + \"Hello\"", None::>); // error (#CAST!) +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -} ``` Concatenating number and string results in a #CAST! error. @@ -90,137 +77,121 @@ Concatenating number and string results in a #CAST! error. Constants ( i.e. a string without '=' ): ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() { -let formula = parse_formula::parse_string_to_formula(&"1.2", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"1.2", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"Hello World", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"Hello World", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -} ``` Excel functions: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() { -let formula = parse_formula::parse_string_to_formula(&"=ABS(-1)", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=ABS(-1)", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=SUM(1,2,\"3\")", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=SUM(1,2,\"3\")", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=PRODUCT(ABS(1),2*1, 3,4*1)", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=PRODUCT(ABS(1),2*1, 3,4*1)", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=RIGHT(\"apple\", 3)", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=RIGHT(\"apple\", 3)", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=LEFT(\"apple\", 3)", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=LEFT(\"apple\", 3)", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=LEFT(\"apple\")", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=LEFT(\"apple\")", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=IF(TRUE,1,0)", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=IF(TRUE,1,0)", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -} ``` Logical expressions: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() { -let formula = parse_formula::parse_string_to_formula(&"=2>=1", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=2>=1", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=OR(1>1,1<>1)", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=OR(1>1,1<>1)", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=AND(\"test\",\"True\", 1, true) ", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=AND(\"test\",\"True\", 1, true) ", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -} ``` References: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::types; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() { let data_function = |s: String| match s.as_str() { -"A" => types::Value::Text("=1+B".to_string()), -"B" => types::Value::Number(3.0), -_ => types::Value::Error(types::Error::Value), + "A" => types::Value::Text("=1+B".to_string()), + "B" => types::Value::Number(3.0), + _ => types::Value::Error(types::Error::Value), }; -let formula = parse_formula::parse_string_to_formula(&"=A+B", None::); +let formula = parse_formula::parse_string_to_formula(&"=A+B", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); -} ``` List: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() { -let formula = parse_formula::parse_string_to_formula(&"={1,2,3}+{1,2,3}", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"={1,2,3}+{1,2,3}", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=XOR({0,0,0})", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=XOR({0,0,0})", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=AVERAGE({1,2,3},1,2,3)", None::); -let result = calculate::calculate_formula(formula, None::); +let formula = parse_formula::parse_string_to_formula(&"=AVERAGE({1,2,3},1,2,3)", None::>); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -} ``` Date: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::types; @@ -230,69 +201,61 @@ use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() -> Result<(), ParseError> { -let start: DateTime = DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z")?; -let end: DateTime = DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z")?; +let start: DateTime = DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z").unwrap(); +let end: DateTime = DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z").unwrap(); let data_function = |s: String| match s.as_str() { -"start" => types::Value::Date(start), -"end" => types::Value::Date(end), -_ => types::Value::Error(types::Error::Value), + "start" => types::Value::Date(start), + "end" => types::Value::Date(end), + _ => types::Value::Error(types::Error::Value), }; -let formula = parse_formula::parse_string_to_formula(&"=DAYS(end, start)", None::); +let formula = parse_formula::parse_string_to_formula(&"=DAYS(end, start)", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=start+1", None::); +let formula = parse_formula::parse_string_to_formula(&"=start+1", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); -let formula = parse_formula::parse_string_to_formula(&"=end-3", None::); +let formula = parse_formula::parse_string_to_formula(&"=end-3", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); -Ok(()) -} ``` Custom Function: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::types; -use xlformula_engine::types::XlNum; use xlformula_engine::NoReference; -fn main() { -let custom_functions = |s: String, params: Vec| match s.as_str() { -"Increase" => types::Value::Number(params[0] + 1.0), -"SimpleSum" => types::Value::Number(params[0] + params[1]), -"EqualFive" => types::Value::Number(5.0), -_ => types::Value::Error(types::Error::Value), +let custom_functions = |s: String, params: Vec| match s.as_str() { + "Increase" => types::Value::Number(params[0] + 1.0), + "SimpleSum" => types::Value::Number(params[0] + params[1]), + "EqualFive" => types::Value::Number(5.0), + _ => types::Value::Error(types::Error::Value), }; let formula = parse_formula::parse_string_to_formula(&"=Increase(1)+1", Some(&custom_functions)); -let result = calculate::calculate_formula(formula, None::); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula(&"=EqualFive()+1", Some(&custom_functions)); -let result = calculate::calculate_formula(formula, None::); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula(&"=SimpleSum(1,2)", Some(&custom_functions)); -let result = calculate::calculate_formula(formula, None::); +let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); -} ``` Handle blank in calculation: ```rust -extern crate xlformula_engine; use xlformula_engine::calculate; use xlformula_engine::parse_formula; use xlformula_engine::types; @@ -301,38 +264,36 @@ use chrono::{DateTime, FixedOffset}; use xlformula_engine::NoReference; use xlformula_engine::NoCustomFunction; -fn main() -> { - let data_function = |s: String| match s.as_str() { - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - - let custom_functions = |s: String, params: Vec| match s.as_str() { - "BLANK" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - - let formula = parse_formula::parse_string_to_formula(&"=SUM(B, 1)", None::); - let result = calculate::calculate_formula(formula, Some(&data_function)); - println!("Result is {}", calculate::result_to_string(result)); - - let formula = - parse_formula::parse_string_to_formula(&"=SUM(BLANK(), 1)", Some(&custom_functions)); - let result = calculate::calculate_formula(formula, None::); - println!("Result is {}", calculate::result_to_string(result)); - - //takes list as input - let formula = parse_formula::parse_string_to_formula(&"=SUM({B, 1})", None::); - let result = calculate::calculate_formula(formula, Some(&data_function)); - println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula(&"=XOR({F,B,T,B,F,{F,B,T,B,F}})", None::); - let result = calculate::calculate_formula(formula, Some(&data_function)); - println!("Result is {}", calculate::result_to_string(result)); - - let formula = parse_formula::parse_string_to_formula(&"=SUM(1, 2, , 3)", None::); - let result = calculate::calculate_formula(formula, None::); - println!("Result is {}", calculate::result_to_string(result)); -} +let data_function = |s: String| match s.as_str() { + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), +}; + +let custom_functions = |s: String, params: Vec| match s.as_str() { + "BLANK" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), +}; + +let formula = parse_formula::parse_string_to_formula(&"=SUM(B, 1)", None::>); +let result = calculate::calculate_formula(formula, Some(&data_function)); +println!("Result is {}", calculate::result_to_string(result)); + +let formula = + parse_formula::parse_string_to_formula(&"=SUM(BLANK(), 1)", Some(&custom_functions)); +let result = calculate::calculate_formula(formula, None::>); +println!("Result is {}", calculate::result_to_string(result)); + +//takes list as input +let formula = parse_formula::parse_string_to_formula(&"=SUM({B, 1})", None::>); +let result = calculate::calculate_formula(formula, Some(&data_function)); +println!("Result is {}", calculate::result_to_string(result)); +let formula = parse_formula::parse_string_to_formula(&"=XOR({F,B,T,B,F,{F,B,T,B,F}})", None::>); +let result = calculate::calculate_formula(formula, Some(&data_function)); +println!("Result is {}", calculate::result_to_string(result)); + +let formula = parse_formula::parse_string_to_formula(&"=SUM(1, 2, , 3)", None::>); +let result = calculate::calculate_formula(formula, None::>); +println!("Result is {}", calculate::result_to_string(result)); ``` ## License diff --git a/src/calculate.rs b/src/calculate.rs index e5c659e..abf300d 100644 --- a/src/calculate.rs +++ b/src/calculate.rs @@ -3,21 +3,33 @@ use crate::{ types::{self, XlNum}, }; use chrono::{DateTime, Duration, FixedOffset}; +use std::{fmt::Debug, str::FromStr}; -type NoCustomFunction<'a> = &'a fn(String, Vec) -> types::Value; +type NoCustomFunction<'a, N> = &'a fn(String, Vec) -> types::Value; -fn calculate_divide_operator(num1: XlNum, num2: XlNum) -> XlNum { +fn calculate_divide_operator(num1: N, num2: N) -> N +where + N: XlNum, + ::Err: Debug, +{ num1 / num2 } -fn is_float_int(num: XlNum) -> bool { - //((num as i32) as XlNum) == num - (((num as i32) as XlNum) - num).abs() == 0.0 +fn is_float_int(num: N) -> bool +where + N: XlNum, + ::Err: Debug, +{ + num.fract().is_zero() } -fn calculate_power_operator(num1: XlNum, num2: XlNum) -> XlNum { +fn calculate_power_operator(num1: N, num2: N) -> N +where + N: XlNum, + ::Err: Debug, +{ if is_float_int(num2) { - num1.powi(num2 as i32) + num1.powi(num2.as_()) } else { num1.powf(num2) } @@ -27,11 +39,15 @@ fn calculate_concat_operator(str1: &str, str2: &str) -> String { str1.to_owned() + str2 } -fn calculate_string_operation_rhs( +fn calculate_string_operation_rhs( l: &str, - rhs: types::Value, + rhs: types::Value, f: fn(str1: &str, str2: &str) -> String, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, @@ -43,11 +59,15 @@ fn calculate_string_operation_rhs( } } -fn calculate_string_operator( - lhs: types::Value, - rhs: types::Value, +fn calculate_string_operator( + lhs: types::Value, + rhs: types::Value, f: fn(str1: &str, str2: &str) -> String, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match lhs { types::Value::Boolean(_) => lhs, types::Value::Error(_) => lhs, @@ -59,16 +79,20 @@ fn calculate_string_operator( } } -fn calcualte_numeric_operator_rhs_text( +fn calcualte_numeric_operator_rhs_text( t: String, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { - match t.parse::() { + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match t.parse::() { Ok(nl) => match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(nl, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -81,16 +105,20 @@ fn calcualte_numeric_operator_rhs_text( } } -fn calculate_numeric_operator_rhs_number( - l: XlNum, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_numeric_operator_rhs_number( + l: N, + lhs: types::Value, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(l, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -106,20 +134,24 @@ fn calculate_numeric_operator_rhs_number( } } types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Number(f(l, 0.0)), + types::Value::Blank => types::Value::Number(f(l, N::zero())), } } -fn calculate_numeric_operator_product_rhs_number( - l: XlNum, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_numeric_operator_product_rhs_number( + l: N, + lhs: types::Value, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(l, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -142,11 +174,15 @@ fn calculate_numeric_operator_product_rhs_number( } } -fn calculate_numeric_operator_rhs_iterator( - mut lhs_vec: Vec, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_numeric_operator_rhs_iterator( + mut lhs_vec: Vec>, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { types::Value::Number(_) => { if let Some(mut temp) = lhs_vec.pop() { @@ -176,25 +212,37 @@ fn calculate_numeric_operator_rhs_iterator( } } -fn add_days_to_date(d: DateTime, rhs: types::Value) -> types::Value { +fn add_days_to_date(d: DateTime, rhs: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { - types::Value::Number(x) => types::Value::Date(d + Duration::days(x as i64)), + types::Value::Number(x) => types::Value::Date(d + Duration::days(x.as_())), _ => types::Value::Error(types::Error::Value), } } -fn subtract_days_from_date(d: DateTime, rhs: types::Value) -> types::Value { +fn subtract_days_from_date(d: DateTime, rhs: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { - types::Value::Number(x) => types::Value::Date(d - Duration::days(x as i64)), + types::Value::Number(x) => types::Value::Date(d - Duration::days(x.as_())), _ => types::Value::Error(types::Error::Value), } } -fn calculate_numeric_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_numeric_operator( + lhs: types::Value, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ //println!("{:?}::{:?}", lhs, rhs); match lhs { types::Value::Boolean(_) => lhs, @@ -203,15 +251,19 @@ fn calculate_numeric_operator( types::Value::Number(l) => calculate_numeric_operator_rhs_number(l, lhs, rhs, f), types::Value::Iterator(lhs_vec) => calculate_numeric_operator_rhs_iterator(lhs_vec, rhs, f), types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => calculate_numeric_operator_rhs_number(0.0, lhs, rhs, f), + types::Value::Blank => calculate_numeric_operator_rhs_number(N::zero(), lhs, rhs, f), } } -fn calculate_numeric_product_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_numeric_product_operator( + lhs: types::Value, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ //println!("{:?}::{:?}", lhs, rhs); match lhs { types::Value::Boolean(_) => lhs, @@ -220,21 +272,25 @@ fn calculate_numeric_product_operator( types::Value::Number(l) => calculate_numeric_operator_product_rhs_number(l, lhs, rhs, f), types::Value::Iterator(lhs_vec) => calculate_numeric_operator_rhs_iterator(lhs_vec, rhs, f), types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => calculate_numeric_operator_product_rhs_number(1.0, lhs, rhs, f), + types::Value::Blank => calculate_numeric_operator_product_rhs_number(N::one(), lhs, rhs, f), } } -fn calculate_average_operator_rhs_number( - element_count: &mut i32, - l: XlNum, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_average_operator_rhs_number( + element_count: &mut i64, + l: N, + lhs: types::Value, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { types::Value::Boolean(_) => rhs, types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { + types::Value::Text(t) => match t.parse::() { Ok(nr) => types::Value::Number(f(l, nr)), Err(_) => types::Value::Error(types::Error::Cast), }, @@ -259,17 +315,21 @@ fn calculate_average_operator_rhs_number( types::Value::Date(_) => types::Value::Error(types::Error::Value), types::Value::Blank => { *element_count -= 1; - types::Value::Number(f(l, 0.0)) + types::Value::Number(f(l, N::zero())) } } } -fn calculate_average_operator_rhs_iterator( - element_count: &mut i32, - mut lhs_vec: Vec, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_average_operator_rhs_iterator( + element_count: &mut i64, + mut lhs_vec: Vec>, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rhs { types::Value::Number(_) => { if let Some(mut temp) = lhs_vec.pop() { @@ -286,12 +346,16 @@ fn calculate_average_operator_rhs_iterator( } } -fn calculate_average_operator( - element_count: &mut i32, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_average_operator( + element_count: &mut i64, + lhs: types::Value, + rhs: types::Value, + f: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match lhs { types::Value::Boolean(_) => lhs, types::Value::Error(_) => lhs, @@ -305,16 +369,20 @@ fn calculate_average_operator( types::Value::Date(_) => types::Value::Error(types::Error::Value), types::Value::Blank => { *element_count -= 1; - calculate_average_operator_rhs_number(element_count, 0.0, lhs, rhs, f) + calculate_average_operator_rhs_number(element_count, N::zero(), lhs, rhs, f) } } } -fn calculate_comparison_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(num1: XlNum, num2: XlNum) -> bool, -) -> types::Value { +fn calculate_comparison_operator( + lhs: types::Value, + rhs: types::Value, + f: fn(num1: N, num2: N) -> bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match lhs { types::Value::Text(l) => match rhs { types::Value::Text(r) => { @@ -370,11 +438,15 @@ fn to_bool(value: types::Boolean) -> bool { } } -fn calculate_boolean_operator_rhs_boolean( +fn calculate_boolean_operator_rhs_boolean( l: types::Boolean, - rh: types::Value, + rh: types::Value, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rh { types::Value::Boolean(r) => { if f(to_bool(l), to_bool(r)) { @@ -414,7 +486,11 @@ fn calculate_boolean_operator_rhs_boolean( } } -fn calculate_boolean_operator_rhs_error(rh: types::Value) -> types::Value { +fn calculate_boolean_operator_rhs_error(rh: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rh { types::Value::Boolean(r) => { if to_bool(r) { @@ -428,11 +504,15 @@ fn calculate_boolean_operator_rhs_error(rh: types::Value) -> types::Value { } } -fn calculate_boolean_operator_rhs_iterator( - rh: types::Value, - mut lhs_vec: Vec, +fn calculate_boolean_operator_rhs_iterator( + rh: types::Value, + mut lhs_vec: Vec>, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match rh { types::Value::Boolean(r) => { if let Some(mut temp) = lhs_vec.pop() { @@ -459,11 +539,15 @@ fn calculate_boolean_operator_rhs_iterator( } } -fn calculate_boolean_operator( - lhs: types::Value, - rhs: types::Value, +fn calculate_boolean_operator( + lhs: types::Value, + rhs: types::Value, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let lh = cast_value_to_boolean(lhs); match lh { types::Value::Boolean(l) => { @@ -481,11 +565,15 @@ fn calculate_boolean_operator( } } -fn calculate_boolean_operator_or( - lhs: types::Value, - rhs: types::Value, +fn calculate_boolean_operator_or( + lhs: types::Value, + rhs: types::Value, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let lh = cast_value_to_boolean(lhs); match lh { types::Value::Boolean(l) => { @@ -504,11 +592,15 @@ fn calculate_boolean_operator_or( } } -fn calculate_boolean_operator_xor( - lhs: types::Value, - rhs: types::Value, +fn calculate_boolean_operator_xor( + lhs: types::Value, + rhs: types::Value, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let lh = cast_value_to_boolean(lhs); match lh { types::Value::Boolean(l) => { @@ -527,7 +619,11 @@ fn calculate_boolean_operator_xor( } } -fn calculate_abs(value: types::Value) -> types::Value { +fn calculate_abs(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match value { types::Value::Boolean(_) => value, types::Value::Error(_) => value, @@ -535,11 +631,15 @@ fn calculate_abs(value: types::Value) -> types::Value { types::Value::Number(l) => types::Value::Number(l.abs()), types::Value::Iterator(_) => types::Value::Error(types::Error::Value), types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Number(0.0), + types::Value::Blank => types::Value::Number(N::zero()), } } -fn calculate_negation(value: types::Value) -> types::Value { +fn calculate_negation(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match value { types::Value::Boolean(l) => { if !(to_bool(l)) { @@ -563,7 +663,7 @@ fn calculate_negation(value: types::Value) -> types::Value { } } types::Value::Number(l) => { - if l == 0.0 { + if l.is_zero() { types::Value::Boolean(types::Boolean::True) } else { types::Value::Boolean(types::Boolean::False) @@ -575,9 +675,13 @@ fn calculate_negation(value: types::Value) -> types::Value { } } -fn calculate_negate(value: types::Value) -> types::Value { +fn calculate_negate(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match value { - types::Value::Number(l) => types::Value::Number(-l), + types::Value::Number(n) => types::Value::Number(-n), types::Value::Iterator(mut value_vec) => { let mut result_vec = Vec::new(); while let Some(top) = value_vec.pop() { @@ -600,7 +704,11 @@ fn cast_text_to_boolean(s: &str) -> Option { } } -fn cast_value_to_boolean(value: types::Value) -> types::Value { +fn cast_value_to_boolean(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match value { types::Value::Boolean(_) => value, types::Value::Error(_) => value, @@ -618,7 +726,7 @@ fn cast_value_to_boolean(value: types::Value) -> types::Value { } } types::Value::Number(l) => { - if l != 0.0 { + if !l.is_zero() { types::Value::Boolean(types::Boolean::True) } else { types::Value::Boolean(types::Boolean::False) @@ -637,10 +745,14 @@ fn cast_value_to_boolean(value: types::Value) -> types::Value { } } -fn convert_iterator_to_result( - result: types::Value, +fn convert_iterator_to_result( + result: types::Value, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match result { types::Value::Iterator(mut value_vec) => { if let Some(mut temp) = value_vec.pop() { @@ -665,10 +777,14 @@ fn convert_iterator_to_result( } } -fn convert_iterator_to_result_or( - result: types::Value, +fn convert_iterator_to_result_or( + result: types::Value, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match result { types::Value::Iterator(mut value_vec) => { if let Some(mut temp) = value_vec.pop() { @@ -693,10 +809,14 @@ fn convert_iterator_to_result_or( } } -fn convert_iterator_to_result_xor( - result: types::Value, +fn convert_iterator_to_result_xor( + result: types::Value, f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match result { types::Value::Iterator(mut value_vec) => { if let Some(mut temp) = value_vec.pop() { @@ -721,10 +841,14 @@ fn convert_iterator_to_result_xor( } } -fn get_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value) { +fn get_values( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ ( match exp.values.pop() { Some(formula) => calculate_formula(formula, f), @@ -737,20 +861,28 @@ fn get_values( ) } -fn get_value( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value { +fn get_value( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match exp.values.pop() { Some(formula) => calculate_formula(formula, f), None => types::Value::Error(types::Error::Argument), } } -fn get_date_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value) { +fn get_date_values( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ ( match exp.values.pop() { Some(formula) => calculate_formula(formula, f), @@ -763,13 +895,17 @@ fn get_date_values( ) } -fn get_number_and_string_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value) { +fn get_number_and_string_values( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ if exp.values.len() == 1 { ( - types::Value::Number(1.0), + types::Value::Number(N::one()), match exp.values.pop() { Some(formula) => calculate_formula(formula, f), None => types::Value::Error(types::Error::Argument), @@ -789,10 +925,14 @@ fn get_number_and_string_values( } } -fn get_iff_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value, types::Value) { +fn get_iff_values( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ ( match exp.values.pop() { Some(formula) => calculate_formula(formula, f), @@ -809,10 +949,14 @@ fn get_iff_values( ) } -fn calculate_iterator( - mut vec: Vec, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value { +fn calculate_iterator( + mut vec: Vec>, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let mut value_vec = Vec::new(); while let Some(top) = vec.pop() { value_vec.push(calculate_formula(top, f)); @@ -820,15 +964,19 @@ fn calculate_iterator( types::Value::Iterator(value_vec) } -fn calculate_reference( +fn calculate_reference( string: String, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value { + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match f { Some(f) => match f(string) { types::Value::Number(x) => types::Value::Number(x), types::Value::Text(s) => calculate_formula( - parse_formula::parse_string_to_formula(&s, None::), + parse_formula::parse_string_to_formula(&s, None::>), Some(f), ), types::Value::Boolean(x) => types::Value::Boolean(x), @@ -842,11 +990,15 @@ fn calculate_reference( } } -fn calculate_bool( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, +fn calculate_bool( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, f_bool: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let mut result = match exp.values.pop() { Some(formula) => calculate_formula(formula, f), None => types::Value::Error(types::Error::Argument), @@ -858,11 +1010,15 @@ fn calculate_bool( convert_iterator_to_result(result, f_bool) } -fn calculate_or( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, +fn calculate_or( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, f_bool: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let mut result = match exp.values.pop() { Some(formula) => calculate_formula(formula, f), None => types::Value::Error(types::Error::Argument), @@ -874,11 +1030,15 @@ fn calculate_or( convert_iterator_to_result_or(result, f_bool) } -fn calculate_xor( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, +fn calculate_xor( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, f_bool: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let mut result = match exp.values.pop() { Some(formula) => calculate_formula(formula, f), None => types::Value::Error(types::Error::Argument), @@ -890,12 +1050,16 @@ fn calculate_xor( convert_iterator_to_result_xor(result, f_bool) } -fn calculate_collective_operator( - mut collective_value: types::Value, - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_collective_operator( + mut collective_value: types::Value, + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, + f_collective: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ while let Some(top) = exp.values.pop() { collective_value = calculate_numeric_operator(collective_value, calculate_formula(top, f), f_collective); @@ -903,12 +1067,16 @@ fn calculate_collective_operator( collective_value } -fn calculate_collective_product_operator( - mut collective_value: types::Value, - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_collective_product_operator( + mut collective_value: types::Value, + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, + f_collective: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ while let Some(top) = exp.values.pop() { collective_value = calculate_numeric_product_operator( collective_value, @@ -917,17 +1085,21 @@ fn calculate_collective_product_operator( ); } match collective_value { - types::Value::Blank => types::Value::Number(0.0), + types::Value::Blank => types::Value::Number(N::zero()), _ => collective_value, } } -fn calculate_average( - mut collective_value: types::Value, - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: XlNum, num2: XlNum) -> XlNum, -) -> types::Value { +fn calculate_average( + mut collective_value: types::Value, + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, + f_collective: fn(num1: N, num2: N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let mut element_count = 0; while let Some(top) = exp.values.pop() { element_count += 1; @@ -943,13 +1115,17 @@ fn calculate_average( } else { calculate_numeric_operator( collective_value, - types::Value::Number(element_count as XlNum), + types::Value::Number(N::from_i64(element_count).unwrap()), calculate_divide_operator, ) } } -fn calculate_days(date_pair: (types::Value, types::Value)) -> types::Value { +fn calculate_days(date_pair: (types::Value, types::Value)) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let begin_of_date: DateTime = DateTime::parse_from_rfc3339("1900-01-01T02:00:00.000Z") .ok() @@ -957,23 +1133,27 @@ fn calculate_days(date_pair: (types::Value, types::Value)) -> types::Value { let (start, end) = date_pair; match (start, end) { (types::Value::Date(start), types::Value::Date(end)) => { - types::Value::Number((end - start).num_days() as XlNum) + types::Value::Number(N::from_i64((end - start).num_days()).unwrap()) } (types::Value::Blank, types::Value::Date(end)) => { - types::Value::Number((end - begin_of_date).num_days() as XlNum) + types::Value::Number(N::from_i64((end - begin_of_date).num_days()).unwrap()) } (types::Value::Date(start), types::Value::Blank) => { - types::Value::Number((begin_of_date - start).num_days() as XlNum) + types::Value::Number(N::from_i64((begin_of_date - start).num_days()).unwrap()) } - (types::Value::Blank, types::Value::Blank) => types::Value::Number(0.0), + (types::Value::Blank, types::Value::Blank) => types::Value::Number(N::zero()), _ => types::Value::Error(types::Error::Value), } } -fn calculate_right(number_string: (types::Value, types::Value)) -> types::Value { +fn calculate_right(number_string: (types::Value, types::Value)) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let (number, string) = number_string; let trim_length = match number { - types::Value::Number(x) => x as usize, + types::Value::Number(x) => x.as_(), _ => 0, }; @@ -989,10 +1169,14 @@ fn calculate_right(number_string: (types::Value, types::Value)) -> types::Value types::Value::Text(trimmed_string.to_string()) } -fn calculate_left(number_string: (types::Value, types::Value)) -> types::Value { +fn calculate_left(number_string: (types::Value, types::Value)) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let (number, string) = number_string; let trim_length = match number { - types::Value::Number(x) => x as usize, + types::Value::Number(x) => x.as_(), _ => 0, }; @@ -1008,7 +1192,13 @@ fn calculate_left(number_string: (types::Value, types::Value)) -> types::Value { types::Value::Text(trimmed_string.to_string()) } -fn calculate_iff(iff_arguments: (types::Value, types::Value, types::Value)) -> types::Value { +fn calculate_iff( + iff_arguments: (types::Value, types::Value, types::Value), +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ let (false_value, true_value, bool_expression) = iff_arguments; match bool_expression { types::Value::Boolean(bool_value) => { @@ -1019,7 +1209,7 @@ fn calculate_iff(iff_arguments: (types::Value, types::Value, types::Value)) -> t } } types::Value::Number(number_value) => { - if number_value == 0.0 { + if number_value.is_zero() { false_value } else { true_value @@ -1032,7 +1222,11 @@ fn calculate_iff(iff_arguments: (types::Value, types::Value, types::Value)) -> t } } -fn calculate_isblank(value: types::Value) -> types::Value { +fn calculate_isblank(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match value { types::Value::Blank => types::Value::Boolean(types::Boolean::True), types::Value::Text(s) => { @@ -1048,21 +1242,25 @@ fn calculate_isblank(value: types::Value) -> types::Value { } } -fn calculate_function( +fn calculate_function( func: types::Function, - exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value { + exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match func { types::Function::Abs => calculate_abs(get_value(exp, f)), types::Function::Sum => { - calculate_collective_operator(types::Value::Number(0.0), exp, f, |n1, n2| n1 + n2) + calculate_collective_operator(types::Value::Number(N::zero()), exp, f, |n1, n2| n1 + n2) } types::Function::Product => { calculate_collective_product_operator(types::Value::Blank, exp, f, |n1, n2| n1 * n2) } types::Function::Average => { - calculate_average(types::Value::Number(0.00), exp, f, |n1, n2| n1 + n2) + calculate_average(types::Value::Number(N::zero()), exp, f, |n1, n2| n1 + n2) } types::Function::Or => calculate_or(exp, f, |n1, n2| n1 || n2), types::Function::And => calculate_bool(exp, f, |n1, n2| n1 && n2), @@ -1077,10 +1275,14 @@ fn calculate_function( } } -fn calculate_operation( - exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value { +fn calculate_operation( + exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match exp.op { types::Operator::Plus => { let (value2, value1) = get_values(exp, f); @@ -1105,7 +1307,7 @@ fn calculate_operation( types::Operator::Divide => { let (value2, value1) = get_values(exp, f); match value2 { - types::Value::Number(0.0) => types::Value::Error(types::Error::Div0), + types::Value::Number(n) if n.is_zero() => types::Value::Error(types::Error::Div0), _ => calculate_numeric_operator(value1, value2, calculate_divide_operator), } } @@ -1126,7 +1328,9 @@ fn calculate_operation( (types::Value::Text(l), types::Value::Text(r)) => { compare_strings(l, r, |s1, s2| s1 == s2) } - _ => calculate_comparison_operator(value1, value2, |n1, n2| (n1 - n2).abs() == 0.0), + _ => calculate_comparison_operator(value1, value2, |n1, n2| { + (n1 - n2).abs().is_zero() + }), } } types::Operator::NotEqual => { @@ -1138,7 +1342,9 @@ fn calculate_operation( (types::Value::Text(l), types::Value::Text(r)) => { compare_strings(l, r, |s1, s2| s1 != s2) } - _ => calculate_comparison_operator(value1, value2, |n1, n2| (n1 - n2).abs() > 0.0), + _ => calculate_comparison_operator(value1, value2, |n1, n2| { + (n1 - n2).abs() > N::zero() + }), } } types::Operator::Greater => { @@ -1181,11 +1387,15 @@ fn calculate_operation( } } -fn compare_dates( +fn compare_dates( date1: DateTime, date2: DateTime, f: fn(d1: DateTime, d2: DateTime) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ if f(date1, date2) { types::Value::Boolean(types::Boolean::True) } else { @@ -1193,11 +1403,15 @@ fn compare_dates( } } -fn compare_strings( +fn compare_strings( string1: String, string2: String, f: fn(s1: String, s2: String) -> bool, -) -> types::Value { +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ if f(string1, string2) { types::Value::Boolean(types::Boolean::True) } else { @@ -1207,10 +1421,14 @@ fn compare_strings( /// Evaluates a string that was parsed and stored in Expression Struct. /// Takes an optional closure with the trait bound Fn(String) -> types::Value. -pub fn calculate_formula( - formula: types::Formula, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value { +pub fn calculate_formula( + formula: types::Formula, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ match formula { types::Formula::Operation(exp) => calculate_operation(exp, f), types::Formula::Value(val) => val, @@ -1220,19 +1438,25 @@ pub fn calculate_formula( } /// Converts a result from Value Enum to a printable string. -pub fn result_to_string(_value: types::Value) -> String { - match _value { +pub fn result_to_string(value: types::Value) -> String +where + N: XlNum, +{ + match value { types::Value::Number(number) => show_number(number), types::Value::Text(text) => text, types::Value::Error(error) => show_error(error), types::Value::Boolean(boolean) => show_boolean(boolean), types::Value::Iterator(value_vec) => show_iterator(value_vec), types::Value::Date(date) => date.to_string(), - types::Value::Blank => show_blank(), + types::Value::Blank => show_blank::(), } } -fn show_number(number: XlNum) -> String { +fn show_number(number: N) -> String +where + N: XlNum, +{ if number.is_infinite() { String::from("#DIV/0!") } else { @@ -1258,7 +1482,10 @@ fn show_boolean(boolean: types::Boolean) -> String { } } -fn show_iterator(mut value_vec: Vec) -> String { +fn show_iterator(mut value_vec: Vec>) -> String +where + N: XlNum, +{ value_vec.reverse(); let mut result = '{'.to_string(); while let Some(top) = value_vec.pop() { @@ -1270,7 +1497,10 @@ fn show_iterator(mut value_vec: Vec) -> String { result } -fn show_blank() -> String { - show_number(0.0) +fn show_blank() -> String +where + N: XlNum, +{ + show_number(N::zero()) //String::from("BLANK") } diff --git a/src/lib.rs b/src/lib.rs index 5dfe8be..7fdb596 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,299 +1,4 @@ -//!# XLFormula Engine -//!XLFormula Engine is a Rust crate for parsing and evaluating Excel formulas. -//! -//!It works with f64 by default (the `f64` feature) or with `f32` by enabling the `f32` feature and disabling default features. -//! -//!## Features -//!It supports: -//! -//!* Any numbers, negative and positive, as float or integer -//!* Arithmetic operations +, -, /, *, ^ -//!* Logical operations AND(), OR(), NOT(), XOR() -//!* Comparison operations =, >, >=, <, <=, <> -//!* String operation & (concatenation) -//!* Build-in variables TRUE, FALSE -//!* Excel functions ABS(), SUM(), PRODUCT(), AVERAGE(), RIGHT(), LEFT(), IF(), ISBLANK() -//!* Operations on lists of values (one dimensional range) -//!* Add or subtract dates and excel funtion DAYS() -//!* Custom functions with number arguments -//!* Handle blank/null values in calculation -//!* Handle empty/missing parameters of function calls as blank values -//! -//!## Installation -//! -//!Add the corresponding entry to your Cargo.toml dependency list: -//!```toml -//![dependencies] -//!xlformula_engine = "0.3.0" -//!``` -//! -//!## Examples -//! -//!Here are simple examples of parsing an Excel formula string and evaluating to a result: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::NoReference; -//!use xlformula_engine::NoCustomFunction; -//! -//!let formula = parse_formula::parse_string_to_formula(&"=1+2", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=(1*(2+3))*2", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=1+3/0", None::); // error (#DIV/0!) -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//!The last string is evaluated to #DIV/0!. -//! -//!Concatenating strings: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::NoReference; -//!use xlformula_engine::NoCustomFunction; -//! -//!let formula = parse_formula::parse_string_to_formula(&"=\"Hello \" & \" World!\"", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=1 + \"Hello\"", None::); // error (#CAST!) -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//!Concatenating number and string results in a #CAST! error. -//! -//!Constants ( i.e. a string without '=' ): -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::NoReference; -//!use xlformula_engine::NoCustomFunction; -//! -//!let formula = parse_formula::parse_string_to_formula(&"1.2", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"Hello World", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//! -//!Excel functions: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::NoReference; -//!use xlformula_engine::NoCustomFunction; -//! -//!let formula = parse_formula::parse_string_to_formula(&"=ABS(-1)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=SUM(1,2,\"3\")", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=PRODUCT(ABS(1),2*1, 3,4*1)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=RIGHT(\"apple\", 3)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=LEFT(\"apple\", 3)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=LEFT(\"apple\")", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=IF(TRUE,1,0)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//! -//!Logical expressions: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::NoReference; -//!use xlformula_engine::NoCustomFunction; -//! -//!let formula = parse_formula::parse_string_to_formula(&"=2>=1", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=OR(1>1,1<>1)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=AND(\"test\",\"True\", 1, true) ", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//! -//!References: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::types; -//!use xlformula_engine::NoCustomFunction; -//! -//!let data_function = |s: String| match s.as_str() { -//!"A" => types::Value::Text("=1+B".to_string()), -//!"B" => types::Value::Number(3.0), -//!_ => types::Value::Error(types::Error::Value), -//!}; -//!let formula = parse_formula::parse_string_to_formula(&"=A+B", None::); -//!let result = calculate::calculate_formula(formula, Some(&data_function)); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//! -//!List: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::NoReference; -//!use xlformula_engine::NoCustomFunction; -//! -//!let formula = parse_formula::parse_string_to_formula(&"={1,2,3}+{1,2,3}", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=XOR({0,0,0})", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=AVERAGE({1,2,3},1,2,3)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//! -//!Date: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::types; -//!use chrono::format::ParseError; -//!use chrono::{DateTime, FixedOffset}; -//!use xlformula_engine::NoCustomFunction; -//! -//!fn main() -> Result<(), ParseError> { -//!let start: DateTime = DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z")?; -//!let end: DateTime = DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z")?; -//!let data_function = |s: String| match s.as_str() { -//!"start" => types::Value::Date(start), -//!"end" => types::Value::Date(end), -//!_ => types::Value::Error(types::Error::Value), -//!}; -//! -//!let formula = parse_formula::parse_string_to_formula(&"=DAYS(end, start)", None::); -//!let result = calculate::calculate_formula(formula, Some(&data_function)); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=start+1", None::); -//!let result = calculate::calculate_formula(formula, Some(&data_function)); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = parse_formula::parse_string_to_formula(&"=end-3", None::); -//!let result = calculate::calculate_formula(formula, Some(&data_function)); -//!println!("Result is {}", calculate::result_to_string(result)); -//! Ok(()) -//!} -//!``` -//! -//!Custom Function: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::types; -//!use xlformula_engine::types::XlNum; -//!use xlformula_engine::NoReference; -//! -//!let custom_functions = |s: String, params: Vec| match s.as_str() { -//!"Increase" => types::Value::Number(params[0] + 1.0), -//!"SimpleSum" => types::Value::Number(params[0] + params[1]), -//!"EqualFive" => types::Value::Number(5.0), -//!_ => types::Value::Error(types::Error::Value), -//!}; -//! -//!let formula = -//!parse_formula::parse_string_to_formula(&"=Increase(1)+1", Some(&custom_functions)); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = -//!parse_formula::parse_string_to_formula(&"=EqualFive()+1", Some(&custom_functions)); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = -//!parse_formula::parse_string_to_formula(&"=SimpleSum(1,2)", Some(&custom_functions)); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` -//! -//!Handle blank in calculation: -//!```rust -//!extern crate xlformula_engine; -//!use xlformula_engine::calculate; -//!use xlformula_engine::parse_formula; -//!use xlformula_engine::types; -//!use xlformula_engine::types::XlNum; -//!use chrono::format::ParseError; -//!use chrono::{DateTime, FixedOffset}; -//!use xlformula_engine::NoReference; -//!use xlformula_engine::NoCustomFunction; -//! -//!let data_function = |s: String| match s.as_str() { -//!"B" => types::Value::Blank, -//!_ => types::Value::Error(types::Error::Value), -//!}; -//! -//!let custom_functions = |s: String, params: Vec| match s.as_str() { -//!"BLANK" => types::Value::Blank, -//!_ => types::Value::Error(types::Error::Value), -//!}; -//! -//!let formula = parse_formula::parse_string_to_formula(&"=SUM(B, 1)", None::); -//!let result = calculate::calculate_formula(formula, Some(&data_function)); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = -//!parse_formula::parse_string_to_formula(&"=SUM(BLANK(), 1)", Some(&custom_functions)); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!//takes list as input -//!let formula = parse_formula::parse_string_to_formula(&"=SUM({B, 1})", None::); -//!let result = calculate::calculate_formula(formula, Some(&data_function)); -//!println!("Result is {}", calculate::result_to_string(result)); -//! -//!let formula = -//!parse_formula::parse_string_to_formula(&"=SUM(1, 2, , 3)", None::); -//!let result = calculate::calculate_formula(formula, None::); -//!println!("Result is {}", calculate::result_to_string(result)); -//!``` - -#[macro_use] -extern crate pest_derive; - +#[doc = include_str!("../README.md")] /// Evaluates a formula. pub mod calculate; @@ -303,5 +8,5 @@ pub mod types; /// Parses a string using `pest` and `pest::prec_climber`. pub mod parse_formula; -pub type NoReference<'a> = &'a fn(String) -> types::Value; -pub type NoCustomFunction<'a> = &'a fn(String, Vec) -> types::Value; +pub type NoReference<'a, N> = &'a fn(String) -> types::Value; +pub type NoCustomFunction<'a, N> = &'a fn(String, Vec) -> types::Value; diff --git a/src/main.rs b/src/main.rs index 9858d6c..f5b8d0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ +extern crate xlformula_engine; use chrono::{format::ParseError, DateTime, FixedOffset}; +use std::{fmt::Debug, str::FromStr}; use xlformula_engine::{ calculate, parse_formula, types::{self, XlNum}, @@ -6,110 +8,129 @@ use xlformula_engine::{ }; fn main() -> Result<(), ParseError> { - let formula = parse_formula::parse_string_to_formula("=1+2", None::); - let result = calculate::calculate_formula(formula, None::); + println!("========== f32 =========="); + demo::()?; + println!("========== f64 =========="); + demo::()?; + Ok(()) +} + +fn demo() -> Result<(), ParseError> +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula("=1+2", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let data_function = |s: String| match s.as_str() { "A" => types::Value::Text("=1+B".to_string()), - "B" => types::Value::Number(3.0), + "B" => types::Value::Number(N::from_f32(3.0).unwrap()), "C" => types::Value::Text("=1+A".to_string()), _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula("=A+B", None::); + let formula = parse_formula::parse_string_to_formula("=A+B", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=SUM(A,B,C)", None::); + let formula = + parse_formula::parse_string_to_formula("=SUM(A,B,C)", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=1+2", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = parse_formula::parse_string_to_formula("=1+2", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=(1*(2+3))*2", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = + parse_formula::parse_string_to_formula("=(1*(2+3))*2", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=1+3/0", None::); // error (#DIV/0!) - let result = calculate::calculate_formula(formula, None::); + let formula = parse_formula::parse_string_to_formula("=1+3/0", None::>); // error (#DIV/0!) + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( r#"="Hello " & " World!""#, - None::, + None::>, ); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(r#"=1 + "Hello""#, None::); // error (#CAST!) - let result = calculate::calculate_formula(formula, None::); + parse_formula::parse_string_to_formula(r#"=1 + "Hello""#, None::>); // error (#CAST!) + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("1.2", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = parse_formula::parse_string_to_formula("1.2", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("Hello World", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = + parse_formula::parse_string_to_formula("Hello World", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=ABS(-1)", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = parse_formula::parse_string_to_formula("=ABS(-1)", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(r#"=SUM(1,2,"3")"#, None::); - let result = calculate::calculate_formula(formula, None::); + parse_formula::parse_string_to_formula(r#"=SUM(1,2,"3")"#, None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( "=PRODUCT(ABS(1),2*1, 3,4*1)", - None::, + None::>, ); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=2>=1", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = parse_formula::parse_string_to_formula("=2>=1", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=OR(1>1,1<>1)", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = + parse_formula::parse_string_to_formula("=OR(1>1,1<>1)", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( r#"=AND("test","True", 1, true) "#, - None::, + None::>, ); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( "=SUM({1,2,3}, 4, {5,6,7})", - None::, + None::>, ); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = - parse_formula::parse_string_to_formula("=AVERAGE({1,2,3},1,2,3)", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = parse_formula::parse_string_to_formula( + "=AVERAGE({1,2,3},1,2,3)", + None::>, + ); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=XOR({0,0,0})", None::); - let result = calculate::calculate_formula(formula, None::); + let formula = + parse_formula::parse_string_to_formula("=XOR({0,0,0})", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula("={1,2,3}+{1,2,3}", None::); - let result = calculate::calculate_formula(formula, None::); + parse_formula::parse_string_to_formula("={1,2,3}+{1,2,3}", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula("={0,0}+{1,2,3}", None::); - let result = calculate::calculate_formula(formula, None::); + parse_formula::parse_string_to_formula("={0,0}+{1,2,3}", None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); // error (#ARG!) let start: DateTime = DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z")?; @@ -121,73 +142,75 @@ fn main() -> Result<(), ParseError> { }; let formula = - parse_formula::parse_string_to_formula("=DAYS(end, start)", None::); + parse_formula::parse_string_to_formula("=DAYS(end, start)", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=start+1", None::); + let formula = parse_formula::parse_string_to_formula("=start+1", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = parse_formula::parse_string_to_formula("=end-3", None::); + let formula = parse_formula::parse_string_to_formula("=end-3", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let custom_functions = |s: String, params: Vec| match s.as_str() { - "Increase" => types::Value::Number(params[0] + 1.0), + let custom_functions = |s: String, params: Vec| match s.as_str() { + "Increase" => types::Value::Number(params[0] + N::one()), "SimpleSum" => types::Value::Number(params[0] + params[1]), - "EqualFive" => types::Value::Number(5.0), + "EqualFive" => types::Value::Number(N::from_f32(5.0).unwrap()), _ => types::Value::Error(types::Error::Value), }; let formula = parse_formula::parse_string_to_formula("=Increase(1)+1", Some(&custom_functions)); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula("=EqualFive()+1", Some(&custom_functions)); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula("=SimpleSum(1,2)", Some(&custom_functions)); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); - let custom_function = |s: String, _params: Vec| match s.as_str() { - "EqualFive" => types::Value::Number(5.0), + let custom_function = |s: String, _params: Vec| match s.as_str() { + "EqualFive" => types::Value::Number(N::from_f32(5.0).unwrap()), _ => types::Value::Error(types::Error::Value), }; let formula = parse_formula::parse_string_to_formula("=EqualFive()", Some(&custom_function)); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); ///////////// RIGHT function - let formula = - parse_formula::parse_string_to_formula(r#"=RIGHT("apple", 3)"#, None::); - let result = calculate::calculate_formula(formula, None::); + let formula = parse_formula::parse_string_to_formula( + r#"=RIGHT("apple", 3)"#, + None::>, + ); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(r#"=RIGHT("apple")"#, None::); - let result = calculate::calculate_formula(formula, None::); + parse_formula::parse_string_to_formula(r#"=RIGHT("apple")"#, None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula( r#"="P"&RIGHT("000"&1,3)"#, - None::, + None::>, ); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); ///////////// LEFT function let formula = - parse_formula::parse_string_to_formula(r#"=LEFT("apple", 3)"#, None::); - let result = calculate::calculate_formula(formula, None::); + parse_formula::parse_string_to_formula(r#"=LEFT("apple", 3)"#, None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula(r#"=LEFT("apple")"#, None::); - let result = calculate::calculate_formula(formula, None::); + parse_formula::parse_string_to_formula(r#"=LEFT("apple")"#, None::>); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); ///////////// Handle blank in calculation @@ -196,19 +219,19 @@ fn main() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; - let custom_functions = |s: String, params: Vec| match s.as_str() { - "Increase" => types::Value::Number(params[0] + 1.0), + let custom_functions = |s: String, params: Vec| match s.as_str() { + "Increase" => types::Value::Number(params[0] + N::one()), "BLANK" => types::Value::Blank, _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula("=SUM(B, 1)", None::); + let formula = parse_formula::parse_string_to_formula("=SUM(B, 1)", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); let formula = parse_formula::parse_string_to_formula("=SUM(BLANK(), 1)", Some(&custom_functions)); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); println!("Result is {}", calculate::result_to_string(result)); let data_function = |s: String| match s.as_str() { @@ -218,17 +241,18 @@ fn main() -> Result<(), ParseError> { _ => types::Value::Error(types::Error::Value), }; - let formula = parse_formula::parse_string_to_formula("=OR({F,B})", None::); + let formula = parse_formula::parse_string_to_formula("=OR({F,B})", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); let formula = - parse_formula::parse_string_to_formula("=SUM(1, 2, , 3)", None::); + parse_formula::parse_string_to_formula("=SUM(1, 2, , 3)", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); ///////////// IF function - let formula = parse_formula::parse_string_to_formula("=IF(TRUE,1,0)", None::); + let formula = + parse_formula::parse_string_to_formula("=IF(TRUE,1,0)", None::>); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); @@ -240,7 +264,7 @@ fn main() -> Result<(), ParseError> { let formula = parse_formula::parse_string_to_formula( r#"=IF(ReferenceKey="10","",ReferenceKey&" - ")&ReferenceName"#, - None::, + None::>, ); let result = calculate::calculate_formula(formula, Some(&data_function)); @@ -252,13 +276,17 @@ fn main() -> Result<(), ParseError> { "ReferenceName" => types::Value::Text("Test".to_string()), _ => types::Value::Error(types::Error::Value), }; - let formula = - parse_formula::parse_string_to_formula("=ISBLANK(ReferenceKey)", None::); + let formula = parse_formula::parse_string_to_formula( + "=ISBLANK(ReferenceKey)", + None::>, + ); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); - let formula = - parse_formula::parse_string_to_formula("=ISBLANK(ReferenceName)", None::); + let formula = parse_formula::parse_string_to_formula( + "=ISBLANK(ReferenceName)", + None::>, + ); let result = calculate::calculate_formula(formula, Some(&data_function)); println!("Result is {}", calculate::result_to_string(result)); diff --git a/src/parse_formula.rs b/src/parse_formula.rs index 9e419a8..afd446e 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -3,13 +3,15 @@ use pest::{ pratt_parser::{Assoc, Op, PrattParser}, Parser, }; +use pest_derive::Parser; +use std::{fmt::Debug, str::FromStr}; #[derive(Parser)] #[grammar = "grammar.pest"] pub struct GrammarParser; /// Use this function to catch a parse error. -fn parse_string(s: &str) -> Option> { +fn parse_string(s: &'_ str) -> Option> { let parse_result = GrammarParser::parse(Rule::formula, s); //println!("{:#?}", parse_result); match parse_result { @@ -24,7 +26,11 @@ fn parse_string(s: &str) -> Option> { // .next() } -fn parse_string_constant(parse_result: pest::iterators::Pair) -> types::Formula { +fn parse_string_constant(parse_result: pest::iterators::Pair) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let string = parse_result .into_inner() .as_str() @@ -36,10 +42,14 @@ fn parse_string_constant(parse_result: pest::iterators::Pair) -> types::Fo } /// Parses a string and stores it in Formula Enum. -pub fn parse_string_to_formula( +pub fn parse_string_to_formula( s: &str, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ match parse_string(s) { Some(parse_result) => match parse_result.as_rule() { Rule::expr => build_formula_with_parser(parse_result.into_inner(), f), @@ -50,25 +60,42 @@ pub fn parse_string_to_formula( } } -fn build_formula_number(pair: pest::iterators::Pair) -> types::Formula { - let x = pair.as_str().parse::().unwrap(); +fn build_formula_number(pair: pest::iterators::Pair) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ + let s = pair.as_str(); + let x = s.parse::().unwrap(); let value = types::Value::Number(x); types::Formula::Value(value) } -fn build_formula_string_double_quote(pair: pest::iterators::Pair) -> types::Formula { +fn build_formula_string_double_quote(pair: pest::iterators::Pair) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let string = pair.into_inner().as_str().parse::().unwrap(); let value = types::Value::Text(string.replace("\"\"", "\"")); types::Formula::Value(value) } -fn build_formula_string_single_quote(pair: pest::iterators::Pair) -> types::Formula { +fn build_formula_string_single_quote(pair: pest::iterators::Pair) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let string = pair.into_inner().as_str().parse::().unwrap(); let value = types::Value::Text(string); types::Formula::Value(value) } -fn build_formula_boolean(boolean_value: bool) -> types::Formula { +fn build_formula_boolean(boolean_value: bool) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ if boolean_value { types::Formula::Value(types::Value::Boolean(types::Boolean::True)) } else { @@ -76,11 +103,15 @@ fn build_formula_boolean(boolean_value: bool) -> types::Formula { } } -fn build_formula_unary_operator( +fn build_formula_unary_operator( unary_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let op_type = match unary_operation { Rule::abs => types::Operator::Function(types::Function::Abs), Rule::not => types::Operator::Function(types::Function::Not), @@ -94,15 +125,23 @@ fn build_formula_unary_operator( types::Formula::Operation(operation) } -fn build_formula_reference(pair: pest::iterators::Pair) -> types::Formula { +fn build_formula_reference(pair: pest::iterators::Pair) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let string = pair.as_str().parse::().unwrap(); types::Formula::Reference(string) } -fn build_formula_iterator( +fn build_formula_iterator( pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let mut vec = Vec::new(); for term in pair.into_inner() { vec.push(build_formula_with_parser(term.into_inner(), f)); @@ -110,11 +149,15 @@ fn build_formula_iterator( types::Formula::Iterator(vec) } -fn build_formula_collective_operator( +fn build_formula_collective_operator( collective_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let mut vec = Vec::new(); for term in pair.into_inner() { if (term.as_str().parse::().unwrap() == "") @@ -152,11 +195,15 @@ fn rule_to_function_operator(collective_operation: Rule) -> types::Operator { } } -fn build_formula_collective_operator_average( +fn build_formula_collective_operator_average( collective_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let mut vec = Vec::new(); for term in pair.into_inner() { if (term.as_str().parse::().unwrap() == "") @@ -164,7 +211,7 @@ fn build_formula_collective_operator_average( | (term.as_str().parse::().unwrap() == ", ") | (term.as_str().parse::().unwrap() == " ,") { - vec.push(types::Formula::Value(types::Value::Number(0.0))) + vec.push(types::Formula::Value(types::Value::Number(N::zero()))) } else { vec.push(build_formula_with_parser(term.into_inner(), f)) } @@ -177,11 +224,15 @@ fn build_formula_collective_operator_average( types::Formula::Operation(operation) } -fn build_formula_collective_operator_and( +fn build_formula_collective_operator_and( collective_operation: Rule, pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let mut vec = Vec::new(); for term in pair.into_inner() { if (term.as_str().parse::().unwrap() == "") @@ -204,10 +255,14 @@ fn build_formula_collective_operator_and( types::Formula::Operation(operation) } -fn build_formula_iff( +fn build_formula_iff( pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let mut vec = Vec::new(); for term in pair.into_inner() { if (term.as_str().parse::().unwrap() == "") @@ -227,15 +282,19 @@ fn build_formula_iff( types::Formula::Operation(operation) } -fn build_formula_custom_function( +fn build_formula_custom_function( pair: pest::iterators::Pair, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let mut vec = Vec::new(); for field in pair.clone().into_inner() { if field.as_rule() == Rule::expr { let x = field.into_inner().as_str(); - let y = x.parse::(); + let y = x.parse::(); if let Ok(y) = y { vec.push(y); } @@ -265,11 +324,15 @@ fn build_formula_custom_function( } } -fn build_formula_binary_operator( +fn build_formula_binary_operator( binary_operator: Rule, - lhs: types::Formula, - rhs: types::Formula, -) -> types::Formula { + lhs: types::Formula, + rhs: types::Formula, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let op_type = match binary_operator { Rule::add => types::Operator::Plus, Rule::subtract => types::Operator::Minus, @@ -292,10 +355,14 @@ fn build_formula_binary_operator( types::Formula::Operation(operation) } -fn build_formula_with_parser( +fn build_formula_with_parser( expression: pest::iterators::Pairs, - f: Option<&impl Fn(String, Vec) -> types::Value>, -) -> types::Formula { + f: Option<&impl Fn(String, Vec) -> types::Value>, +) -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ let pratt = PrattParser::new() .op(Op::infix(Rule::concat, Assoc::Left)) .op(Op::infix(Rule::equal, Assoc::Left) | Op::infix(Rule::not_equal, Assoc::Left)) @@ -334,24 +401,26 @@ fn build_formula_with_parser( _ => unreachable!(), }) .map_infix( - |lhs: types::Formula, op: pest::iterators::Pair, rhs: types::Formula| match op - .as_rule() - { - Rule::add => build_formula_binary_operator(Rule::add, lhs, rhs), - Rule::subtract => build_formula_binary_operator(Rule::subtract, lhs, rhs), - Rule::multiply => build_formula_binary_operator(Rule::multiply, lhs, rhs), - Rule::divide => build_formula_binary_operator(Rule::divide, lhs, rhs), - Rule::power => build_formula_binary_operator(Rule::power, lhs, rhs), - Rule::concat => build_formula_binary_operator(Rule::concat, lhs, rhs), - Rule::equal => build_formula_binary_operator(Rule::equal, lhs, rhs), - Rule::not_equal => build_formula_binary_operator(Rule::not_equal, lhs, rhs), - Rule::greater => build_formula_binary_operator(Rule::greater, lhs, rhs), - Rule::less => build_formula_binary_operator(Rule::less, lhs, rhs), - Rule::greater_or_equal => { - build_formula_binary_operator(Rule::greater_or_equal, lhs, rhs) + |lhs: types::Formula, op: pest::iterators::Pair, rhs: types::Formula| { + match op.as_rule() { + Rule::add => build_formula_binary_operator(Rule::add, lhs, rhs), + Rule::subtract => build_formula_binary_operator(Rule::subtract, lhs, rhs), + Rule::multiply => build_formula_binary_operator(Rule::multiply, lhs, rhs), + Rule::divide => build_formula_binary_operator(Rule::divide, lhs, rhs), + Rule::power => build_formula_binary_operator(Rule::power, lhs, rhs), + Rule::concat => build_formula_binary_operator(Rule::concat, lhs, rhs), + Rule::equal => build_formula_binary_operator(Rule::equal, lhs, rhs), + Rule::not_equal => build_formula_binary_operator(Rule::not_equal, lhs, rhs), + Rule::greater => build_formula_binary_operator(Rule::greater, lhs, rhs), + Rule::less => build_formula_binary_operator(Rule::less, lhs, rhs), + Rule::greater_or_equal => { + build_formula_binary_operator(Rule::greater_or_equal, lhs, rhs) + } + Rule::less_or_equal => { + build_formula_binary_operator(Rule::less_or_equal, lhs, rhs) + } + _ => unreachable!(), } - Rule::less_or_equal => build_formula_binary_operator(Rule::less_or_equal, lhs, rhs), - _ => unreachable!(), }, ) .parse(expression) diff --git a/src/types.rs b/src/types.rs index a321fdf..74f8326 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,9 @@ use chrono::{DateTime, FixedOffset}; +use num_traits::{AsPrimitive, Float, FromPrimitive}; +use std::{ + fmt::{Debug, Display}, + str::FromStr, +}; /// Defines Excel Functions. #[derive(Debug, Copy, Clone)] pub enum Function { @@ -54,15 +59,28 @@ pub enum Boolean { False, } -#[cfg(feature = "f32")] -pub type XlNum = f32; -#[cfg(feature = "f64")] -pub type XlNum = f64; +pub trait XlNum: + Float + + AsPrimitive + + AsPrimitive + + AsPrimitive + + FromPrimitive + + FromStr + + Debug + + Display +{ +} + +impl XlNum for f32 {} +impl XlNum for f64 {} /// The result of an evaluation. #[derive(Debug, Clone, PartialEq)] -pub enum Value { - Number(XlNum), +pub enum Value +where + N: XlNum, +{ + Number(N), Text(String), Boolean(Boolean), Iterator(Vec), @@ -73,16 +91,22 @@ pub enum Value { /// Defines each term in Expression Struct. #[derive(Debug, Clone)] -pub enum Formula { - Operation(Expression), - Value(Value), +pub enum Formula +where + N: XlNum, +{ + Operation(Expression), + Value(Value), Reference(String), Iterator(Vec), } /// Struct that holds a parsed string. Formula enum and Expression Struct are defined recursively. #[derive(Debug, Clone)] -pub struct Expression { +pub struct Expression +where + N: XlNum, +{ pub op: Operator, - pub values: Vec, + pub values: Vec>, } diff --git a/tests/test.rs b/tests/test.rs index 3120e1d..0d0f27f 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,1572 +1,1997 @@ -use chrono::{format::ParseError, DateTime, Duration, FixedOffset}; +use chrono::{DateTime, Duration, FixedOffset}; +use std::{fmt::Debug, str::FromStr}; use xlformula_engine::{ calculate, parse_formula, types::{self, XlNum}, NoCustomFunction, NoReference, }; -fn evaluate_formula_number(s: &str) -> XlNum { - let formula = parse_formula::parse_string_to_formula(s, None::); - let result = calculate::calculate_formula(formula, None::); +fn evaluate_formula_number(s: &str) -> N +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); + let result = calculate::calculate_formula(formula, None::>); + calculate::result_to_string(result).parse::().unwrap() +} + +fn evaluate_formula_string(s: &str) -> String +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); + let result = calculate::calculate_formula(formula, None::>); calculate::result_to_string(result) - .parse::() - .unwrap() } -fn evaluate_formula_string(s: &str) -> String { - let formula = parse_formula::parse_string_to_formula(s, None::); - let result = calculate::calculate_formula(formula, None::); - calculate::result_to_string(result) -} - -fn evaluate_formula_string_with_reference( +fn evaluate_formula_string_with_reference( s: &str, - f: Option<&impl Fn(String) -> types::Value>, -) -> String { - let formula = parse_formula::parse_string_to_formula(s, None::); + f: Option<&impl Fn(String) -> types::Value>, +) -> String +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); let result = calculate::calculate_formula(formula, f); calculate::result_to_string(result) } -fn evaluate_formula_number_with_reference( +fn evaluate_formula_number_with_reference( s: &str, - f: Option<&impl Fn(String) -> types::Value>, -) -> XlNum { - let formula = parse_formula::parse_string_to_formula(s, None::); + f: Option<&impl Fn(String) -> types::Value>, +) -> N +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); let result = calculate::calculate_formula(formula, f); - calculate::result_to_string(result) - .parse::() - .unwrap() + calculate::result_to_string(result).parse::().unwrap() } -fn evaluate_formula_number_with_reference_no_conversion( +fn evaluate_formula_number_with_reference_no_conversion( s: &str, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value { - let formula = parse_formula::parse_string_to_formula(s, None::); + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); calculate::calculate_formula(formula, f) //calculate::result_to_string(result).parse::().unwrap() } -fn evaluate_formula_boolean_with_reference( +fn evaluate_formula_boolean_with_reference( s: &str, - f: Option<&impl Fn(String) -> types::Value>, -) -> String { - let formula = parse_formula::parse_string_to_formula(s, None::); + f: Option<&impl Fn(String) -> types::Value>, +) -> String +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); let result = calculate::calculate_formula(formula, f); calculate::result_to_string(result) //.parse::().unwrap() } -fn evaluate_formula_date_with_reference( +fn evaluate_formula_date_with_reference( s: &str, - f: Option<&impl Fn(String) -> types::Value>, -) -> String { - let formula = parse_formula::parse_string_to_formula(s, None::); + f: Option<&impl Fn(String) -> types::Value>, +) -> String +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); let result = calculate::calculate_formula(formula, f); calculate::result_to_string(result) } -fn evaluate_formula_number_with_custom_function( +fn evaluate_formula_number_with_custom_function( s: &str, - custom_function: Option<&impl Fn(String, Vec) -> types::Value>, + custom_function: Option<&impl Fn(String, Vec) -> types::Value>, //reference: Option<&impl Fn(String) -> types::Value>, -) -> XlNum { +) -> N +where + N: XlNum, + ::Err: Debug, +{ let formula = parse_formula::parse_string_to_formula(s, custom_function); - let result = calculate::calculate_formula(formula, None::); - calculate::result_to_string(result) - .parse::() - .unwrap() + let result = calculate::calculate_formula(formula, None::>); + calculate::result_to_string(result).parse::().unwrap() } -fn evaluate_formula_string_with_custom_function( +fn evaluate_formula_string_with_custom_function( s: &str, - custom_function: Option<&impl Fn(String, Vec) -> types::Value>, + custom_function: Option<&impl Fn(String, Vec) -> types::Value>, //reference: Option<&impl Fn(String) -> types::Value>, -) -> String { +) -> String +where + N: XlNum, + ::Err: Debug, +{ let formula = parse_formula::parse_string_to_formula(s, custom_function); - let result = calculate::calculate_formula(formula, None::); + let result = calculate::calculate_formula(formula, None::>); calculate::result_to_string(result) } -fn _evaluate_formula_number_with_custom_function_and_reference( +fn _evaluate_formula_number_with_custom_function_and_reference( s: &str, - custom_function: Option<&impl Fn(String, Vec) -> types::Value>, - reference: Option<&impl Fn(String) -> types::Value>, -) -> XlNum { + custom_function: Option<&impl Fn(String, Vec) -> types::Value>, + reference: Option<&impl Fn(String) -> types::Value>, +) -> N +where + N: XlNum, + ::Err: Debug, +{ let formula = parse_formula::parse_string_to_formula(s, custom_function); let result = calculate::calculate_formula(formula, reference); - calculate::result_to_string(result) - .parse::() - .unwrap() + calculate::result_to_string(result).parse::().unwrap() +} + +macro_rules! test_all_types { + ($func:ident, $formula:expr, $result:expr) => { + assert_eq!($func::($formula), $result); + assert_eq!($func::($formula), $result); + }; +} + +macro_rules! test_all_types_with_data { + ($func:ident, $formula:expr, $data:expr, $result:expr) => { + assert_eq!($func::($formula, $data), $result); + assert_eq!($func::($formula, $data), $result); + }; } /////////////////// Simple math operators with floats and integer /////////////////// #[test] fn it_evaluate_add_operator_simple_addition() { - assert_eq!(evaluate_formula_number("=1+2"), 3.0,); + test_all_types!(evaluate_formula_number, "=1+2", 3.0); } #[test] fn it_evaluate_add_operator_spaces_between_operators() { - assert_eq!(evaluate_formula_number("=1 + 2"), 3.0,); + test_all_types!(evaluate_formula_number, "=1 + 2", 3.0); } #[test] fn it_evaluate_add_operator_spaces_before_number() { - assert_eq!(evaluate_formula_number("= 1+2"), 3.0,); + test_all_types!(evaluate_formula_number, "= 1+2", 3.0); } #[test] fn it_evaluate_add_operator_with_large_numbers() { - assert_eq!( - evaluate_formula_number("=1234567890 + 1234567890"), + test_all_types!( + evaluate_formula_number, + "=1234567890 + 1234567890", 2469135780.0 ); } #[test] fn it_evaluate_add_operator_with_negative_numbers() { - assert_eq!(evaluate_formula_number("=-1 + -2"), -3.0); + test_all_types!(evaluate_formula_number, "=-1 + -2", -3.0); } #[test] fn it_evaluate_minus_operator1() { - assert_eq!(evaluate_formula_number("=123 - 23"), 100.0,); + test_all_types!(evaluate_formula_number, "=123 - 23", 100.0); } #[test] fn it_evaluate_minus_operator_with_negative_numbers() { - assert_eq!(evaluate_formula_number("=-12--6"), -6.0,); + test_all_types!(evaluate_formula_number, "=-12--6", -6.0); } #[test] fn it_evaluate_multiply_operator() { - assert_eq!(evaluate_formula_number("=3 * 2"), 6.0,); + test_all_types!(evaluate_formula_number, "=3 * 2", 6.0); } #[test] fn it_evaluate_divide_operator() { - assert_eq!(evaluate_formula_number("=6 / 3"), 2.0,); + test_all_types!(evaluate_formula_number, "=6 / 3", 2.0); } #[test] fn it_evaluate_divide_operator_divsion_by_zero() { - assert_eq!(evaluate_formula_string("=6 / 0"), "#DIV/0!"); + test_all_types!(evaluate_formula_string, "=6 / 0", "#DIV/0!"); } #[test] fn it_evaluate_negative() { - assert_eq!(evaluate_formula_number("=-1 * -5"), 5.0,); + test_all_types!(evaluate_formula_number, "=-1 * -5", 5.0); } #[test] fn it_evaluate_power_int() { - assert_eq!(evaluate_formula_number("=2^3"), 8.0,); + test_all_types!(evaluate_formula_number, "=2^3", 8.0); } #[test] fn it_evaluate_float() { - assert_eq!(evaluate_formula_number("=1.2+0.5"), 1.7,); + test_all_types!(evaluate_formula_number, "=1.2+0.5", 1.7); } #[test] -#[cfg(feature = "f32")] -fn it_evaluate_negative_float_f32() { +fn it_evaluate_negative_float() { // left: `-0.70000005`, right: `-0.7`' - assert_approx_eq::assert_approx_eq!(evaluate_formula_number("=-1.2+0.5"), -0.7); -} - -#[test] -#[cfg(feature = "f64")] -fn it_evaluate_negative_float_f64() { - assert_eq!(evaluate_formula_number("=-1.2+0.5"), -0.7); + assert_approx_eq::assert_approx_eq!(evaluate_formula_number::("=-1.2+0.5"), -0.7); + assert_eq!(evaluate_formula_number::("=-1.2+0.5"), -0.7); } #[test] fn it_evaluate_power_float() { - assert_eq!(evaluate_formula_number("=4^0.5"), 2.0,); + test_all_types!(evaluate_formula_number, "=4^0.5", 2.0); } #[test] fn it_evaluate_multiple_operations() { - assert_eq!(evaluate_formula_number("=1+2+3"), 6.0,); + test_all_types!(evaluate_formula_number, "=1+2+3", 6.0); } #[test] fn it_evaluate_multiple_operations2() { - assert_eq!(evaluate_formula_number("=1+2-3"), 0.0,); + test_all_types!(evaluate_formula_number, "=1+2-3", 0.0); } #[test] fn it_evaluate_multiple_operations_in_right_order() { - assert_eq!(evaluate_formula_number("=1+2*3"), 7.0,); + test_all_types!(evaluate_formula_number, "=1+2*3", 7.0); } #[test] fn it_evaluate_multiple_operations_in_right_order2() { - assert_eq!(evaluate_formula_number("=1+3/3"), 2.0,); + test_all_types!(evaluate_formula_number, "=1+3/3", 2.0); } #[test] fn it_evaluate_multiple_operations_with_errors() { - assert_eq!(evaluate_formula_string("=1+3/0"), "#DIV/0!",); + test_all_types!(evaluate_formula_string, "=1+3/0", "#DIV/0!"); } #[test] fn it_evaluate_parens() { - assert_eq!(evaluate_formula_number("=(1+2)"), 3.0,); + test_all_types!(evaluate_formula_number, "=(1+2)", 3.0); } #[test] fn it_evaluate_multiple_parens() { - assert_eq!(evaluate_formula_number("=(1+2)+(3+4)"), 10.0,); + test_all_types!(evaluate_formula_number, "=(1+2)+(3+4)", 10.0); } #[test] fn it_evaluate_nested_parens() { - assert_eq!(evaluate_formula_number("=(1*(2+3))*2"), 10.0,); + test_all_types!(evaluate_formula_number, "=(1*(2+3))*2", 10.0); } /////////////////// Strings /////////////////// #[test] fn it_evaluate_strings() { - assert_eq!(evaluate_formula_string("=\"Hello! \""), "Hello! ",); + test_all_types!(evaluate_formula_string, "=\"Hello! \"", "Hello! "); } #[test] fn it_evaluate_strings_in_numeric_operator() { - assert_eq!(evaluate_formula_string("=\"Hello\"+1"), "#CAST!",); + test_all_types!(evaluate_formula_string, "=\"Hello\"+1", "#CAST!"); } #[test] fn it_evaluate_strings_in_numeric_operator2() { - assert_eq!(evaluate_formula_string("=1 + \"Hello\""), "#CAST!",); + test_all_types!(evaluate_formula_string, "=1 + \"Hello\"", "#CAST!"); } #[test] fn it_evaluate_concat_operator1() { - assert_eq!( - evaluate_formula_string("=\"Hello\" & \"World!\""), - "HelloWorld!", + test_all_types!( + evaluate_formula_string, + "=\"Hello\" & \"World!\"", + "HelloWorld!" ); } #[test] fn it_evaluate_concat_operator3() { - assert_eq!( - evaluate_formula_string("=\"Hello \" & \" World!\""), - "Hello World!", + test_all_types!( + evaluate_formula_string, + "=\"Hello \" & \" World!\"", + "Hello World!" ); } #[test] fn it_evaluate_concat_operator_with_casting() { - assert_eq!(evaluate_formula_string("=\"Hello\"&1"), "Hello1",); + test_all_types!(evaluate_formula_string, "=\"Hello\"&1", "Hello1"); } #[test] fn it_evaluate_concat_operator_with_casting2() { - assert_eq!(evaluate_formula_string("=\"Hello \"&1.2"), "Hello 1.2",); + test_all_types!(evaluate_formula_string, "=\"Hello \"&1.2", "Hello 1.2"); } #[test] fn it_evaluate_concat_operator_with_numberic() { - assert_eq!(evaluate_formula_string("=1 & 2"), "12",); + test_all_types!(evaluate_formula_string, "=1 & 2", "12"); } #[test] fn it_evaluate_strings_with_quoted_quotes1() { - assert_eq!( - evaluate_formula_string("=\"Hello 'World'\""), - "Hello 'World'", + test_all_types!( + evaluate_formula_string, + "=\"Hello 'World'\"", + "Hello 'World'" ); } #[test] fn it_evaluate_strings_with_quoted_quotes() { - assert_eq!( - evaluate_formula_string("=\"Hello \"\"World\"\"\""), - "Hello \"World\"", + test_all_types!( + evaluate_formula_string, + "=\"Hello \"\"World\"\"\"", + "Hello \"World\"" ); } #[test] fn it_evaluate_strings_with_single_quotes() { - assert_eq!( - evaluate_formula_string("=\"Hello \"&'World'"), - "Hello World", + test_all_types!( + evaluate_formula_string, + "=\"Hello \"&'World'", + "Hello World" ); } #[test] fn it_evaluate_strings_with_quotes() { - assert_eq!( - evaluate_formula_string("='Hello \"World\"'"), - "Hello \"World\"", + test_all_types!( + evaluate_formula_string, + "='Hello \"World\"'", + "Hello \"World\"" ); } #[test] fn it_evaluate_strings_with_quotes2() { - assert_eq!( - evaluate_formula_string("='Hello'"), // '& 'World' - "Hello", + test_all_types!( + evaluate_formula_string, + "='Hello'", // '& 'World' + "Hello" ); } /////////////////// Constants /////////////////// #[test] fn it_evaluate_constant_number() { - assert_eq!(evaluate_formula_number("1"), 1.0,); + test_all_types!(evaluate_formula_number, "1", 1.0); } #[test] fn it_evaluate_constant_number_float() { - assert_eq!(evaluate_formula_number("1.2"), 1.2,); + test_all_types!(evaluate_formula_number, "1.2", 1.2); } #[test] fn it_evaluate_constant_text() { - assert_eq!(evaluate_formula_string("Hello World"), "Hello World",); + test_all_types!(evaluate_formula_string, "Hello World", "Hello World"); } #[test] fn it_evaluate_constant_text_with_quotes() { - assert_eq!(evaluate_formula_string("Hello \"World'"), "Hello \"World'",); + test_all_types!(evaluate_formula_string, "Hello \"World'", "Hello \"World'"); } #[test] fn it_evaluate_constant_starting_with_equal() { - assert_eq!(evaluate_formula_string("'="), "=",); - assert_eq!(evaluate_formula_string("'=hello"), "=hello",); + test_all_types!(evaluate_formula_string, "'=", "="); + test_all_types!(evaluate_formula_string, "'=hello", "=hello"); } /////////////////// Formulas /////////////////// #[test] fn it_support_basic_math_function() { - assert_eq!(evaluate_formula_number("=ABS(-1)"), 1.0,); + test_all_types!(evaluate_formula_number, "=ABS(-1)", 1.0); } #[test] fn it_support_basic_math_function_with_nested_formulas() { - assert_eq!(evaluate_formula_number("=ABS(-1-4)"), 5.0,); + test_all_types!(evaluate_formula_number, "=ABS(-1-4)", 5.0); } #[test] fn it_support_basic_math_function_with_nested_functions() { - assert_eq!(evaluate_formula_number("=ABS(ABS(-1))"), 1.0,); + test_all_types!(evaluate_formula_number, "=ABS(ABS(-1))", 1.0); } #[test] fn it_evaluate_functions_sum() { - assert_eq!( - evaluate_formula_number("=SUM(1*1, ABS(2), ABS(2+1), 4)"), - 10.0, + test_all_types!( + evaluate_formula_number, + "=SUM(1*1, ABS(2), ABS(2+1), 4)", + 10.0 ); - assert_eq!(evaluate_formula_number("=SUM(1, 2, , 3)"), 6.0,); - assert_eq!(evaluate_formula_number("=SUM( 1 , 2,,3,)"), 6.0,); - assert_eq!(evaluate_formula_number("=SUM( 1 , 2,,,3,)"), 6.0,); - assert_eq!(evaluate_formula_number("=SUM(,)"), 0.0,); + test_all_types!(evaluate_formula_number, "=SUM(1, 2, , 3)", 6.0); + test_all_types!(evaluate_formula_number, "=SUM( 1 , 2,,3,)", 6.0); + test_all_types!(evaluate_formula_number, "=SUM( 1 , 2,,,3,)", 6.0); + test_all_types!(evaluate_formula_number, "=SUM(,)", 0.0); } #[test] fn it_evaluate_functions_avg() { - assert_eq!(evaluate_formula_number("=AVERAGE(1,2)"), 1.5,); - assert_eq!(evaluate_formula_number("=AVERAGE({1,2,3})"), 2.0,); - assert_eq!(evaluate_formula_number("=AVERAGE({1,2,3},1,2,3)"), 2.0,); - assert_eq!(evaluate_formula_number("=AVERAGE(3,1,2,3)"), 2.25,); - assert_eq!(evaluate_formula_number("=AVERAGE(1,2,3,4,5,1,2,3)"), 2.625,); - assert_eq!( - evaluate_formula_number("=AVERAGE({1,2,3,4,5},1,2,3)"), - 2.625, - ); - assert_eq!( - evaluate_formula_number("=AVERAGE(AVERAGE({1,2,3,4,5}),1,2,3)"), - 2.25, - ); - assert_eq!(evaluate_formula_number("=AVERAGE({100,200})"), 150.0,); - assert_eq!(evaluate_formula_number("=AVERAGE( 1 , 2,,3)"), 1.5,); - assert_eq!(evaluate_formula_number("=AVERAGE( 1 , )"), 0.5,); - assert_eq!(evaluate_formula_number("=AVERAGE(,)"), 0.0,); - assert_eq!(evaluate_formula_number("=AVERAGE(1,,2,3,)",), 1.2); - //assert_eq!(evaluate_formula_number(&"=AVERAGE({{100,200}})"), 150.0); + test_all_types!(evaluate_formula_number, "=AVERAGE(1,2)", 1.5); + test_all_types!(evaluate_formula_number, "=AVERAGE({1,2,3})", 2.0); + test_all_types!(evaluate_formula_number, "=AVERAGE({1,2,3},1,2,3)", 2.0); + test_all_types!(evaluate_formula_number, "=AVERAGE(3,1,2,3)", 2.25); + test_all_types!(evaluate_formula_number, "=AVERAGE(1,2,3,4,5,1,2,3)", 2.625); + test_all_types!( + evaluate_formula_number, + "=AVERAGE({1,2,3,4,5},1,2,3)", + 2.625 + ); + test_all_types!( + evaluate_formula_number, + "=AVERAGE(AVERAGE({1,2,3,4,5}),1,2,3)", + 2.25 + ); + test_all_types!(evaluate_formula_number, "=AVERAGE({100,200})", 150.0); + test_all_types!(evaluate_formula_number, "=AVERAGE( 1 , 2,,3)", 1.5); + test_all_types!(evaluate_formula_number, "=AVERAGE( 1 , )", 0.5); + test_all_types!(evaluate_formula_number, "=AVERAGE(,)", 0.0); + test_all_types!(evaluate_formula_number, "=AVERAGE(1,,2,3,)", 1.2); + // test_all_types!(evaluate_formula_number, "=AVERAGE({{100,200}})", 150.0); } #[test] fn it_evaluate_functions_product() { - assert_eq!(evaluate_formula_number("=PRODUCT(ABS(1),2*1, 3,4*1)"), 24.0,); - assert_eq!(evaluate_formula_number("=PRODUCT( 1 , 2,,3)"), 6.0,); - assert_eq!(evaluate_formula_number("=PRODUCT(,)"), 0.0,); + test_all_types!(evaluate_formula_number, "=PRODUCT(ABS(1),2*1, 3,4*1)", 24.0); + test_all_types!(evaluate_formula_number, "=PRODUCT( 1 , 2,,3)", 6.0); + test_all_types!(evaluate_formula_number, "=PRODUCT(,)", 0.0); } #[test] fn it_evaluate_operators_with_casting() { - assert_eq!(evaluate_formula_number("=\"1\"+2+\"3\""), 6.0,); + test_all_types!(evaluate_formula_number, "=\"1\"+2+\"3\"", 6.0); } #[test] fn it_evaluate_functions_with_casting() { - assert_eq!(evaluate_formula_number("=SUM(1,2,\"3\")"), 6.0,); + test_all_types!(evaluate_formula_number, "=SUM(1,2,\"3\")", 6.0); } /////////////////////// Parse error ////////////////////////////////// #[test] fn it_evaluate_wrong_parens1() { - assert_eq!(evaluate_formula_string("=(2+3"), "#PARSE!",); - assert_eq!(evaluate_formula_string("=\"Hello World"), "#PARSE!",); - assert_eq!(evaluate_formula_string("=Hello World"), "#PARSE!",); + test_all_types!(evaluate_formula_string, "=(2+3", "#PARSE!"); + test_all_types!(evaluate_formula_string, "=\"Hello World", "#PARSE!"); + test_all_types!(evaluate_formula_string, "=Hello World", "#PARSE!"); } //////////////////////////// Boolean ////////////////////////////////// #[test] fn it_evaluate_comparison_operators() { - assert_eq!(evaluate_formula_string("=1*1=1/1"), "TRUE",); - assert_eq!(evaluate_formula_string("=1^1<>1"), "FALSE",); - assert_eq!(evaluate_formula_string("=1*2>1"), "TRUE",); - assert_eq!(evaluate_formula_string("=1*1/1+2<1^1"), "FALSE",); - assert_eq!(evaluate_formula_string("=2>=1"), "TRUE",); - assert_eq!(evaluate_formula_string("=11<=3"), "FALSE",); - assert_eq!(evaluate_formula_string("=\"Joshu\"=\"Joshu\""), "TRUE",); - assert_eq!(evaluate_formula_string("=\"Joshu\"<>\"NotJoshu\""), "TRUE",); + test_all_types!(evaluate_formula_string, "=1*1=1/1", "TRUE"); + test_all_types!(evaluate_formula_string, "=1^1<>1", "FALSE"); + test_all_types!(evaluate_formula_string, "=1*2>1", "TRUE"); + test_all_types!(evaluate_formula_string, "=1*1/1+2<1^1", "FALSE"); + test_all_types!(evaluate_formula_string, "=2>=1", "TRUE"); + test_all_types!(evaluate_formula_string, "=11<=3", "FALSE"); + test_all_types!(evaluate_formula_string, "=\"Joshu\"=\"Joshu\"", "TRUE"); + test_all_types!(evaluate_formula_string, "=\"Joshu\"<>\"NotJoshu\"", "TRUE"); } #[test] fn it_evaluate_boolean_or() { - assert_eq!(evaluate_formula_string("=OR(1>1,1<>1)"), "FALSE",); - assert_eq!(evaluate_formula_string("=OR(1=1,2<=4)"), "TRUE",); - assert_eq!(evaluate_formula_string("=OR(\"True\")"), "TRUE",); - assert_eq!(evaluate_formula_string("=OR(True)"), "TRUE",); - assert_eq!(evaluate_formula_string("=OR(1)"), "TRUE",); - assert_eq!(evaluate_formula_string("=OR(\"test\")"), "#CAST!",); - assert_eq!( - evaluate_formula_string("=OR(\"false\",\"FALSE\", 1, FALSE)"), - "TRUE", + test_all_types!(evaluate_formula_string, "=OR(1>1,1<>1)", "FALSE"); + test_all_types!(evaluate_formula_string, "=OR(1=1,2<=4)", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR(\"True\")", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR(True)", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR(1)", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR(\"test\")", "#CAST!"); + test_all_types!( + evaluate_formula_string, + "=OR(\"false\",\"FALSE\", 1, FALSE)", + "TRUE" ); - assert_eq!( - evaluate_formula_string("=OR(\"True\",1,\"test\", true) "), - "TRUE", + test_all_types!( + evaluate_formula_string, + "=OR(\"True\",1,\"test\", true) ", + "TRUE" ); - assert_eq!(evaluate_formula_string("=OR(1, )"), "TRUE",); - assert_eq!(evaluate_formula_string("=OR(1,,, )"), "TRUE",); + test_all_types!(evaluate_formula_string, "=OR(1, )", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR(1,,, )", "TRUE"); } #[test] fn it_evaluate_boolean_and() { - assert_eq!(evaluate_formula_string("=AND(1>1,1=1)"), "FALSE",); - assert_eq!(evaluate_formula_string("=AND(1=1,2<=4)"), "TRUE",); - assert_eq!(evaluate_formula_string("=AND(\"true\", 0)"), "FALSE",); - assert_eq!(evaluate_formula_string("=AND(\"True\")"), "TRUE",); - assert_eq!(evaluate_formula_string("=AND(false)"), "FALSE",); - assert_eq!(evaluate_formula_string("=AND(1)"), "TRUE",); - assert_eq!( - evaluate_formula_string("=AND(\"test\", \"test\")"), - "#VALUE!", + test_all_types!(evaluate_formula_string, "=AND(1>1,1=1)", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND(1=1,2<=4)", "TRUE"); + test_all_types!(evaluate_formula_string, "=AND(\"true\", 0)", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND(\"True\")", "TRUE"); + test_all_types!(evaluate_formula_string, "=AND(false)", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND(1)", "TRUE"); + test_all_types!( + evaluate_formula_string, + "=AND(\"test\", \"test\")", + "#VALUE!" ); - assert_eq!( - evaluate_formula_string("=AND(\"test\",\"True\", 1, true) "), - "#VALUE!", + test_all_types!( + evaluate_formula_string, + "=AND(\"test\",\"True\", 1, true) ", + "#VALUE!" ); - assert_eq!( - evaluate_formula_string("=AND(\"True\",\"test\", 1, true) "), - "#VALUE!", + test_all_types!( + evaluate_formula_string, + "=AND(\"True\",\"test\", 1, true) ", + "#VALUE!" ); - assert_eq!( - evaluate_formula_string("=AND(\"True\", 1, true, \"test\")"), - "#VALUE!", + test_all_types!( + evaluate_formula_string, + "=AND(\"True\", 1, true, \"test\")", + "#VALUE!" ); - assert_eq!(evaluate_formula_string("=AND(1, )"), "FALSE",); - assert_eq!(evaluate_formula_string("=AND(1,,,1)"), "FALSE",); + test_all_types!(evaluate_formula_string, "=AND(1, )", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND(1,,,1)", "FALSE"); } #[test] fn it_evaluate_boolean_xor() { - assert_eq!(evaluate_formula_string("=XOR(2=2,1=1)"), "FALSE",); - assert_eq!(evaluate_formula_string("=XOR(1=1,2>4)"), "TRUE",); - assert_eq!(evaluate_formula_string("=XOR(\"True\")"), "TRUE",); - assert_eq!(evaluate_formula_string("=XOR(False)"), "FALSE",); - assert_eq!(evaluate_formula_string("=XOR(1)"), "TRUE",); - assert_eq!(evaluate_formula_string("=XOR(1=1,\"test\")"), "TRUE",); - assert_eq!( - evaluate_formula_string("=XOR(TRUE, TRUE, TRUE, TRUE)"), + test_all_types!(evaluate_formula_string, "=XOR(2=2,1=1)", "FALSE"); + test_all_types!(evaluate_formula_string, "=XOR(1=1,2>4)", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR(\"True\")", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR(False)", "FALSE"); + test_all_types!(evaluate_formula_string, "=XOR(1)", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR(1=1,\"test\")", "TRUE"); + test_all_types!( + evaluate_formula_string, + "=XOR(TRUE, TRUE, TRUE, TRUE)", "FALSE" ); - assert_eq!(evaluate_formula_string("=XOR(TRUE, TRUE)"), "FALSE"); - assert_eq!( - evaluate_formula_string("=XOR(false, FALSE, \"false\", false)"), + test_all_types!(evaluate_formula_string, "=XOR(TRUE, TRUE)", "FALSE"); + test_all_types!( + evaluate_formula_string, + "=XOR(false, FALSE, \"false\", false)", "FALSE" ); - assert_eq!(evaluate_formula_string("=XOR(true)"), "TRUE",); - assert_eq!(evaluate_formula_string("=XOR(false)"), "FALSE",); - assert_eq!(evaluate_formula_string("=XOR(1, )"), "TRUE",); - assert_eq!(evaluate_formula_string("=XOR(1,, )"), "TRUE",); - assert_eq!(evaluate_formula_string("=XOR(1,,1 )"), "FALSE",); - assert_eq!(evaluate_formula_string("=XOR(1,,1, )"), "FALSE",); + test_all_types!(evaluate_formula_string, "=XOR(true)", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR(false)", "FALSE"); + test_all_types!(evaluate_formula_string, "=XOR(1, )", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR(1,, )", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR(1,,1 )", "FALSE"); + test_all_types!(evaluate_formula_string, "=XOR(1,,1, )", "FALSE"); } #[test] fn it_evaluate_boolean_not() { - assert_eq!(evaluate_formula_string("=NOT(11<=3)"), "TRUE",); - assert_eq!(evaluate_formula_string("=NOT(1=1)"), "FALSE",); - assert_eq!(evaluate_formula_string("=NOT(True)"), "FALSE",); - assert_eq!(evaluate_formula_string("=NOT(\"false\")"), "TRUE",); - assert_eq!(evaluate_formula_string("=NOT(\"test\")"), "#CAST!",); - assert_eq!(evaluate_formula_string("=NOT(0)"), "TRUE",); - assert_eq!(evaluate_formula_string("=not(11<=3)"), "TRUE",); - assert_eq!(evaluate_formula_string("=Not(11<=3)"), "TRUE",); - assert_eq!(evaluate_formula_string("=nOT(11<=3)"), "TRUE",); + test_all_types!(evaluate_formula_string, "=NOT(11<=3)", "TRUE"); + test_all_types!(evaluate_formula_string, "=NOT(1=1)", "FALSE"); + test_all_types!(evaluate_formula_string, "=NOT(True)", "FALSE"); + test_all_types!(evaluate_formula_string, "=NOT(\"false\")", "TRUE"); + test_all_types!(evaluate_formula_string, "=NOT(\"test\")", "#CAST!"); + test_all_types!(evaluate_formula_string, "=NOT(0)", "TRUE"); + test_all_types!(evaluate_formula_string, "=not(11<=3)", "TRUE"); + test_all_types!(evaluate_formula_string, "=Not(11<=3)", "TRUE"); + test_all_types!(evaluate_formula_string, "=nOT(11<=3)", "TRUE"); } //////////////////////////// References ////////////////////////////////// #[test] fn it_evaluate_references() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Number(1.0), - "B" => types::Value::Number(2.0), - "C" => types::Value::Number(3.0), - "fix_rate" => types::Value::Number(10.0), - "input." => types::Value::Number(2.0), - "D" => types::Value::Number(1.0), - "F" => types::Value::Text("=D+1".to_string()), - "G" => types::Value::Text("=F+1+D+1".to_string()), - "Test" => types::Value::Iterator(vec![ - types::Value::Number(100.0), - types::Value::Number(200.0), - types::Value::Number(300.0), - ]), - - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=A+B", Some(&data_function)), - 3.0, - ); - assert_eq!( - evaluate_formula_number_with_reference("=(A*(B+C))*B", Some(&data_function)), - 10.0, - ); - assert_eq!( - evaluate_formula_number_with_reference("=fix_rate*input.", Some(&data_function)), - 20.0, + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Number(N::from_f32(1.0).unwrap()), + "B" => types::Value::Number(N::from_f32(2.0).unwrap()), + "C" => types::Value::Number(N::from_f32(3.0).unwrap()), + "fix_rate" => types::Value::Number(N::from_f32(10.0).unwrap()), + "input." => types::Value::Number(N::from_f32(2.0).unwrap()), + "D" => types::Value::Number(N::from_f32(1.0).unwrap()), + "F" => types::Value::Text("=D+1".to_string()), + "G" => types::Value::Text("=F+1+D+1".to_string()), + "Test" => types::Value::Iterator(vec![ + types::Value::Number(N::from_f32(100.0).unwrap()), + types::Value::Number(N::from_f32(200.0).unwrap()), + types::Value::Number(N::from_f32(300.0).unwrap()), + ]), + + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A+B", + Some(&data_function), + 3.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=D+F", Some(&data_function)), - 3.0, + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=(A*(B+C))*B", + Some(&data_function), + 10.0 + ); + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=fix_rate*input.", + Some(&data_function), + 20.0 + ); + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=D+F", + Some(&data_function), + 3.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=G+F", Some(&data_function)), - 7.0, + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=G+F", + Some(&data_function), + 7.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=SUM(A,B,C)", Some(&data_function)), - 6.0, + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM(A,B,C)", + Some(&data_function), + 6.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE(Test)", Some(&data_function)), - 200.0, + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE(Test)", + Some(&data_function), + 200.0 ); } #[test] fn it_evaluate_references_other_formulas() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Text("=1+B".to_string()), - "B" => types::Value::Number(3.0), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=A+B", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Text("=1+B".to_string()), + "B" => types::Value::Number(N::from_f32(3.0).unwrap()), + _ => types::Value::Error(types::Error::Value), + } + } + + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A+B", + Some(&data_function), 7.0 ); } #[test] fn it_evaluate_references_boolean_formulas() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Boolean(types::Boolean::True), - "B" => types::Value::Boolean(types::Boolean::False), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_boolean_with_reference("=AND(A,B)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Boolean(types::Boolean::True), + "B" => types::Value::Boolean(types::Boolean::False), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(A,B)", + Some(&data_function), "FALSE" ); } #[test] fn it_evaluate_references_error_value_formulas() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Boolean(types::Boolean::True), - "B" => types::Value::Error(types::Error::Value), //types::Value::Boolean(types::Boolean::False), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_boolean_with_reference("=AND(A,B)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Boolean(types::Boolean::True), + "B" => types::Value::Error(types::Error::Value), //types::Value::Boolean(types::Boolean::False), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(A,B)", + Some(&data_function), "#VALUE!" ); } #[test] fn it_evaluate_references_with_dot() { - let data_function = |s: String| match s.as_str() { - "A.B" => types::Value::Number(1.0), - "B.C" => types::Value::Number(2.0), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=A.B+B.C", Some(&data_function)), - 3.0, + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A.B" => types::Value::Number(N::from_f32(1.0).unwrap()), + "B.C" => types::Value::Number(N::from_f32(2.0).unwrap()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A.B+B.C", + Some(&data_function), + 3.0 ); } #[test] fn it_evaluate_iterators() { - assert_eq!(evaluate_formula_number("=SUM({1,2,3})"), 6.0,); - assert_eq!(evaluate_formula_number("=PRODUCT({1,2,3})"), 6.0,); - assert_eq!(evaluate_formula_number("=SUM({1,2,3}, {5,6})"), 17.0,); + test_all_types!(evaluate_formula_number, "=SUM({1,2,3})", 6.0); + test_all_types!(evaluate_formula_number, "=PRODUCT({1,2,3})", 6.0); + test_all_types!(evaluate_formula_number, "=SUM({1,2,3}, {5,6})", 17.0); } #[test] fn it_evaluate_iterators_and_scalars() { - assert_eq!(evaluate_formula_number("=SUM({1,2,3}, 4)"), 10.0,); - assert_eq!(evaluate_formula_number("=PRODUCT({1,2,3}, 4)"), 24.0,); + test_all_types!(evaluate_formula_number, "=SUM({1,2,3}, 4)", 10.0); + test_all_types!(evaluate_formula_number, "=PRODUCT({1,2,3}, 4)", 24.0); } #[test] fn it_evaluate_multiple_iterators_and_scalars() { - assert_eq!(evaluate_formula_number("=SUM({1,2,3})"), 6.0,); - assert_eq!(evaluate_formula_number("=SUM({ 1,2,3}, 4, {5, 6})"), 21.0,); - assert_eq!(evaluate_formula_number("=SUM({1,2,3},4,{5,6})"), 21.0,); - assert_eq!(evaluate_formula_number("=SUM({1+1,2,3-3},4,{5,6*1})"), 19.0,); - assert_eq!( - evaluate_formula_number("=PRODUCT({1+1,2,3-2},4, {5,6*1})"), - 480.0, + test_all_types!(evaluate_formula_number, "=SUM({1,2,3})", 6.0); + test_all_types!(evaluate_formula_number, "=SUM({ 1,2,3}, 4, {5, 6})", 21.0); + test_all_types!(evaluate_formula_number, "=SUM({1,2,3},4,{5,6})", 21.0); + test_all_types!(evaluate_formula_number, "=SUM({1+1,2,3-3},4,{5,6*1})", 19.0); + test_all_types!( + evaluate_formula_number, + "=PRODUCT({1+1,2,3-2},4, {5,6*1})", + 480.0 ); } #[test] fn it_evaluate_references_iterator() { - let data_function = |s: String| match s.as_str() { - "A.B" => types::Value::Number(1.0), - "B.C" => types::Value::Number(2.0), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=SUM({A.B,B.C})", Some(&data_function)), - 3.0, + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A.B" => types::Value::Number(N::from_f32(1.0).unwrap()), + "B.C" => types::Value::Number(N::from_f32(2.0).unwrap()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM({A.B,B.C})", + Some(&data_function), + 3.0 ); } #[test] fn it_evaluate_iterator_operators() { - assert_eq!(evaluate_formula_string("={1,2,3}+{1,2,3}"), "{2,4,6}"); - assert_eq!(evaluate_formula_string("={3,2,1}-{1,2,3}"), "{2,0,-2}"); - assert_eq!(evaluate_formula_string("={1,2,3}*{1,2,3}"), "{1,4,9}"); - assert_eq!(evaluate_formula_string("={1,2,3}/{1,2,3}"), "{1,1,1}"); - assert_eq!( - evaluate_formula_string("={1,2,3}/{0,0,0}"), + test_all_types!(evaluate_formula_string, "={1,2,3}+{1,2,3}", "{2,4,6}"); + test_all_types!(evaluate_formula_string, "={3,2,1}-{1,2,3}", "{2,0,-2}"); + test_all_types!(evaluate_formula_string, "={1,2,3}*{1,2,3}", "{1,4,9}"); + test_all_types!(evaluate_formula_string, "={1,2,3}/{1,2,3}", "{1,1,1}"); + test_all_types!( + evaluate_formula_string, + "={1,2,3}/{0,0,0}", "{#DIV/0!,#DIV/0!,#DIV/0!}" ); - assert_eq!(evaluate_formula_string("=-{1,2,3}"), "{-1,-2,-3}"); - assert_eq!(evaluate_formula_string("=-({1,2,3})"), "{-1,-2,-3}"); + test_all_types!(evaluate_formula_string, "=-{1,2,3}", "{-1,-2,-3}"); + test_all_types!(evaluate_formula_string, "=-({1,2,3})", "{-1,-2,-3}"); } #[test] fn it_evaluate_iterator_in_logic_functions() { - assert_eq!(evaluate_formula_string("=AND(0,{0,0,0},0)"), "FALSE"); - assert_eq!(evaluate_formula_string("=AND(0,{0,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string("=AND({0,0,0},0)"), "FALSE"); - assert_eq!(evaluate_formula_string("=OR(1,{1,0,0},0,1)"), "TRUE"); - assert_eq!(evaluate_formula_string("=OR(0,{0,1,0})"), "TRUE"); - assert_eq!(evaluate_formula_string("=OR({0,0,0},1)"), "TRUE"); - assert_eq!(evaluate_formula_string("=AND({0,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string("=AND({1,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string("=AND({1,1,1})"), "TRUE"); - assert_eq!(evaluate_formula_string("=OR({0,0,0})"), "FALSE"); - assert_eq!(evaluate_formula_string("=OR({1,0,0})"), "TRUE"); - assert_eq!(evaluate_formula_string("=OR({0,1,1})"), "TRUE"); - assert_eq!(evaluate_formula_string("=OR({1,0,1})"), "TRUE"); - assert_eq!(evaluate_formula_string("=OR({1,1,1})"), "TRUE"); - assert_eq!(evaluate_formula_string("=XOR({1,0,1})"), "FALSE"); - assert_eq!(evaluate_formula_string("=XOR({0,1,0})"), "TRUE"); - assert_eq!(evaluate_formula_string("=XOR({0,0,0})"), "FALSE"); + test_all_types!(evaluate_formula_string, "=AND(0,{0,0,0},0)", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND(0,{0,0,0})", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND({0,0,0},0)", "FALSE"); + test_all_types!(evaluate_formula_string, "=OR(1,{1,0,0},0,1)", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR(0,{0,1,0})", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR({0,0,0},1)", "TRUE"); + test_all_types!(evaluate_formula_string, "=AND({0,0,0})", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND({1,0,0})", "FALSE"); + test_all_types!(evaluate_formula_string, "=AND({1,1,1})", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR({0,0,0})", "FALSE"); + test_all_types!(evaluate_formula_string, "=OR({1,0,0})", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR({0,1,1})", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR({1,0,1})", "TRUE"); + test_all_types!(evaluate_formula_string, "=OR({1,1,1})", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR({1,0,1})", "FALSE"); + test_all_types!(evaluate_formula_string, "=XOR({0,1,0})", "TRUE"); + test_all_types!(evaluate_formula_string, "=XOR({0,0,0})", "FALSE"); } #[test] fn it_evaluate_iterator_with_diffrent_number_of_entries() { - assert_eq!(evaluate_formula_string("={0,0}+{1,2,3}"), "{1,2,#ARG!}"); - assert_eq!(evaluate_formula_string("={0,0}*{1,2,3}"), "{0,0,#ARG!}"); - assert_eq!( - evaluate_formula_string("={1,2,3}/{0,0}"), + test_all_types!(evaluate_formula_string, "={0,0}+{1,2,3}", "{1,2,#ARG!}"); + test_all_types!(evaluate_formula_string, "={0,0}*{1,2,3}", "{0,0,#ARG!}"); + test_all_types!( + evaluate_formula_string, + "={1,2,3}/{0,0}", "{#DIV/0!,#DIV/0!,#ARG!}" ); - assert_eq!(evaluate_formula_string("={0,0}+{1,\"Hi\"}"), "{1,#CAST!}"); -} - -#[test] -fn it_evaluate_date() -> Result<(), ParseError> { - let start: DateTime = DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z")?; - let end: DateTime = DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z")?; - let data_function = |s: String| match s.as_str() { - "start" => types::Value::Date(start), - "end" => types::Value::Date(end), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=DAYS(end, start)", Some(&data_function)), + test_all_types!(evaluate_formula_string, "={0,0}+{1,\"Hi\"}", "{1,#CAST!}"); +} + +#[test] +fn it_evaluate_date() { + fn start_date() -> DateTime { + DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z").unwrap() + } + fn end_date() -> DateTime { + DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z").unwrap() + } + fn data_function(s: String) -> types::Value { + match s.as_str() { + "start" => types::Value::Date(start_date()), + "end" => types::Value::Date(end_date()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=DAYS(end, start)", + Some(&data_function), 182.00 ); - assert_eq!( - evaluate_formula_number_with_reference("=days(end, start)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=days(end, start)", + Some(&data_function), 182.00 ); - assert_eq!( - evaluate_formula_date_with_reference("=start + 1", Some(&data_function)), - (start + Duration::days(1)).to_string() + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=start + 1", + Some(&data_function), + (start_date() + Duration::days(1)).to_string() ); - assert_eq!( - evaluate_formula_date_with_reference("=end-3", Some(&data_function)), - (end - Duration::days(3)).to_string() + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=end-3", + Some(&data_function), + (end_date() - Duration::days(3)).to_string() ); - Ok(()) } #[test] fn it_evaluate_custom_functions_() { - let custom_functions = |s: String, params: Vec| match s.as_str() { - "Increase" => types::Value::Number(params[0] + 1.0), - "SimpleSum" => types::Value::Number(params[0] + params[1]), - "CustomSum" => types::Value::Number(params[0] + params[1] + params[2]), - "EqualFive" => types::Value::Number(5.0), - "CountText" => types::Value::Text(10.0.to_string()), - "CountNumber" => types::Value::Number(20.0), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_custom_function("=Increase(1)", Some(&custom_functions)), + fn custom_functions(s: String, params: Vec) -> types::Value { + match s.as_str() { + "Increase" => types::Value::Number(params[0] + N::one()), + "SimpleSum" => types::Value::Number(params[0] + params[1]), + "CustomSum" => types::Value::Number(params[0] + params[1] + params[2]), + "EqualFive" => types::Value::Number(N::from_f32(5.0).unwrap()), + "CountText" => types::Value::Text(10.0.to_string()), + "CountNumber" => types::Value::Number(N::from_f32(20.0).unwrap()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=Increase(1)", + Some(&custom_functions), 2.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function("=SimpleSum(1,2)", Some(&custom_functions)), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=SimpleSum(1,2)", + Some(&custom_functions), 3.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function("=CustomSum(1,2,3)", Some(&custom_functions)), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=CustomSum(1,2,3)", + Some(&custom_functions), 6.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function("=EqualFive()+1", Some(&custom_functions)), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=EqualFive()+1", + Some(&custom_functions), 6.0 ); - assert_eq!( - evaluate_formula_string_with_custom_function("=\"P\"&CountText()", Some(&custom_functions)), + test_all_types_with_data!( + evaluate_formula_string_with_custom_function, + "=\"P\"&CountText()", + Some(&custom_functions), "P10" ); - assert_eq!( - evaluate_formula_string_with_custom_function( - "=\"P\"&CountNumber()", - Some(&custom_functions) - ), + test_all_types_with_data!( + evaluate_formula_string_with_custom_function, + "=\"P\"&CountNumber()", + Some(&custom_functions), "P20" ); } #[test] fn it_evaluate_left_and_right_functions() { - assert_eq!(evaluate_formula_string("=RIGHT(\"apple\", 3)"), "ple",); - assert_eq!(evaluate_formula_string("=RIGHT(\"apple\")"), "e",); + test_all_types!(evaluate_formula_string, "=RIGHT(\"apple\", 3)", "ple"); + test_all_types!(evaluate_formula_string, "=RIGHT(\"apple\")", "e"); - assert_eq!(evaluate_formula_string("=\"P\"&RIGHT(\"000\"&1,3)"), "P001",); - assert_eq!(evaluate_formula_string("=LEFT(\"apple\", 3)"), "app",); - assert_eq!(evaluate_formula_string("=LEFT(\"apple\")"), "a",); + test_all_types!(evaluate_formula_string, "=\"P\"&RIGHT(\"000\"&1,3)", "P001"); + test_all_types!(evaluate_formula_string, "=LEFT(\"apple\", 3)", "app"); + test_all_types!(evaluate_formula_string, "=LEFT(\"apple\")", "a"); - assert_eq!(evaluate_formula_string("=\"P\"&LEFT(\"000\"&1,3)"), "P000",); + test_all_types!(evaluate_formula_string, "=\"P\"&LEFT(\"000\"&1,3)", "P000"); - assert_eq!(evaluate_formula_string("=LEFT(\"apple\", 10)"), "apple",); - assert_eq!(evaluate_formula_string("=RIGHT(\"apple\", 10)"), "apple",); + test_all_types!(evaluate_formula_string, "=LEFT(\"apple\", 10)", "apple"); + test_all_types!(evaluate_formula_string, "=RIGHT(\"apple\", 10)", "apple"); } #[test] fn it_evaluates_blanks() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Number(1.0), - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=SUM(A,B)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Number(N::one()), + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM(A,B)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=A+B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A+B", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference_no_conversion("=SUM(A,C)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference_no_conversion, + "=SUM(A,C)", + Some(&data_function), types::Value::Error(types::Error::Value) ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE(A,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE(A,B)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=SUM(A,B,2,3,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM(A,B,2,3,B)", + Some(&data_function), 6.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE(A,B,2,3,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE(A,B,2,3,B)", + Some(&data_function), 2.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(A,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(A,B)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(1,B,B,B,A,2)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(1,B,B,B,A,2)", + Some(&data_function), 2.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=1+B+B+B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=1+B+B+B", + Some(&data_function), 1.0 ); } #[test] fn it_evaluates_blanks_only() { - let data_function = |s: String| match s.as_str() { - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=SUM(B)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM(B)", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_string_with_reference("=AVERAGE(B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=AVERAGE(B)", + Some(&data_function), "#DIV/0!" ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(B)", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=-B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=-B", + Some(&data_function), 0.0 ); } #[test] fn it_evaluates_blanks_when_blank_in_first_position() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Number(1.0), - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=SUM(B,A)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Number(N::one()), + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM(B,A)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B+A", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B+A", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE(B,A)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE(B,A)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=SUM(B,2,3,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM(B,2,3,B)", + Some(&data_function), 5.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE(B,2,3,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE(B,2,3,B)", + Some(&data_function), 2.5 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(B,A)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(B,A)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(B,A,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(B,A,B)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(B,B,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(B,B,B)", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(B,B,B,A,2)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(B,B,B,A,2)", + Some(&data_function), 2.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B+B+B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B+B+B", + Some(&data_function), 0.0 ); } #[test] fn it_evaluates_blanks_in_abs_function() { - let data_function = |s: String| match s.as_str() { - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=ABS(B)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=ABS(B)", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=ABS(-B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=ABS(-B)", + Some(&data_function), 0.0 ); } #[test] fn it_evaluates_blanks_in_days_function() { - let start: DateTime = DateTime::parse_from_rfc3339("2019-02-01T02:00:00.000Z") - .ok() - .unwrap(); - let data_function = |s: String| match s.as_str() { - "start" => types::Value::Date(start), - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=DAYS(B, B)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + let start: DateTime = + DateTime::parse_from_rfc3339("2019-02-01T02:00:00.000Z").unwrap(); + match s.as_str() { + "start" => types::Value::Date(start), + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=DAYS(B, B)", + Some(&data_function), 0.00 ); - assert_eq!( - evaluate_formula_number_with_reference("=DAYS(start, B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=DAYS(start, B)", + Some(&data_function), 43495.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=DAYS(B, start)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=DAYS(B, start)", + Some(&data_function), -43495.0 ); } #[test] fn it_evaluates_blanks_with_operators() { - let data_function = |s: String| match s.as_str() { - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=1-B", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=1-B", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B-1", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B-1", + Some(&data_function), -1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=1+B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=1+B", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B+1", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B+1", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_string_with_reference("=1/B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=1/B", + Some(&data_function), "#DIV/0!" ); - assert_eq!( - evaluate_formula_number_with_reference("=B/1", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B/1", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=1*B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=1*B", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B*1", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B*1", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=1^B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=1^B", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B^1", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B^1", + Some(&data_function), 0.0 ); } #[test] fn it_evaluates_blanks_with_operators_and_reference() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Number(1.0), - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=A-B", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Number(N::one()), + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A-B", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B-A", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B-A", + Some(&data_function), -1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=A+B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A+B", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B+A", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B+A", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_string_with_reference("=A/B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=A/B", + Some(&data_function), "#DIV/0!" ); - assert_eq!( - evaluate_formula_number_with_reference("=B/A", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B/A", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=A*B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A*B", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B*A", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B*A", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=A^B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=A^B", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=B^A", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=B^A", + Some(&data_function), 0.0 ); } #[test] fn it_evaluates_blanks_in_boolean_operations() { - let data_function = |s: String| match s.as_str() { - "T" => types::Value::Boolean(types::Boolean::True), - "B" => types::Value::Blank, - "F" => types::Value::Boolean(types::Boolean::False), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_boolean_with_reference("=AND(T,B)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "T" => types::Value::Boolean(types::Boolean::True), + "B" => types::Value::Blank, + "F" => types::Value::Boolean(types::Boolean::False), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(T,B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR(T,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR(T,B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=NOT(B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=NOT(B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=AND(F,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(F,B)", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR(F,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR(F,B)", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR(T,B,F,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR(T,B,F,B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR(F,B,T,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR(F,B,T,B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=AND(T,B,F,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(T,B,F,B)", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=AND(F,B,T,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(F,B,T,B)", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR(T,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR(T,B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR(F,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR(F,B)", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR(T,B,F,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR(T,B,F,B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR(F,B,T,B)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR(F,B,T,B)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR(T,B,F,B,T)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR(T,B,F,B,T)", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR(F,B,T,B,F)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR(F,B,T,B,F)", + Some(&data_function), "TRUE" ); } #[test] fn evaluate_invalid_reference_in_functions() { - let data_function = |_s: String| types::Value::Error(types::Error::Value); + fn data_function(_s: String) -> types::Value { + types::Value::Error(types::Error::Value) + } + // test with a non existing reference - let formula = - parse_formula::parse_string_to_formula("=AND(T, NO_REFERENCE)", None::); assert_eq!( - calculate::calculate_formula(formula, Some(&data_function)), + calculate::calculate_formula::( + parse_formula::parse_string_to_formula( + "=AND(T, NO_REFERENCE)", + None::>, + ), + Some(&data_function) + ), types::Value::Error(types::Error::Value) ); assert_eq!( - evaluate_formula_boolean_with_reference("=AND(T, NO_REFERENCE)", Some(&data_function)), + calculate::calculate_formula::( + parse_formula::parse_string_to_formula( + "=AND(T, NO_REFERENCE)", + None::>, + ), + Some(&data_function) + ), + types::Value::Error(types::Error::Value) + ); + + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(T, NO_REFERENCE)", + Some(&data_function), "#VALUE!" ); } #[test] fn it_evaluates_blanks_in_comparison_operators() { - let data_function = |s: String| match s.as_str() { - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_string_with_reference("=B=B", Some(&data_function)), - "TRUE", + fn data_function(s: String) -> types::Value { + match s.as_str() { + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B=B", + Some(&data_function), + "TRUE" ); - assert_eq!( - evaluate_formula_string_with_reference("=1=B", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=1=B", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=B=1", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B=1", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=1>B", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=1>B", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=B>1", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B>1", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=0=B", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=0=B", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=B=0", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B=0", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=B=\"\"", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=B=\"\"", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=\"\"=B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=\"\"=B", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=\"something\"=B", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=\"something\"=B", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=B='test'", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B='test'", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("='test'=B", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "='test'=B", + Some(&data_function), + "FALSE" ); } #[test] fn it_evaluates_blanks_in_comparison_operators_with_references() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Number(-2.0), - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_string_with_reference("=A>=B", Some(&data_function)), - "FALSE", + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Number(N::from_f32(-2.0).unwrap()), + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=A>=B", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=B>=A", Some(&data_function)), - "FALSE", + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B>=A", + Some(&data_function), + "FALSE" ); - assert_eq!( - evaluate_formula_string_with_reference("=A types::Value::Number(-2.0), - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_string_with_reference("=\"Hello\"&B", Some(&data_function)), - "Hello", - ); - assert_eq!( - evaluate_formula_string_with_reference("=B&\"Hello\"", Some(&data_function)), - "Hello", - ); - assert_eq!( - evaluate_formula_string_with_reference("=B&B", Some(&data_function)), - "", - ); - assert_eq!( - evaluate_formula_string_with_reference("=RIGHT(B)", Some(&data_function)), - "", - ); - assert_eq!( - evaluate_formula_string_with_reference("=LEFT(B)", Some(&data_function)), - "", + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Number(N::from_f32(-2.0).unwrap()), + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=\"Hello\"&B", + Some(&data_function), + "Hello" + ); + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B&\"Hello\"", + Some(&data_function), + "Hello" + ); + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=B&B", + Some(&data_function), + "" + ); + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=RIGHT(B)", + Some(&data_function), + "" + ); + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=LEFT(B)", + Some(&data_function), + "" ); } #[test] fn it_evaluates_blank_constructors() { - let custom_functions = |s: String, _params: Vec| match s.as_str() { - "BLANK" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_custom_function("=SUM(BLANK())", Some(&custom_functions)), + fn custom_functions(s: String, _params: Vec) -> types::Value { + match s.as_str() { + "BLANK" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=SUM(BLANK())", + Some(&custom_functions), 0.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function("=SUM(BLANK(), 1)", Some(&custom_functions)), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=SUM(BLANK(), 1)", + Some(&custom_functions), 1.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function( - "=PRODUCT(BLANK(), 1)", - Some(&custom_functions) - ), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=PRODUCT(BLANK(), 1)", + Some(&custom_functions), 1.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function( - "=AVERAGE(BLANK(), 1)", - Some(&custom_functions) - ), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=AVERAGE(BLANK(), 1)", + Some(&custom_functions), 1.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function("=BLANK()+1", Some(&custom_functions)), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=BLANK()+1", + Some(&custom_functions), 1.0 ); - assert_eq!( - evaluate_formula_number_with_custom_function("=BLANK()*1", Some(&custom_functions)), + test_all_types_with_data!( + evaluate_formula_number_with_custom_function, + "=BLANK()*1", + Some(&custom_functions), 0.0 ); } #[test] fn it_evaluates_blank_in_iterators() { - let data_function = |s: String| match s.as_str() { - "A" => types::Value::Number(100.0), - "Array" => types::Value::Iterator(vec![ - types::Value::Number(100.0), - types::Value::Blank, - types::Value::Blank, - ]), - "B" => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE({A, B, B})", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "A" => types::Value::Number(N::from_f32(100.0).unwrap()), + "Array" => types::Value::Iterator(vec![ + types::Value::Number(N::from_f32(100.0).unwrap()), + types::Value::Blank, + types::Value::Blank, + ]), + "B" => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE({A, B, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE({A, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE({A, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE({B, A, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE({B, A, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=AVERAGE(Array)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=AVERAGE(Array)", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=SUM({A, B, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM({A, B, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=SUM({A, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM({A, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=SUM({B, A, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM({B, A, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=SUM(Array)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=SUM(Array)", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT({A, B, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT({A, B, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT({A, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT({A, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT({B, A, B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT({B, A, B})", + Some(&data_function), 100.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=PRODUCT(Array)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=PRODUCT(Array)", + Some(&data_function), 100.0 ); } #[test] fn it_evaluates_blank_with_iterators_in_boolean_operations() { - let data_function = |s: String| match s.as_str() { - "T" => types::Value::Boolean(types::Boolean::True), - "B" => types::Value::Blank, - "F" => types::Value::Boolean(types::Boolean::False), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_boolean_with_reference("=AND({T,B})", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "T" => types::Value::Boolean(types::Boolean::True), + "B" => types::Value::Blank, + "F" => types::Value::Boolean(types::Boolean::False), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND({T,B})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR({T,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR({T,B})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=AND({F,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND({F,B})", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR({F,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR({F,B})", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR({T,B,F,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR({T,B,F,B})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=OR({F,B,T,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=OR({F,B,T,B})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=AND({T,B,F,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND({T,B,F,B})", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=AND({F,B,T,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND({F,B,T,B})", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR({T,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR({T,B})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR({F,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR({F,B})", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR({T,B,F,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR({T,B,F,B})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR({F,B,T,B})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR({F,B,T,B})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR({T,B,F,B,T})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR({T,B,F,B,T})", + Some(&data_function), "FALSE" ); - assert_eq!( - evaluate_formula_boolean_with_reference("=XOR({F,B,T,B,F})", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR({F,B,T,B,F})", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_boolean_with_reference( - "=XOR({F,B,T,B,F,{F,B,T,B,F}})", - Some(&data_function) - ), + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=XOR({F,B,T,B,F,{F,B,T,B,F}})", + Some(&data_function), "FALSE" ); } #[test] fn it_evaluates_formulas_with_3_params() { - assert_eq!(evaluate_formula_number("=IF()"), 0.0); - assert_eq!(evaluate_formula_number("=IF( )"), 0.0); - assert_eq!(evaluate_formula_number("=IF(,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF( ,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(, )"), 0.0); - assert_eq!(evaluate_formula_number("=IF( , )"), 0.0); - assert_eq!(evaluate_formula_number("=IF(,,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(, ,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(,,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(,, )"), 0.0); - assert_eq!(evaluate_formula_number("=IF(1,,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(1,2,)"), 2.0); - assert_eq!(evaluate_formula_number("=IF(1,2)"), 2.0); - assert_eq!(evaluate_formula_number("=IF(0,2,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(0,2)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(0,,3)"), 3.0); - assert_eq!(evaluate_formula_number("=IF(1,,3)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(0,2,3)"), 3.0); - assert_eq!(evaluate_formula_number("=IF(,2,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(,2)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(,2,3)"), 3.0); - assert_eq!(evaluate_formula_number("=IF(,,3)"), 3.0); -} - -#[test] -fn it_evaluates_if_formulas() -> Result<(), ParseError> { - assert_eq!(evaluate_formula_number("=IF(TRUE,1,0)"), 1.0); - assert_eq!(evaluate_formula_number("=IF(FALSE,1,0)"), 0.0); - assert_eq!(evaluate_formula_string("=IF(TRUE,\"a\",0)"), "a"); - assert_eq!(evaluate_formula_number("=IF(FALSE,\"a\",0)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(1=1,1,0)"), 1.0); - assert_eq!(evaluate_formula_number("=IF(1=2,1,0)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(AND(TRUE,FALSE),1,0)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(TRUE,IF(FALSE,1,2),0)"), 2.0); - assert_eq!(evaluate_formula_number("=IF(2,1,0)"), 1.0); - assert_eq!(evaluate_formula_number("=IF(-1,1,0)"), 1.0); - assert_eq!(evaluate_formula_number("=IF(0,1,0)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(TRUE,1+2+3,0)"), 6.0); - assert_eq!(evaluate_formula_number("=IF(FALSE,1)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(FALSE,1,)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(TRUE,,1)"), 0.0); - assert_eq!(evaluate_formula_number("=IF(TRUE, ,1)"), 0.0); - assert_eq!(evaluate_formula_string("=IF(TRUE,TRUE,FALSE)"), "TRUE"); - assert_eq!( - evaluate_formula_string("=IF( TRUE , TRUE , FALSE )"), + test_all_types!(evaluate_formula_number, "=IF()", 0.0); + test_all_types!(evaluate_formula_number, "=IF( )", 0.0); + test_all_types!(evaluate_formula_number, "=IF(,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF( ,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(, )", 0.0); + test_all_types!(evaluate_formula_number, "=IF( , )", 0.0); + test_all_types!(evaluate_formula_number, "=IF(,,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(, ,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(,,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(,, )", 0.0); + test_all_types!(evaluate_formula_number, "=IF(1,,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(1,2,)", 2.0); + test_all_types!(evaluate_formula_number, "=IF(1,2)", 2.0); + test_all_types!(evaluate_formula_number, "=IF(0,2,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(0,2)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(0,,3)", 3.0); + test_all_types!(evaluate_formula_number, "=IF(1,,3)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(0,2,3)", 3.0); + test_all_types!(evaluate_formula_number, "=IF(,2,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(,2)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(,2,3)", 3.0); + test_all_types!(evaluate_formula_number, "=IF(,,3)", 3.0); +} + +#[test] +fn it_evaluates_if_formulas() { + test_all_types!(evaluate_formula_number, "=IF(TRUE,1,0)", 1.0); + test_all_types!(evaluate_formula_number, "=IF(FALSE,1,0)", 0.0); + test_all_types!(evaluate_formula_string, "=IF(TRUE,\"a\",0)", "a"); + test_all_types!(evaluate_formula_number, "=IF(FALSE,\"a\",0)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(1=1,1,0)", 1.0); + test_all_types!(evaluate_formula_number, "=IF(1=2,1,0)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(AND(TRUE,FALSE),1,0)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(TRUE,IF(FALSE,1,2),0)", 2.0); + test_all_types!(evaluate_formula_number, "=IF(2,1,0)", 1.0); + test_all_types!(evaluate_formula_number, "=IF(-1,1,0)", 1.0); + test_all_types!(evaluate_formula_number, "=IF(0,1,0)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(TRUE,1+2+3,0)", 6.0); + test_all_types!(evaluate_formula_number, "=IF(FALSE,1)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(FALSE,1,)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(TRUE,,1)", 0.0); + test_all_types!(evaluate_formula_number, "=IF(TRUE, ,1)", 0.0); + test_all_types!(evaluate_formula_string, "=IF(TRUE,TRUE,FALSE)", "TRUE"); + test_all_types!( + evaluate_formula_string, + "=IF( TRUE , TRUE , FALSE )", "TRUE" ); - assert_eq!(evaluate_formula_string("=IF(TRUE , TRUE, FALSE )"), "TRUE"); - assert_eq!(evaluate_formula_number("=IF(1,IF(FALSE,1,2),0)"), 2.0); - assert_eq!( - evaluate_formula_number("=IF(1=0,IF(FALSE,1,2),IF(TRUE,1,2))"), + test_all_types!(evaluate_formula_string, "=IF(TRUE , TRUE, FALSE )", "TRUE"); + test_all_types!(evaluate_formula_number, "=IF(1,IF(FALSE,1,2),0)", 2.0); + test_all_types!( + evaluate_formula_number, + "=IF(1=0,IF(FALSE,1,2),IF(TRUE,1,2))", 1.0 ); - assert_eq!( - evaluate_formula_string("=IF(1/0,IF(FALSE,1,2),0)"), - "#DIV/0!", + test_all_types!( + evaluate_formula_string, + "=IF(1/0,IF(FALSE,1,2),0)", + "#DIV/0!" ); - assert_eq!(evaluate_formula_string("=IF(\"text\",1,0)"), "#VALUE!",); - - let date1: DateTime = DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z")?; - let date2: DateTime = DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z")?; - let data_function = |s: String| match s.as_str() { - "date1" => types::Value::Date(date1), - "date2" => types::Value::Date(date2), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_number_with_reference("=IF(date1=date2,1,0)", Some(&data_function)), + test_all_types!(evaluate_formula_string, "=IF(\"text\",1,0)", "#VALUE!"); + + fn data_function(s: String) -> types::Value { + let date1: DateTime = + DateTime::parse_from_rfc3339("2019-03-01T02:00:00.000Z").unwrap(); + let date2: DateTime = + DateTime::parse_from_rfc3339("2019-08-30T02:00:00.000Z").unwrap(); + match s.as_str() { + "date1" => types::Value::Date(date1), + "date2" => types::Value::Date(date2), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=IF(date1=date2,1,0)", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=IF(date1<>date2,1,0)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=IF(date1<>date2,1,0)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=IF(date1date2,1,0)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=IF(date1>date2,1,0)", + Some(&data_function), 0.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=IF(date1<=date2,1,0)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=IF(date1<=date2,1,0)", + Some(&data_function), 1.0 ); - assert_eq!( - evaluate_formula_number_with_reference("=IF(date1>=date2,1,0)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_number_with_reference, + "=IF(date1>=date2,1,0)", + Some(&data_function), 0.0 ); - Ok(()) } #[test] fn it_evaluates_if_formulas_with_text() { - let data_function = |s: String| match s.as_str() { - "ReferenceKey" => types::Value::Text("100".to_string()), - "ReferenceName" => types::Value::Text("Test".to_string()), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_string_with_reference( - "=IF(ReferenceKey=\"10\",\"\",ReferenceKey&\" - \")&ReferenceName", - Some(&data_function) - ), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "ReferenceKey" => types::Value::Text("100".to_string()), + "ReferenceName" => types::Value::Text("Test".to_string()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=IF(ReferenceKey=\"10\",\"\",ReferenceKey&\" - \")&ReferenceName", + Some(&data_function), "100 - Test" ); } #[test] fn it_evaluates_isblank_function() { - let data_function = |s: String| match s.as_str() { - // "ReferenceKey" => types::Value::Text("100".to_string()), - "ReferenceName" => types::Value::Text("Test".to_string()), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_string_with_reference("=ISBLANK(ReferenceKey)", Some(&data_function)), + fn data_function(s: String) -> types::Value { + match s.as_str() { + // "ReferenceKey" => types::Value::Text("100".to_string()), + "ReferenceName" => types::Value::Text("Test".to_string()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=ISBLANK(ReferenceKey)", + Some(&data_function), "TRUE" ); - assert_eq!( - evaluate_formula_string_with_reference("=ISBLANK(ReferenceName)", Some(&data_function)), + test_all_types_with_data!( + evaluate_formula_string_with_reference, + "=ISBLANK(ReferenceName)", + Some(&data_function), "FALSE" ); } #[test] fn test_inner_function_with_whitespace() { - let data_function = |s: String| match s.as_str() { - "any_dropdowns" => types::Value::Number(1.0), - "primary_carrier" => types::Value::Text("Allianz*".to_owned()), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_boolean_with_reference( - "=AND(RIGHT(primary_carrier, 1)=\"*\", 1 > 0)", - Some(&data_function) - ), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "any_dropdowns" => types::Value::Number(N::one()), + "primary_carrier" => types::Value::Text("Allianz*".to_owned()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=AND(RIGHT(primary_carrier, 1)=\"*\", 1 > 0)", + Some(&data_function), "TRUE" ); } #[test] fn test_zero_padding() { - let data_function = |s: String| match s.as_str() { - "num" => types::Value::Number(91.0), - _ => types::Value::Error(types::Error::Value), - }; - assert_eq!( - evaluate_formula_boolean_with_reference( - "=RIGHT(\"00000000\" & num, 8)", - Some(&data_function) - ), + fn data_function(s: String) -> types::Value { + match s.as_str() { + "num" => types::Value::Number(N::from_f32(91.0).unwrap()), + _ => types::Value::Error(types::Error::Value), + } + } + test_all_types_with_data!( + evaluate_formula_boolean_with_reference, + "=RIGHT(\"00000000\" & num, 8)", + Some(&data_function), "00000091" ); } From 945b1df4d16b65ae296d8d3f4a7d9e9ac97b528f Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Tue, 9 Sep 2025 14:42:32 +0300 Subject: [PATCH 19/23] Reorganize calculation files. Simply boolean evaluations. Use strum --- .vscode/settings.json | 4 + Cargo.lock | 28 + Cargo.toml | 1 + src/calculate.rs | 1506 --------------------------- src/calculate/args.rs | 90 ++ src/calculate/display.rs | 32 + src/calculate/iterator.rs | 48 + src/calculate/mod.rs | 48 + src/calculate/operation/boolean.rs | 279 +++++ src/calculate/operation/date.rs | 103 ++ src/calculate/operation/function.rs | 139 +++ src/calculate/operation/mod.rs | 124 +++ src/calculate/operation/number.rs | 386 +++++++ src/calculate/operation/string.rs | 58 ++ src/calculate/reference.rs | 34 + src/types.rs | 30 +- 16 files changed, 1402 insertions(+), 1508 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 src/calculate.rs create mode 100644 src/calculate/args.rs create mode 100644 src/calculate/display.rs create mode 100644 src/calculate/iterator.rs create mode 100644 src/calculate/mod.rs create mode 100644 src/calculate/operation/boolean.rs create mode 100644 src/calculate/operation/date.rs create mode 100644 src/calculate/operation/function.rs create mode 100644 src/calculate/operation/mod.rs create mode 100644 src/calculate/operation/number.rs create mode 100644 src/calculate/operation/string.rs create mode 100644 src/calculate/reference.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..84bca01 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.check.command": "clippy", + "editor.formatOnSave": true +} diff --git a/Cargo.lock b/Cargo.lock index c548f4f..03d9cef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "libc" version = "0.2.174" @@ -171,6 +177,27 @@ dependencies = [ "digest", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.104" @@ -235,4 +262,5 @@ dependencies = [ "num-traits", "pest", "pest_derive", + "strum", ] diff --git a/Cargo.toml b/Cargo.toml index cb60cc4..76535ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ version = "0.4.0" [dependencies] chrono = { version = "0.4.41", default-features = false } +strum = { version = "0.27.2", features = ["derive"] } num-traits = "0.2.19" pest = "2.8.1" pest_derive = "2.8.1" diff --git a/src/calculate.rs b/src/calculate.rs deleted file mode 100644 index abf300d..0000000 --- a/src/calculate.rs +++ /dev/null @@ -1,1506 +0,0 @@ -use crate::{ - parse_formula, - types::{self, XlNum}, -}; -use chrono::{DateTime, Duration, FixedOffset}; -use std::{fmt::Debug, str::FromStr}; - -type NoCustomFunction<'a, N> = &'a fn(String, Vec) -> types::Value; - -fn calculate_divide_operator(num1: N, num2: N) -> N -where - N: XlNum, - ::Err: Debug, -{ - num1 / num2 -} - -fn is_float_int(num: N) -> bool -where - N: XlNum, - ::Err: Debug, -{ - num.fract().is_zero() -} - -fn calculate_power_operator(num1: N, num2: N) -> N -where - N: XlNum, - ::Err: Debug, -{ - if is_float_int(num2) { - num1.powi(num2.as_()) - } else { - num1.powf(num2) - } -} - -fn calculate_concat_operator(str1: &str, str2: &str) -> String { - str1.to_owned() + str2 -} - -fn calculate_string_operation_rhs( - l: &str, - rhs: types::Value, - f: fn(str1: &str, str2: &str) -> String, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Boolean(_) => rhs, - types::Value::Error(_) => rhs, - types::Value::Number(r) => types::Value::Text(f(l, &r.to_string())), - types::Value::Text(r) => types::Value::Text(f(l, &r)), - types::Value::Iterator(_) => types::Value::Error(types::Error::Value), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Text(f(l, "")), - } -} - -fn calculate_string_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(str1: &str, str2: &str) -> String, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match lhs { - types::Value::Boolean(_) => lhs, - types::Value::Error(_) => lhs, - types::Value::Number(l) => calculate_string_operation_rhs(&l.to_string(), rhs, f), - types::Value::Text(l) => calculate_string_operation_rhs(&l, rhs, f), - types::Value::Iterator(_) => types::Value::Error(types::Error::Value), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => calculate_string_operation_rhs("", rhs, f), - } -} - -fn calcualte_numeric_operator_rhs_text( - t: String, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match t.parse::() { - Ok(nl) => match rhs { - types::Value::Boolean(_) => rhs, - types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { - Ok(nr) => types::Value::Number(f(nl, nr)), - Err(_) => types::Value::Error(types::Error::Cast), - }, - types::Value::Number(r) => types::Value::Number(f(nl, r)), - types::Value::Iterator(_) => types::Value::Error(types::Error::Value), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Error(types::Error::Value), - }, - Err(_) => types::Value::Error(types::Error::Cast), - } -} - -fn calculate_numeric_operator_rhs_number( - l: N, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Boolean(_) => rhs, - types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { - Ok(nr) => types::Value::Number(f(l, nr)), - Err(_) => types::Value::Error(types::Error::Cast), - }, - types::Value::Number(r) => types::Value::Number(f(l, r)), - types::Value::Iterator(mut value_vec) => { - if let Some(mut temp) = value_vec.pop() { - while let Some(top) = value_vec.pop() { - temp = calculate_numeric_operator(temp, top, f); - } - calculate_numeric_operator(lhs, temp, f) - } else { - types::Value::Error(types::Error::Argument) - } - } - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Number(f(l, N::zero())), - } -} - -fn calculate_numeric_operator_product_rhs_number( - l: N, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Boolean(_) => rhs, - types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { - Ok(nr) => types::Value::Number(f(l, nr)), - Err(_) => types::Value::Error(types::Error::Cast), - }, - types::Value::Number(r) => types::Value::Number(f(l, r)), - types::Value::Iterator(mut value_vec) => { - if let Some(mut temp) = value_vec.pop() { - while let Some(top) = value_vec.pop() { - temp = calculate_numeric_product_operator(temp, top, f); - } - calculate_numeric_product_operator(lhs, temp, f) - } else { - types::Value::Error(types::Error::Argument) - } - } - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => match lhs { - types::Value::Blank => types::Value::Blank, - _ => types::Value::Number(l), - }, - } -} - -fn calculate_numeric_operator_rhs_iterator( - mut lhs_vec: Vec>, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Number(_) => { - if let Some(mut temp) = lhs_vec.pop() { - while let Some(top) = lhs_vec.pop() { - temp = calculate_numeric_operator(temp, top, f); - } - calculate_numeric_operator(temp, rhs, f) - } else { - types::Value::Error(types::Error::Argument) - } - } - types::Value::Iterator(mut rhs_vec) => { - let mut result_vec = Vec::new(); - loop { - match (lhs_vec.pop(), rhs_vec.pop()) { - (Some(x), Some(y)) => { - result_vec.push(calculate_numeric_operator(x, y, f)); - } - (Some(_), None) => result_vec.push(types::Value::Error(types::Error::Argument)), - (None, Some(_)) => result_vec.push(types::Value::Error(types::Error::Argument)), - (None, None) => break, - }; - } - types::Value::Iterator(result_vec) - } - _ => types::Value::Error(types::Error::Value), - } -} - -fn add_days_to_date(d: DateTime, rhs: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Number(x) => types::Value::Date(d + Duration::days(x.as_())), - _ => types::Value::Error(types::Error::Value), - } -} - -fn subtract_days_from_date(d: DateTime, rhs: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Number(x) => types::Value::Date(d - Duration::days(x.as_())), - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_numeric_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - //println!("{:?}::{:?}", lhs, rhs); - match lhs { - types::Value::Boolean(_) => lhs, - types::Value::Error(_) => lhs, - types::Value::Text(t) => calcualte_numeric_operator_rhs_text(t, rhs, f), - types::Value::Number(l) => calculate_numeric_operator_rhs_number(l, lhs, rhs, f), - types::Value::Iterator(lhs_vec) => calculate_numeric_operator_rhs_iterator(lhs_vec, rhs, f), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => calculate_numeric_operator_rhs_number(N::zero(), lhs, rhs, f), - } -} - -fn calculate_numeric_product_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - //println!("{:?}::{:?}", lhs, rhs); - match lhs { - types::Value::Boolean(_) => lhs, - types::Value::Error(_) => lhs, - types::Value::Text(t) => calcualte_numeric_operator_rhs_text(t, rhs, f), - types::Value::Number(l) => calculate_numeric_operator_product_rhs_number(l, lhs, rhs, f), - types::Value::Iterator(lhs_vec) => calculate_numeric_operator_rhs_iterator(lhs_vec, rhs, f), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => calculate_numeric_operator_product_rhs_number(N::one(), lhs, rhs, f), - } -} - -fn calculate_average_operator_rhs_number( - element_count: &mut i64, - l: N, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Boolean(_) => rhs, - types::Value::Error(_) => rhs, - types::Value::Text(t) => match t.parse::() { - Ok(nr) => types::Value::Number(f(l, nr)), - Err(_) => types::Value::Error(types::Error::Cast), - }, - types::Value::Number(r) => types::Value::Number(f(l, r)), - types::Value::Iterator(mut value_vec) => { - if let Some(mut temp) = value_vec.pop() { - if temp == types::Value::Blank { - *element_count -= 1; - } - while let Some(top) = value_vec.pop() { - temp = calculate_numeric_operator(temp, top.clone(), f); - match top { - types::Value::Blank => (), - _ => *element_count += 1, - }; - } - calculate_numeric_operator(lhs, temp, f) - } else { - types::Value::Error(types::Error::Argument) - } - } - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => { - *element_count -= 1; - types::Value::Number(f(l, N::zero())) - } - } -} - -fn calculate_average_operator_rhs_iterator( - element_count: &mut i64, - mut lhs_vec: Vec>, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rhs { - types::Value::Number(_) => { - if let Some(mut temp) = lhs_vec.pop() { - while let Some(top) = lhs_vec.pop() { - temp = calculate_numeric_operator(temp, top, f); - *element_count += 1; - } - calculate_numeric_operator(temp, rhs, f) - } else { - types::Value::Error(types::Error::Argument) - } - } - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_average_operator( - element_count: &mut i64, - lhs: types::Value, - rhs: types::Value, - f: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match lhs { - types::Value::Boolean(_) => lhs, - types::Value::Error(_) => lhs, - types::Value::Text(t) => calcualte_numeric_operator_rhs_text(t, rhs, f), - types::Value::Number(l) => { - calculate_average_operator_rhs_number(element_count, l, lhs, rhs, f) - } - types::Value::Iterator(lhs_vec) => { - calculate_average_operator_rhs_iterator(element_count, lhs_vec, rhs, f) - } - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => { - *element_count -= 1; - calculate_average_operator_rhs_number(element_count, N::zero(), lhs, rhs, f) - } - } -} - -fn calculate_comparison_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(num1: N, num2: N) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match lhs { - types::Value::Text(l) => match rhs { - types::Value::Text(r) => { - if l.eq(&r) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Blank => { - if l.is_empty() { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - _ => types::Value::Error(types::Error::Value), - }, - types::Value::Number(l) => match rhs { - types::Value::Number(r) => { - if f(l, r) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Blank => types::Value::Boolean(types::Boolean::False), - _ => types::Value::Error(types::Error::Value), - }, - types::Value::Blank => match rhs { - types::Value::Number(_) => types::Value::Boolean(types::Boolean::False), - types::Value::Text(r) => { - if r.is_empty() { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Blank => types::Value::Boolean(types::Boolean::True), - _ => types::Value::Error(types::Error::Value), - }, - types::Value::Boolean(_) => types::Value::Error(types::Error::Value), - types::Value::Error(_) => types::Value::Error(types::Error::Value), - types::Value::Iterator(_) => types::Value::Error(types::Error::Value), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - } -} - -fn to_bool(value: types::Boolean) -> bool { - match value { - types::Boolean::True => true, - types::Boolean::False => false, - } -} - -fn calculate_boolean_operator_rhs_boolean( - l: types::Boolean, - rh: types::Value, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rh { - types::Value::Boolean(r) => { - if f(to_bool(l), to_bool(r)) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Iterator(mut value_vec) => { - if let Some(mut temp) = value_vec.pop() { - while let Some(top) = value_vec.pop() { - temp = calculate_boolean_operator(temp, top, f); - } - let rhs = cast_value_to_boolean(temp); - match rhs { - types::Value::Boolean(r) => { - if f(to_bool(l), to_bool(r)) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - _ => types::Value::Error(types::Error::Value), - } - } else { - types::Value::Error(types::Error::Argument) - } - } - types::Value::Blank => { - if to_bool(l) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_boolean_operator_rhs_error(rh: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rh { - types::Value::Boolean(r) => { - if to_bool(r) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Error(_) => types::Value::Error(types::Error::Cast), - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_boolean_operator_rhs_iterator( - rh: types::Value, - mut lhs_vec: Vec>, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match rh { - types::Value::Boolean(r) => { - if let Some(mut temp) = lhs_vec.pop() { - while let Some(top) = lhs_vec.pop() { - temp = calculate_boolean_operator(temp, top, f); - } - let lhs = cast_value_to_boolean(temp); - match lhs { - types::Value::Boolean(l) => { - if f(to_bool(l), to_bool(r)) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - _ => types::Value::Error(types::Error::Value), - } - } else { - types::Value::Error(types::Error::Argument) - } - } - - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_boolean_operator( - lhs: types::Value, - rhs: types::Value, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let lh = cast_value_to_boolean(lhs); - match lh { - types::Value::Boolean(l) => { - calculate_boolean_operator_rhs_boolean(l, cast_value_to_boolean(rhs), f) - } - types::Value::Iterator(lhs_vec) => { - calculate_boolean_operator_rhs_iterator(cast_value_to_boolean(rhs), lhs_vec, f) - } - types::Value::Blank => calculate_boolean_operator_rhs_boolean( - types::Boolean::True, - cast_value_to_boolean(rhs), - f, - ), - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_boolean_operator_or( - lhs: types::Value, - rhs: types::Value, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let lh = cast_value_to_boolean(lhs); - match lh { - types::Value::Boolean(l) => { - calculate_boolean_operator_rhs_boolean(l, cast_value_to_boolean(rhs), f) - } - types::Value::Error(_) => calculate_boolean_operator_rhs_error(cast_value_to_boolean(rhs)), - types::Value::Iterator(lhs_vec) => { - calculate_boolean_operator_rhs_iterator(cast_value_to_boolean(rhs), lhs_vec, f) - } - types::Value::Blank => calculate_boolean_operator_rhs_boolean( - types::Boolean::False, - cast_value_to_boolean(rhs), - f, - ), - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_boolean_operator_xor( - lhs: types::Value, - rhs: types::Value, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let lh = cast_value_to_boolean(lhs); - match lh { - types::Value::Boolean(l) => { - calculate_boolean_operator_rhs_boolean(l, cast_value_to_boolean(rhs), f) - } - types::Value::Error(_) => calculate_boolean_operator_rhs_error(cast_value_to_boolean(rhs)), - types::Value::Iterator(lhs_vec) => { - calculate_boolean_operator_rhs_iterator(cast_value_to_boolean(rhs), lhs_vec, f) - } - types::Value::Blank => calculate_boolean_operator_rhs_boolean( - types::Boolean::False, - cast_value_to_boolean(rhs), - f, - ), - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_abs(value: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match value { - types::Value::Boolean(_) => value, - types::Value::Error(_) => value, - types::Value::Text(_) => value, - types::Value::Number(l) => types::Value::Number(l.abs()), - types::Value::Iterator(_) => types::Value::Error(types::Error::Value), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Number(N::zero()), - } -} - -fn calculate_negation(value: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match value { - types::Value::Boolean(l) => { - if !(to_bool(l)) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Error(_) => value, - types::Value::Text(t) => { - let l = cast_text_to_boolean(&t); - match l { - Some(l) => { - if !(to_bool(l)) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - None => types::Value::Error(types::Error::Cast), - } - } - types::Value::Number(l) => { - if l.is_zero() { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Iterator(_) => types::Value::Error(types::Error::Value), - types::Value::Date(_) => types::Value::Error(types::Error::Value), - types::Value::Blank => types::Value::Boolean(types::Boolean::True), - } -} - -fn calculate_negate(value: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match value { - types::Value::Number(n) => types::Value::Number(-n), - types::Value::Iterator(mut value_vec) => { - let mut result_vec = Vec::new(); - while let Some(top) = value_vec.pop() { - result_vec.push(calculate_negate(top)); - } - types::Value::Iterator(result_vec) - } - types::Value::Blank => types::Value::Blank, - _ => types::Value::Error(types::Error::Value), - } -} - -fn cast_text_to_boolean(s: &str) -> Option { - if s.eq_ignore_ascii_case("TRUE") { - Some(types::Boolean::True) - } else if s.eq_ignore_ascii_case("FALSE") { - Some(types::Boolean::False) - } else { - None - } -} - -fn cast_value_to_boolean(value: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match value { - types::Value::Boolean(_) => value, - types::Value::Error(_) => value, - types::Value::Text(t) => { - let l = cast_text_to_boolean(&t); - match l { - Some(l) => { - if to_bool(l) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - None => types::Value::Error(types::Error::Cast), - } - } - types::Value::Number(l) => { - if !l.is_zero() { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Iterator(mut value_vec) => { - let mut boolean_vec = Vec::new(); - while let Some(top) = value_vec.pop() { - let value = cast_value_to_boolean(top); - boolean_vec.push(value); - } - types::Value::Iterator(boolean_vec) - } - types::Value::Date(_) => types::Value::Error(types::Error::Cast), - types::Value::Blank => types::Value::Blank, - } -} - -fn convert_iterator_to_result( - result: types::Value, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match result { - types::Value::Iterator(mut value_vec) => { - if let Some(mut temp) = value_vec.pop() { - while let Some(top) = value_vec.pop() { - temp = calculate_boolean_operator(temp, top, f); - } - match cast_value_to_boolean(temp) { - types::Value::Boolean(bool_result) => { - if to_bool(bool_result) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - _ => types::Value::Error(types::Error::Value), - } - } else { - types::Value::Error(types::Error::Argument) - } - } - _ => result, - } -} - -fn convert_iterator_to_result_or( - result: types::Value, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match result { - types::Value::Iterator(mut value_vec) => { - if let Some(mut temp) = value_vec.pop() { - while let Some(top) = value_vec.pop() { - temp = calculate_boolean_operator_or(temp, top, f); - } - match cast_value_to_boolean(temp) { - types::Value::Boolean(bool_result) => { - if to_bool(bool_result) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - _ => types::Value::Error(types::Error::Value), - } - } else { - types::Value::Error(types::Error::Argument) - } - } - _ => result, - } -} - -fn convert_iterator_to_result_xor( - result: types::Value, - f: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match result { - types::Value::Iterator(mut value_vec) => { - if let Some(mut temp) = value_vec.pop() { - while let Some(top) = value_vec.pop() { - temp = calculate_boolean_operator_xor(temp, top, f); - } - match cast_value_to_boolean(temp) { - types::Value::Boolean(bool_result) => { - if to_bool(bool_result) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - _ => types::Value::Error(types::Error::Value), - } - } else { - types::Value::Error(types::Error::Argument) - } - } - _ => result, - } -} - -fn get_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value) -where - N: XlNum, - ::Err: Debug, -{ - ( - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }, - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }, - ) -} - -fn get_value( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - } -} - -fn get_date_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value) -where - N: XlNum, - ::Err: Debug, -{ - ( - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }, - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }, - ) -} - -fn get_number_and_string_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value) -where - N: XlNum, - ::Err: Debug, -{ - if exp.values.len() == 1 { - ( - types::Value::Number(N::one()), - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }, - ) - } else { - ( - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }, - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }, - ) - } -} - -fn get_iff_values( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> (types::Value, types::Value, types::Value) -where - N: XlNum, - ::Err: Debug, -{ - ( - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Blank, - }, - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Blank, - }, - match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Blank, - }, - ) -} - -fn calculate_iterator( - mut vec: Vec>, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let mut value_vec = Vec::new(); - while let Some(top) = vec.pop() { - value_vec.push(calculate_formula(top, f)); - } - types::Value::Iterator(value_vec) -} - -fn calculate_reference( - string: String, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match f { - Some(f) => match f(string) { - types::Value::Number(x) => types::Value::Number(x), - types::Value::Text(s) => calculate_formula( - parse_formula::parse_string_to_formula(&s, None::>), - Some(f), - ), - types::Value::Boolean(x) => types::Value::Boolean(x), - types::Value::Error(types::Error::Value) => types::Value::Error(types::Error::Value), - types::Value::Iterator(v) => types::Value::Iterator(v), - types::Value::Date(d) => types::Value::Date(d), - types::Value::Blank => types::Value::Blank, - _ => types::Value::Error(types::Error::Reference), - }, - None => types::Value::Error(types::Error::Reference), - } -} - -fn calculate_bool( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_bool: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let mut result = match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }; - result = cast_value_to_boolean(result); - while let Some(top) = exp.values.pop() { - result = calculate_boolean_operator(result, calculate_formula(top, f), f_bool); - } - convert_iterator_to_result(result, f_bool) -} - -fn calculate_or( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_bool: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let mut result = match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }; - result = cast_value_to_boolean(result); - while let Some(top) = exp.values.pop() { - result = calculate_boolean_operator_or(result, calculate_formula(top, f), f_bool); - } - convert_iterator_to_result_or(result, f_bool) -} - -fn calculate_xor( - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_bool: fn(bool1: bool, bool2: bool) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let mut result = match exp.values.pop() { - Some(formula) => calculate_formula(formula, f), - None => types::Value::Error(types::Error::Argument), - }; - result = cast_value_to_boolean(result); - while let Some(top) = exp.values.pop() { - result = calculate_boolean_operator_xor(result, calculate_formula(top, f), f_bool); - } - convert_iterator_to_result_xor(result, f_bool) -} - -fn calculate_collective_operator( - mut collective_value: types::Value, - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - while let Some(top) = exp.values.pop() { - collective_value = - calculate_numeric_operator(collective_value, calculate_formula(top, f), f_collective); - } - collective_value -} - -fn calculate_collective_product_operator( - mut collective_value: types::Value, - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - while let Some(top) = exp.values.pop() { - collective_value = calculate_numeric_product_operator( - collective_value, - calculate_formula(top, f), - f_collective, - ); - } - match collective_value { - types::Value::Blank => types::Value::Number(N::zero()), - _ => collective_value, - } -} - -fn calculate_average( - mut collective_value: types::Value, - mut exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, - f_collective: fn(num1: N, num2: N) -> N, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let mut element_count = 0; - while let Some(top) = exp.values.pop() { - element_count += 1; - collective_value = calculate_average_operator( - &mut element_count, - collective_value, - calculate_formula(top, f), - f_collective, - ); - } - if element_count == 0 { - types::Value::Error(types::Error::Div0) - } else { - calculate_numeric_operator( - collective_value, - types::Value::Number(N::from_i64(element_count).unwrap()), - calculate_divide_operator, - ) - } -} - -fn calculate_days(date_pair: (types::Value, types::Value)) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let begin_of_date: DateTime = - DateTime::parse_from_rfc3339("1900-01-01T02:00:00.000Z") - .ok() - .unwrap(); - let (start, end) = date_pair; - match (start, end) { - (types::Value::Date(start), types::Value::Date(end)) => { - types::Value::Number(N::from_i64((end - start).num_days()).unwrap()) - } - (types::Value::Blank, types::Value::Date(end)) => { - types::Value::Number(N::from_i64((end - begin_of_date).num_days()).unwrap()) - } - (types::Value::Date(start), types::Value::Blank) => { - types::Value::Number(N::from_i64((begin_of_date - start).num_days()).unwrap()) - } - (types::Value::Blank, types::Value::Blank) => types::Value::Number(N::zero()), - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_right(number_string: (types::Value, types::Value)) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let (number, string) = number_string; - let trim_length = match number { - types::Value::Number(x) => x.as_(), - _ => 0, - }; - - let trimmed_string = match string { - types::Value::Text(s) => { - let temp: &'static str = Box::leak(s.into_boxed_str()); - let len = temp.len(); - let start = if trim_length > len { len } else { trim_length }; - &temp[(len - start)..] - } - _ => "", - }; - types::Value::Text(trimmed_string.to_string()) -} - -fn calculate_left(number_string: (types::Value, types::Value)) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let (number, string) = number_string; - let trim_length = match number { - types::Value::Number(x) => x.as_(), - _ => 0, - }; - - let trimmed_string = match string { - types::Value::Text(s) => { - let temp: &'static str = Box::leak(s.into_boxed_str()); - let len = temp.len(); - let end = if trim_length > len { len } else { trim_length }; - &temp[..end] - } - _ => "", - }; - types::Value::Text(trimmed_string.to_string()) -} - -fn calculate_iff( - iff_arguments: (types::Value, types::Value, types::Value), -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - let (false_value, true_value, bool_expression) = iff_arguments; - match bool_expression { - types::Value::Boolean(bool_value) => { - if to_bool(bool_value) { - true_value - } else { - false_value - } - } - types::Value::Number(number_value) => { - if number_value.is_zero() { - false_value - } else { - true_value - } - } - types::Value::Blank => false_value, - types::Value::Error(_) => bool_expression, - types::Value::Text(_) => types::Value::Error(types::Error::Value), - _ => types::Value::Error(types::Error::Value), - } -} - -fn calculate_isblank(value: types::Value) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match value { - types::Value::Blank => types::Value::Boolean(types::Boolean::True), - types::Value::Text(s) => { - if s.is_empty() { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } - } - types::Value::Error(types::Error::Value) => types::Value::Boolean(types::Boolean::True), - types::Value::Error(types::Error::Reference) => types::Value::Boolean(types::Boolean::True), - _ => types::Value::Boolean(types::Boolean::False), - } -} - -fn calculate_function( - func: types::Function, - exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match func { - types::Function::Abs => calculate_abs(get_value(exp, f)), - types::Function::Sum => { - calculate_collective_operator(types::Value::Number(N::zero()), exp, f, |n1, n2| n1 + n2) - } - types::Function::Product => { - calculate_collective_product_operator(types::Value::Blank, exp, f, |n1, n2| n1 * n2) - } - types::Function::Average => { - calculate_average(types::Value::Number(N::zero()), exp, f, |n1, n2| n1 + n2) - } - types::Function::Or => calculate_or(exp, f, |n1, n2| n1 || n2), - types::Function::And => calculate_bool(exp, f, |n1, n2| n1 && n2), - types::Function::Xor => calculate_xor(exp, f, |n1, n2| n1 ^ n2), - types::Function::Not => calculate_negation(get_value(exp, f)), - types::Function::Negate => calculate_negate(get_value(exp, f)), - types::Function::Days => calculate_days(get_date_values(exp, f)), - types::Function::Right => calculate_right(get_number_and_string_values(exp, f)), - types::Function::Left => calculate_left(get_number_and_string_values(exp, f)), - types::Function::Iff => calculate_iff(get_iff_values(exp, f)), - types::Function::IsBlank => calculate_isblank(get_value(exp, f)), - } -} - -fn calculate_operation( - exp: types::Expression, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match exp.op { - types::Operator::Plus => { - let (value2, value1) = get_values(exp, f); - match value1 { - types::Value::Date(d) => add_days_to_date(d, value2), - _ => calculate_numeric_operator(value1, value2, |n1, n2| n1 + n2), - } - } - - types::Operator::Minus => { - let (value2, value1) = get_values(exp, f); - match value1 { - types::Value::Date(d) => subtract_days_from_date(d, value2), - _ => calculate_numeric_operator(value1, value2, |n1, n2| n1 - n2), - } - } - - types::Operator::Multiply => { - let (value2, value1) = get_values(exp, f); - calculate_numeric_operator(value1, value2, |n1, n2| n1 * n2) - } - types::Operator::Divide => { - let (value2, value1) = get_values(exp, f); - match value2 { - types::Value::Number(n) if n.is_zero() => types::Value::Error(types::Error::Div0), - _ => calculate_numeric_operator(value1, value2, calculate_divide_operator), - } - } - types::Operator::Power => { - let (value2, value1) = get_values(exp, f); - calculate_numeric_operator(value1, value2, calculate_power_operator) - } - types::Operator::Concat => { - let (value2, value1) = get_values(exp, f); - calculate_string_operator(value1, value2, calculate_concat_operator) - } - types::Operator::Equal => { - let (value2, value1) = get_values(exp, f); - match (value1.clone(), value2.clone()) { - (types::Value::Date(l), types::Value::Date(r)) => { - compare_dates(l, r, |d1, d2| d1 == d2) - } - (types::Value::Text(l), types::Value::Text(r)) => { - compare_strings(l, r, |s1, s2| s1 == s2) - } - _ => calculate_comparison_operator(value1, value2, |n1, n2| { - (n1 - n2).abs().is_zero() - }), - } - } - types::Operator::NotEqual => { - let (value2, value1) = get_values(exp, f); - match (value1.clone(), value2.clone()) { - (types::Value::Date(l), types::Value::Date(r)) => { - compare_dates(l, r, |d1, d2| d1 != d2) - } - (types::Value::Text(l), types::Value::Text(r)) => { - compare_strings(l, r, |s1, s2| s1 != s2) - } - _ => calculate_comparison_operator(value1, value2, |n1, n2| { - (n1 - n2).abs() > N::zero() - }), - } - } - types::Operator::Greater => { - let (value2, value1) = get_values(exp, f); - match (value1.clone(), value2.clone()) { - (types::Value::Date(l), types::Value::Date(r)) => { - compare_dates(l, r, |d1, d2| d1 > d2) - } - _ => calculate_comparison_operator(value1, value2, |n1, n2| n1 > n2), - } - } - types::Operator::Less => { - let (value2, value1) = get_values(exp, f); - match (value1.clone(), value2.clone()) { - (types::Value::Date(l), types::Value::Date(r)) => { - compare_dates(l, r, |d1, d2| d1 < d2) - } - _ => calculate_comparison_operator(value1, value2, |n1, n2| n1 < n2), - } - } - types::Operator::GreaterOrEqual => { - let (value2, value1) = get_values(exp, f); - match (value1.clone(), value2.clone()) { - (types::Value::Date(l), types::Value::Date(r)) => { - compare_dates(l, r, |d1, d2| d1 >= d2) - } - _ => calculate_comparison_operator(value1, value2, |n1, n2| n1 >= n2), - } - } - types::Operator::LessOrEqual => { - let (value2, value1) = get_values(exp, f); - match (value1.clone(), value2.clone()) { - (types::Value::Date(l), types::Value::Date(r)) => { - compare_dates(l, r, |d1, d2| d1 <= d2) - } - _ => calculate_comparison_operator(value1, value2, |n1, n2| n1 <= n2), - } - } - types::Operator::Function(func) => calculate_function(func, exp, f), - } -} - -fn compare_dates( - date1: DateTime, - date2: DateTime, - f: fn(d1: DateTime, d2: DateTime) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - if f(date1, date2) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } -} - -fn compare_strings( - string1: String, - string2: String, - f: fn(s1: String, s2: String) -> bool, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - if f(string1, string2) { - types::Value::Boolean(types::Boolean::True) - } else { - types::Value::Boolean(types::Boolean::False) - } -} - -/// Evaluates a string that was parsed and stored in Expression Struct. -/// Takes an optional closure with the trait bound Fn(String) -> types::Value. -pub fn calculate_formula( - formula: types::Formula, - f: Option<&impl Fn(String) -> types::Value>, -) -> types::Value -where - N: XlNum, - ::Err: Debug, -{ - match formula { - types::Formula::Operation(exp) => calculate_operation(exp, f), - types::Formula::Value(val) => val, - types::Formula::Reference(string) => calculate_reference(string, f), - types::Formula::Iterator(vec) => calculate_iterator(vec, f), - } -} - -/// Converts a result from Value Enum to a printable string. -pub fn result_to_string(value: types::Value) -> String -where - N: XlNum, -{ - match value { - types::Value::Number(number) => show_number(number), - types::Value::Text(text) => text, - types::Value::Error(error) => show_error(error), - types::Value::Boolean(boolean) => show_boolean(boolean), - types::Value::Iterator(value_vec) => show_iterator(value_vec), - types::Value::Date(date) => date.to_string(), - types::Value::Blank => show_blank::(), - } -} - -fn show_number(number: N) -> String -where - N: XlNum, -{ - if number.is_infinite() { - String::from("#DIV/0!") - } else { - number.to_string() - } -} - -fn show_error(error: types::Error) -> String { - match error { - types::Error::Div0 => String::from("#DIV/0!"), - types::Error::Cast => String::from("#CAST!"), - types::Error::Parse => String::from("#PARSE!"), - types::Error::Value => String::from("#VALUE!"), - types::Error::Argument => String::from("#ARG!"), - types::Error::Reference => String::from("#REF!"), - } -} - -fn show_boolean(boolean: types::Boolean) -> String { - match boolean { - types::Boolean::True => String::from("TRUE"), - types::Boolean::False => String::from("FALSE"), - } -} - -fn show_iterator(mut value_vec: Vec>) -> String -where - N: XlNum, -{ - value_vec.reverse(); - let mut result = '{'.to_string(); - while let Some(top) = value_vec.pop() { - result = result + &result_to_string(top); - result = result + &','.to_string(); - } - result = result.trim_end_matches(',').to_string(); - result = result + &'}'.to_string(); - result -} - -fn show_blank() -> String -where - N: XlNum, -{ - show_number(N::zero()) - //String::from("BLANK") -} diff --git a/src/calculate/args.rs b/src/calculate/args.rs new file mode 100644 index 0000000..df574ed --- /dev/null +++ b/src/calculate/args.rs @@ -0,0 +1,90 @@ +use super::calculate_formula; +use crate::types::{self, XlNum}; +use std::{fmt::Debug, str::FromStr}; + +pub fn get_unary_function_arg( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + } +} + +pub fn get_binary_function_args( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ + let rhs = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }; + let lhs = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }; + (lhs, rhs) +} + +pub fn get_ternary_function_args( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ + let last = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Blank, + }; + let middle = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Blank, + }; + let first = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Blank, + }; + (first, middle, last) +} + +pub fn get_number_and_string_values( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ + let (number, string) = if exp.values.len() == 1 { + ( + types::Value::Number(N::one()), + match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }, + ) + } else { + ( + match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }, + match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }, + ) + }; + (string, number) +} diff --git a/src/calculate/display.rs b/src/calculate/display.rs new file mode 100644 index 0000000..bd23707 --- /dev/null +++ b/src/calculate/display.rs @@ -0,0 +1,32 @@ +use super::result_to_string; +use crate::types::{self, XlNum}; + +pub fn show_number(number: N) -> String +where + N: XlNum, +{ + if number.is_infinite() { + types::Error::Div0.to_string() + } else { + number.to_string() + } +} + +pub fn show_iterator(value_vec: Vec>) -> String +where + N: XlNum, +{ + let mut result = "{".to_string(); + for top in value_vec { + result = format!("{result}{},", result_to_string(top)); + } + result = result.trim_end_matches(',').to_string(); + result + "}" +} + +pub fn show_blank() -> String +where + N: XlNum, +{ + show_number(N::zero()) +} diff --git a/src/calculate/iterator.rs b/src/calculate/iterator.rs new file mode 100644 index 0000000..f23ba42 --- /dev/null +++ b/src/calculate/iterator.rs @@ -0,0 +1,48 @@ +use super::{ + calculate_formula, + operation::boolean::{calculate_boolean_operator, cast_value_to_boolean}, +}; +use crate::types::{self, XlNum}; +use std::{fmt::Debug, str::FromStr}; + +pub fn convert_iterator_to_result( + result: types::Value, + f: fn(bool, bool) -> bool, + allow_error: bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match result { + types::Value::Iterator(mut value_vec) => { + if let Some(mut temp) = value_vec.pop() { + while let Some(top) = value_vec.pop() { + temp = calculate_boolean_operator(temp, top, f, allow_error); + } + match cast_value_to_boolean(temp) { + value @ types::Value::Boolean(_) => value, + _ => types::Value::Error(types::Error::Value), + } + } else { + types::Value::Error(types::Error::Argument) + } + } + _ => result, + } +} + +pub fn calculate_iterator( + mut vec: Vec>, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let mut value_vec = Vec::new(); + while let Some(top) = vec.pop() { + value_vec.push(calculate_formula(top, f)); + } + types::Value::Iterator(value_vec) +} diff --git a/src/calculate/mod.rs b/src/calculate/mod.rs new file mode 100644 index 0000000..6341b74 --- /dev/null +++ b/src/calculate/mod.rs @@ -0,0 +1,48 @@ +mod args; +mod display; +mod iterator; +mod operation; +mod reference; + +use self::{ + display::{show_blank, show_iterator, show_number}, + iterator::calculate_iterator, + operation::calculate_operation, + reference::calculate_reference, +}; +use crate::types::{self, XlNum}; +use std::{fmt::Debug, str::FromStr}; + +/// Evaluates a string that was parsed and stored in Expression Struct. +/// Takes an optional closure with the trait bound Fn(String) -> types::Value. +pub fn calculate_formula( + formula: types::Formula, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match formula { + types::Formula::Operation(exp) => calculate_operation(exp, f), + types::Formula::Value(val) => val, + types::Formula::Reference(string) => calculate_reference(string, f), + types::Formula::Iterator(vec) => calculate_iterator(vec, f), + } +} + +/// Converts a result from Value Enum to a printable string. +pub fn result_to_string(value: types::Value) -> String +where + N: XlNum, +{ + match value { + types::Value::Number(number) => show_number(number), + types::Value::Text(text) => text, + types::Value::Error(error) => error.to_string(), + types::Value::Boolean(boolean) => boolean.to_string(), + types::Value::Iterator(value_vec) => show_iterator(value_vec), + types::Value::Date(date) => date.to_string(), + types::Value::Blank => show_blank::(), + } +} diff --git a/src/calculate/operation/boolean.rs b/src/calculate/operation/boolean.rs new file mode 100644 index 0000000..c78e7c0 --- /dev/null +++ b/src/calculate/operation/boolean.rs @@ -0,0 +1,279 @@ +use crate::{ + calculate::{calculate_formula, iterator::convert_iterator_to_result}, + types::{self, XlNum}, +}; +use std::{fmt::Debug, str::FromStr}; + +fn calculate_boolean_operator_rhs_boolean( + l: types::Boolean, + rh: types::Value, + f: fn(bool, bool) -> bool, + allow_error: bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rh { + types::Value::Boolean(r) => types::Value::Boolean(f(l.into(), r.into()).into()), + types::Value::Iterator(mut value_vec) => { + if let Some(mut temp) = value_vec.pop() { + while let Some(top) = value_vec.pop() { + temp = calculate_boolean_operator(temp, top, f, allow_error); + } + let rhs = cast_value_to_boolean(temp); + match rhs { + types::Value::Boolean(r) => types::Value::Boolean(f(l.into(), r.into()).into()), + _ => types::Value::Error(types::Error::Value), + } + } else { + types::Value::Error(types::Error::Argument) + } + } + types::Value::Blank => types::Value::Boolean(l), + _ => types::Value::Error(types::Error::Value), + } +} + +fn calculate_boolean_operator_rhs_error(rh: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rh { + types::Value::Boolean(_) => rh, + types::Value::Error(_) => types::Value::Error(types::Error::Cast), + _ => types::Value::Error(types::Error::Value), + } +} + +fn calculate_boolean_operator_rhs_iterator( + rh: types::Value, + mut lhs_vec: Vec>, + f: fn(bool, bool) -> bool, + allow_error: bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rh { + types::Value::Boolean(r) => { + if let Some(mut temp) = lhs_vec.pop() { + while let Some(top) = lhs_vec.pop() { + temp = calculate_boolean_operator(temp, top, f, allow_error); + } + let lhs = cast_value_to_boolean(temp); + match lhs { + types::Value::Boolean(l) => types::Value::Boolean(f(l.into(), r.into()).into()), + _ => types::Value::Error(types::Error::Value), + } + } else { + types::Value::Error(types::Error::Argument) + } + } + + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_boolean_operator( + lhs: types::Value, + rhs: types::Value, + f: fn(bool, bool) -> bool, + allow_error: bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let lh = cast_value_to_boolean(lhs); + match lh { + types::Value::Boolean(l) => { + calculate_boolean_operator_rhs_boolean(l, cast_value_to_boolean(rhs), f, allow_error) + } + types::Value::Error(_) if allow_error => { + calculate_boolean_operator_rhs_error(cast_value_to_boolean(rhs)) + } + types::Value::Iterator(lhs_vec) => calculate_boolean_operator_rhs_iterator( + cast_value_to_boolean(rhs), + lhs_vec, + f, + allow_error, + ), + types::Value::Blank => calculate_boolean_operator_rhs_boolean( + (!allow_error).into(), + cast_value_to_boolean(rhs), + f, + allow_error, + ), + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_comparison_operator( + lhs: types::Value, + rhs: types::Value, + f: impl Fn(N, N) -> bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match lhs { + types::Value::Text(l) => match rhs { + types::Value::Text(r) => types::Value::Boolean(types::Boolean::from(l.eq(&r))), + types::Value::Blank => types::Value::Boolean(l.is_empty().into()), + _ => types::Value::Error(types::Error::Value), + }, + types::Value::Number(l) => match rhs { + types::Value::Number(r) => types::Value::Boolean(types::Boolean::from(f(l, r))), + types::Value::Blank => types::Value::Boolean(types::Boolean::False), + _ => types::Value::Error(types::Error::Value), + }, + types::Value::Blank => match rhs { + types::Value::Number(_) => types::Value::Boolean(types::Boolean::False), + types::Value::Text(r) => types::Value::Boolean(r.is_empty().into()), + types::Value::Blank => types::Value::Boolean(types::Boolean::True), + _ => types::Value::Error(types::Error::Value), + }, + types::Value::Boolean(_) + | types::Value::Error(_) + | types::Value::Iterator(_) + | types::Value::Date(_) => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_negation(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match value { + types::Value::Boolean(l) => types::Value::Boolean(match l { + types::Boolean::True => types::Boolean::False, + types::Boolean::False => types::Boolean::True, + }), + types::Value::Error(_) => value, + types::Value::Text(t) => { + let l = cast_text_to_boolean(&t); + match l { + Some(l) => types::Value::Boolean(match l { + types::Boolean::True => types::Boolean::False, + types::Boolean::False => types::Boolean::True, + }), + None => types::Value::Error(types::Error::Cast), + } + } + types::Value::Number(l) => types::Value::Boolean(l.is_zero().into()), + types::Value::Blank => types::Value::Boolean(types::Boolean::True), + types::Value::Iterator(_) | types::Value::Date(_) => { + types::Value::Error(types::Error::Value) + } + } +} + +pub fn calculate_negate(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match value { + types::Value::Number(n) => types::Value::Number(-n), + types::Value::Iterator(mut value_vec) => { + let mut result_vec = Vec::new(); + while let Some(top) = value_vec.pop() { + result_vec.push(calculate_negate(top)); + } + types::Value::Iterator(result_vec) + } + types::Value::Blank => types::Value::Blank, + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_boolean( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, + f_bool: fn(bool, bool) -> bool, + allow_error: bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let mut result = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }; + result = cast_value_to_boolean(result); + while let Some(top) = exp.values.pop() { + result = calculate_boolean_operator(result, calculate_formula(top, f), f_bool, allow_error); + } + convert_iterator_to_result(result, f_bool, allow_error) +} + +pub fn calculate_isblank(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match value { + types::Value::Text(s) => { + if s.is_empty() { + types::Value::Boolean(types::Boolean::True) + } else { + types::Value::Boolean(types::Boolean::False) + } + } + types::Value::Blank + | types::Value::Error(types::Error::Value) + | types::Value::Error(types::Error::Reference) => { + types::Value::Boolean(types::Boolean::True) + } + _ => types::Value::Boolean(types::Boolean::False), + } +} + +fn cast_text_to_boolean(s: &str) -> Option { + if s.eq_ignore_ascii_case("TRUE") { + Some(types::Boolean::True) + } else if s.eq_ignore_ascii_case("FALSE") { + Some(types::Boolean::False) + } else { + None + } +} + +pub fn cast_value_to_boolean(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match value { + types::Value::Boolean(_) | types::Value::Blank | types::Value::Error(_) => value, + types::Value::Text(t) => { + let l = cast_text_to_boolean(&t); + match l { + Some(l) => types::Value::Boolean(l), + None => types::Value::Error(types::Error::Cast), + } + } + types::Value::Number(l) => { + if !l.is_zero() { + types::Value::Boolean(types::Boolean::True) + } else { + types::Value::Boolean(types::Boolean::False) + } + } + types::Value::Iterator(mut value_vec) => { + let mut boolean_vec = Vec::new(); + while let Some(top) = value_vec.pop() { + let value = cast_value_to_boolean(top); + boolean_vec.push(value); + } + types::Value::Iterator(boolean_vec) + } + types::Value::Date(_) => types::Value::Error(types::Error::Cast), + } +} diff --git a/src/calculate/operation/date.rs b/src/calculate/operation/date.rs new file mode 100644 index 0000000..02c5dd8 --- /dev/null +++ b/src/calculate/operation/date.rs @@ -0,0 +1,103 @@ +use crate::types::{self, XlNum}; +use chrono::{DateTime, Datelike, Duration, FixedOffset}; +use std::{fmt::Debug, str::FromStr}; + +pub fn add_days_to_date(d: DateTime, rhs: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Number(x) => types::Value::Date(d + Duration::days(x.as_())), + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn subtract_days_from_date(d: DateTime, rhs: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Number(x) => types::Value::Date(d - Duration::days(x.as_())), + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_days(start: types::Value, end: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let begin_of_date: DateTime = + DateTime::parse_from_rfc3339("1900-01-01T02:00:00.000Z") + .ok() + .unwrap(); + match (start, end) { + (types::Value::Date(start), types::Value::Date(end)) => { + types::Value::Number(N::from_i64((end - start).num_days()).unwrap()) + } + (types::Value::Blank, types::Value::Date(end)) => { + types::Value::Number(N::from_i64((end - begin_of_date).num_days()).unwrap()) + } + (types::Value::Date(start), types::Value::Blank) => { + types::Value::Number(N::from_i64((begin_of_date - start).num_days()).unwrap()) + } + (types::Value::Blank, types::Value::Blank) => types::Value::Number(N::zero()), + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_year(date_value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match date_value { + types::Value::Date(d) => match N::from_i32(d.year()) { + Some(year) => types::Value::Number(year), + None => types::Value::Error(types::Error::Value), + }, + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_month(date_value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match date_value { + types::Value::Date(d) => match N::from_u32(d.month()) { + Some(year) => types::Value::Number(year), + None => types::Value::Error(types::Error::Value), + }, + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_day(date_value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match date_value { + types::Value::Date(d) => match N::from_u32(d.day()) { + Some(year) => types::Value::Number(year), + None => types::Value::Error(types::Error::Value), + }, + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn compare_dates( + date1: DateTime, + date2: DateTime, + f: fn(d1: DateTime, d2: DateTime) -> bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + types::Value::Boolean(f(date1, date2).into()) +} diff --git a/src/calculate/operation/function.rs b/src/calculate/operation/function.rs new file mode 100644 index 0000000..c98b3ae --- /dev/null +++ b/src/calculate/operation/function.rs @@ -0,0 +1,139 @@ +use super::{ + boolean::{calculate_boolean, calculate_isblank, calculate_negate, calculate_negation}, + date::{calculate_day, calculate_days, calculate_month, calculate_year}, + number::{ + calculate_abs, calculate_average, calculate_collective_operator, + calculate_collective_product_operator, + }, +}; +use crate::{ + calculate::args::{ + get_binary_function_args, get_number_and_string_values, get_ternary_function_args, + get_unary_function_arg, + }, + types::{self, XlNum}, +}; +use std::{fmt::Debug, str::FromStr}; + +fn calculate_right(string: types::Value, number: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let trim_length = match number { + types::Value::Number(x) => x.as_(), + _ => 0, + }; + + let trimmed_string = match string { + types::Value::Text(s) => { + let temp: &'static str = Box::leak(s.into_boxed_str()); + let len = temp.len(); + let start = if trim_length > len { len } else { trim_length }; + &temp[(len - start)..] + } + _ => "", + }; + types::Value::Text(trimmed_string.to_string()) +} + +fn calculate_left(string: types::Value, number: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let trim_length = match number { + types::Value::Number(x) => x.as_(), + _ => 0, + }; + + let trimmed_string = match string { + types::Value::Text(s) => { + let temp: &'static str = Box::leak(s.into_boxed_str()); + let len = temp.len(); + let end = if trim_length > len { len } else { trim_length }; + &temp[..end] + } + _ => "", + }; + types::Value::Text(trimmed_string.to_string()) +} + +fn calculate_iff( + bool_expression: types::Value, + true_value: types::Value, + false_value: types::Value, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match bool_expression { + types::Value::Boolean(bool_value) => { + if bool_value.into() { + true_value + } else { + false_value + } + } + types::Value::Number(number_value) => { + if number_value.is_zero() { + false_value + } else { + true_value + } + } + types::Value::Blank => false_value, + types::Value::Error(_) => bool_expression, + types::Value::Text(_) => types::Value::Error(types::Error::Value), + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_function( + func: types::Function, + exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match func { + types::Function::Abs => calculate_abs(get_unary_function_arg(exp, f)), + types::Function::Sum => { + calculate_collective_operator(types::Value::Number(N::zero()), exp, f, |n1, n2| n1 + n2) + } + types::Function::Product => { + calculate_collective_product_operator(types::Value::Blank, exp, f, |n1, n2| n1 * n2) + } + types::Function::Average => { + calculate_average(types::Value::Number(N::zero()), exp, f, |n1, n2| n1 + n2) + } + types::Function::Or => calculate_boolean(exp, f, |n1, n2| n1 || n2, true), + types::Function::And => calculate_boolean(exp, f, |n1, n2| n1 && n2, false), + types::Function::Xor => calculate_boolean(exp, f, |n1, n2| n1 ^ n2, true), + types::Function::Not => calculate_negation(get_unary_function_arg(exp, f)), + types::Function::Negate => calculate_negate(get_unary_function_arg(exp, f)), + types::Function::Days => { + let (end, start) = get_binary_function_args(exp, f); + calculate_days(start, end) + } + types::Function::Right => { + let (string, number) = get_number_and_string_values(exp, f); + calculate_right(string, number) + } + types::Function::Left => { + let (string, number) = get_number_and_string_values(exp, f); + calculate_left(string, number) + } + types::Function::Iff => { + let (bool_expression, true_value, false_value) = get_ternary_function_args(exp, f); + calculate_iff(bool_expression, true_value, false_value) + } + types::Function::IsBlank => calculate_isblank(get_unary_function_arg(exp, f)), + types::Function::Year => calculate_year(get_unary_function_arg(exp, f)), + types::Function::Month => calculate_month(get_unary_function_arg(exp, f)), + types::Function::Day => calculate_day(get_unary_function_arg(exp, f)), + } +} diff --git a/src/calculate/operation/mod.rs b/src/calculate/operation/mod.rs new file mode 100644 index 0000000..cdbc674 --- /dev/null +++ b/src/calculate/operation/mod.rs @@ -0,0 +1,124 @@ +pub mod boolean; +pub mod date; +pub mod function; +pub mod number; +pub mod string; + +use self::{ + boolean::calculate_comparison_operator, + date::{add_days_to_date, compare_dates, subtract_days_from_date}, + function::calculate_function, + number::{calculate_divide_operator, calculate_numeric_operator, calculate_power_operator}, + string::{calculate_concat_operator, calculate_string_operator, compare_strings}, +}; +use super::args::get_binary_function_args; +use crate::types::{self, XlNum}; +use std::{fmt::Debug, str::FromStr}; + +pub fn calculate_operation( + exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match exp.op { + types::Operator::Plus => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match lhs { + types::Value::Date(d) => add_days_to_date(d, rhs), + _ => calculate_numeric_operator(lhs, rhs, |n1, n2| n1 + n2), + } + } + + types::Operator::Minus => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match lhs { + types::Value::Date(d) => subtract_days_from_date(d, rhs), + _ => calculate_numeric_operator(lhs, rhs, |n1, n2| n1 - n2), + } + } + + types::Operator::Multiply => { + let (lhs, rhs) = get_binary_function_args(exp, f); + calculate_numeric_operator(lhs, rhs, |n1, n2| n1 * n2) + } + types::Operator::Divide => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match rhs { + types::Value::Number(n) if n.is_zero() => types::Value::Error(types::Error::Div0), + _ => calculate_numeric_operator(lhs, rhs, calculate_divide_operator), + } + } + types::Operator::Power => { + let (lhs, rhs) = get_binary_function_args(exp, f); + calculate_numeric_operator(lhs, rhs, calculate_power_operator) + } + types::Operator::Concat => { + let (lhs, rhs) = get_binary_function_args(exp, f); + calculate_string_operator(lhs, rhs, calculate_concat_operator) + } + types::Operator::Equal => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match (lhs.clone(), rhs.clone()) { + (types::Value::Date(l), types::Value::Date(r)) => { + compare_dates(l, r, |d1, d2| d1 == d2) + } + (types::Value::Text(l), types::Value::Text(r)) => { + compare_strings(l, r, |s1, s2| s1 == s2) + } + _ => calculate_comparison_operator(lhs, rhs, |n1, n2| (n1 - n2).abs().is_zero()), + } + } + types::Operator::NotEqual => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match (lhs.clone(), rhs.clone()) { + (types::Value::Date(l), types::Value::Date(r)) => { + compare_dates(l, r, |d1, d2| d1 != d2) + } + (types::Value::Text(l), types::Value::Text(r)) => { + compare_strings(l, r, |s1, s2| s1 != s2) + } + _ => calculate_comparison_operator(lhs, rhs, |n1, n2| (n1 - n2).abs() > N::zero()), + } + } + types::Operator::Greater => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match (lhs.clone(), rhs.clone()) { + (types::Value::Date(l), types::Value::Date(r)) => { + compare_dates(l, r, |d1, d2| d1 > d2) + } + _ => calculate_comparison_operator(lhs, rhs, |n1, n2| n1 > n2), + } + } + types::Operator::Less => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match (lhs.clone(), rhs.clone()) { + (types::Value::Date(l), types::Value::Date(r)) => { + compare_dates(l, r, |d1, d2| d1 < d2) + } + _ => calculate_comparison_operator(lhs, rhs, |n1, n2| n1 < n2), + } + } + types::Operator::GreaterOrEqual => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match (lhs.clone(), rhs.clone()) { + (types::Value::Date(l), types::Value::Date(r)) => { + compare_dates(l, r, |d1, d2| d1 >= d2) + } + _ => calculate_comparison_operator(lhs, rhs, |n1, n2| n1 >= n2), + } + } + types::Operator::LessOrEqual => { + let (lhs, rhs) = get_binary_function_args(exp, f); + match (lhs.clone(), rhs.clone()) { + (types::Value::Date(l), types::Value::Date(r)) => { + compare_dates(l, r, |d1, d2| d1 <= d2) + } + _ => calculate_comparison_operator(lhs, rhs, |n1, n2| n1 <= n2), + } + } + types::Operator::Function(func) => calculate_function(func, exp, f), + } +} diff --git a/src/calculate/operation/number.rs b/src/calculate/operation/number.rs new file mode 100644 index 0000000..ed2a5f5 --- /dev/null +++ b/src/calculate/operation/number.rs @@ -0,0 +1,386 @@ +use crate::{ + calculate::calculate_formula, + types::{self, XlNum}, +}; +use std::{fmt::Debug, str::FromStr}; + +pub fn calculate_divide_operator(num1: N, num2: N) -> N +where + N: XlNum, + ::Err: Debug, +{ + num1 / num2 +} + +fn is_float_int(num: N) -> bool +where + N: XlNum, + ::Err: Debug, +{ + num.fract().is_zero() +} + +pub fn calculate_power_operator(num1: N, num2: N) -> N +where + N: XlNum, + ::Err: Debug, +{ + if is_float_int(num2) { + num1.powi(num2.as_()) + } else { + num1.powf(num2) + } +} + +fn calculate_numeric_operator_rhs_text( + t: String, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match t.parse::() { + Ok(nl) => match rhs { + types::Value::Boolean(_) | types::Value::Error(_) => rhs, + types::Value::Text(t) => match t.parse::() { + Ok(nr) => types::Value::Number(f(nl, nr)), + Err(_) => types::Value::Error(types::Error::Cast), + }, + types::Value::Number(r) => types::Value::Number(f(nl, r)), + types::Value::Iterator(_) | types::Value::Date(_) | types::Value::Blank => { + types::Value::Error(types::Error::Value) + } + }, + Err(_) => types::Value::Error(types::Error::Cast), + } +} + +fn calculate_numeric_operator_rhs_number( + l: N, + lhs: types::Value, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Boolean(_) | types::Value::Error(_) => rhs, + types::Value::Text(t) => match t.parse::() { + Ok(nr) => types::Value::Number(f(l, nr)), + Err(_) => types::Value::Error(types::Error::Cast), + }, + types::Value::Number(r) => types::Value::Number(f(l, r)), + types::Value::Iterator(mut value_vec) => { + if let Some(mut temp) = value_vec.pop() { + while let Some(top) = value_vec.pop() { + temp = calculate_numeric_operator(temp, top, f); + } + calculate_numeric_operator(lhs, temp, f) + } else { + types::Value::Error(types::Error::Argument) + } + } + types::Value::Date(_) => types::Value::Error(types::Error::Value), + types::Value::Blank => types::Value::Number(f(l, N::zero())), + } +} + +fn calculate_numeric_operator_product_rhs_number( + l: N, + lhs: types::Value, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Boolean(_) | types::Value::Error(_) => rhs, + types::Value::Text(t) => match t.parse::() { + Ok(nr) => types::Value::Number(f(l, nr)), + Err(_) => types::Value::Error(types::Error::Cast), + }, + types::Value::Number(r) => types::Value::Number(f(l, r)), + types::Value::Iterator(mut value_vec) => { + if let Some(mut temp) = value_vec.pop() { + while let Some(top) = value_vec.pop() { + temp = calculate_numeric_product_operator(temp, top, f); + } + calculate_numeric_product_operator(lhs, temp, f) + } else { + types::Value::Error(types::Error::Argument) + } + } + types::Value::Date(_) => types::Value::Error(types::Error::Value), + types::Value::Blank => match lhs { + types::Value::Blank => types::Value::Blank, + _ => types::Value::Number(l), + }, + } +} + +fn calculate_numeric_operator_rhs_iterator( + mut lhs_vec: Vec>, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Number(_) => { + if let Some(mut temp) = lhs_vec.pop() { + while let Some(top) = lhs_vec.pop() { + temp = calculate_numeric_operator(temp, top, f); + } + calculate_numeric_operator(temp, rhs, f) + } else { + types::Value::Error(types::Error::Argument) + } + } + types::Value::Iterator(mut rhs_vec) => { + let mut result_vec = Vec::new(); + loop { + match (lhs_vec.pop(), rhs_vec.pop()) { + (Some(x), Some(y)) => { + result_vec.push(calculate_numeric_operator(x, y, f)); + } + (Some(_), None) | (None, Some(_)) => { + result_vec.push(types::Value::Error(types::Error::Argument)) + } + (None, None) => break, + }; + } + types::Value::Iterator(result_vec) + } + _ => types::Value::Error(types::Error::Value), + } +} + +pub fn calculate_numeric_operator( + lhs: types::Value, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + //println!("{:?}::{:?}", lhs, rhs); + match lhs { + types::Value::Boolean(_) | types::Value::Error(_) => lhs, + types::Value::Text(t) => calculate_numeric_operator_rhs_text(t, rhs, f), + types::Value::Number(l) => calculate_numeric_operator_rhs_number(l, lhs, rhs, f), + types::Value::Iterator(lhs_vec) => calculate_numeric_operator_rhs_iterator(lhs_vec, rhs, f), + types::Value::Date(_) => types::Value::Error(types::Error::Value), + types::Value::Blank => calculate_numeric_operator_rhs_number(N::zero(), lhs, rhs, f), + } +} + +fn calculate_numeric_product_operator( + lhs: types::Value, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + //println!("{:?}::{:?}", lhs, rhs); + match lhs { + types::Value::Boolean(_) | types::Value::Error(_) => lhs, + types::Value::Text(t) => calculate_numeric_operator_rhs_text(t, rhs, f), + types::Value::Number(l) => calculate_numeric_operator_product_rhs_number(l, lhs, rhs, f), + types::Value::Iterator(lhs_vec) => calculate_numeric_operator_rhs_iterator(lhs_vec, rhs, f), + types::Value::Date(_) => types::Value::Error(types::Error::Value), + types::Value::Blank => calculate_numeric_operator_product_rhs_number(N::one(), lhs, rhs, f), + } +} + +fn calculate_average_operator_rhs_number( + element_count: &mut i64, + l: N, + lhs: types::Value, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Boolean(_) | types::Value::Error(_) => rhs, + types::Value::Text(t) => match t.parse::() { + Ok(nr) => types::Value::Number(f(l, nr)), + Err(_) => types::Value::Error(types::Error::Cast), + }, + types::Value::Number(r) => types::Value::Number(f(l, r)), + types::Value::Iterator(mut value_vec) => { + if let Some(mut temp) = value_vec.pop() { + if temp == types::Value::Blank { + *element_count -= 1; + } + while let Some(top) = value_vec.pop() { + temp = calculate_numeric_operator(temp, top.clone(), f); + match top { + types::Value::Blank => (), + _ => *element_count += 1, + }; + } + calculate_numeric_operator(lhs, temp, f) + } else { + types::Value::Error(types::Error::Argument) + } + } + types::Value::Date(_) => types::Value::Error(types::Error::Value), + types::Value::Blank => { + *element_count -= 1; + types::Value::Number(f(l, N::zero())) + } + } +} + +fn calculate_average_operator_rhs_iterator( + element_count: &mut i64, + mut lhs_vec: Vec>, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Number(_) => { + if let Some(mut temp) = lhs_vec.pop() { + while let Some(top) = lhs_vec.pop() { + temp = calculate_numeric_operator(temp, top, f); + *element_count += 1; + } + calculate_numeric_operator(temp, rhs, f) + } else { + types::Value::Error(types::Error::Argument) + } + } + _ => types::Value::Error(types::Error::Value), + } +} + +fn calculate_average_operator( + element_count: &mut i64, + lhs: types::Value, + rhs: types::Value, + f: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match lhs { + types::Value::Boolean(_) | types::Value::Error(_) => lhs, + types::Value::Text(t) => calculate_numeric_operator_rhs_text(t, rhs, f), + types::Value::Number(l) => { + calculate_average_operator_rhs_number(element_count, l, lhs, rhs, f) + } + types::Value::Iterator(lhs_vec) => { + calculate_average_operator_rhs_iterator(element_count, lhs_vec, rhs, f) + } + types::Value::Date(_) => types::Value::Error(types::Error::Value), + types::Value::Blank => { + *element_count -= 1; + calculate_average_operator_rhs_number(element_count, N::zero(), lhs, rhs, f) + } + } +} + +pub fn calculate_abs(value: types::Value) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match value { + types::Value::Boolean(_) | types::Value::Error(_) | types::Value::Text(_) => value, + types::Value::Number(l) => types::Value::Number(l.abs()), + types::Value::Iterator(_) | types::Value::Date(_) => { + types::Value::Error(types::Error::Value) + } + types::Value::Blank => types::Value::Number(N::zero()), + } +} + +pub fn calculate_average( + mut collective_value: types::Value, + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, + f_collective: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let mut element_count = 0; + while let Some(top) = exp.values.pop() { + element_count += 1; + collective_value = calculate_average_operator( + &mut element_count, + collective_value, + calculate_formula(top, f), + f_collective, + ); + } + if element_count == 0 { + types::Value::Error(types::Error::Div0) + } else { + calculate_numeric_operator( + collective_value, + types::Value::Number(N::from_i64(element_count).unwrap()), + calculate_divide_operator, + ) + } +} + +pub fn calculate_collective_operator( + mut collective_value: types::Value, + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, + f_collective: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + while let Some(top) = exp.values.pop() { + collective_value = + calculate_numeric_operator(collective_value, calculate_formula(top, f), f_collective); + } + collective_value +} + +pub fn calculate_collective_product_operator( + mut collective_value: types::Value, + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, + f_collective: fn(N, N) -> N, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + while let Some(top) = exp.values.pop() { + collective_value = calculate_numeric_product_operator( + collective_value, + calculate_formula(top, f), + f_collective, + ); + } + match collective_value { + types::Value::Blank => types::Value::Number(N::zero()), + _ => collective_value, + } +} diff --git a/src/calculate/operation/string.rs b/src/calculate/operation/string.rs new file mode 100644 index 0000000..b46f02c --- /dev/null +++ b/src/calculate/operation/string.rs @@ -0,0 +1,58 @@ +use crate::types::{self, XlNum}; +use std::{fmt::Debug, str::FromStr}; + +pub fn calculate_concat_operator(str1: &str, str2: &str) -> String { + str1.to_owned() + str2 +} + +fn calculate_string_operation_rhs( + l: &str, + rhs: types::Value, + f: fn(str1: &str, str2: &str) -> String, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match rhs { + types::Value::Boolean(_) | types::Value::Error(_) => rhs, + types::Value::Number(r) => types::Value::Text(f(l, &r.to_string())), + types::Value::Text(r) => types::Value::Text(f(l, &r)), + types::Value::Iterator(_) | types::Value::Date(_) => { + types::Value::Error(types::Error::Value) + } + types::Value::Blank => types::Value::Text(f(l, "")), + } +} + +pub fn calculate_string_operator( + lhs: types::Value, + rhs: types::Value, + f: fn(str1: &str, str2: &str) -> String, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match lhs { + types::Value::Boolean(_) | types::Value::Error(_) => lhs, + types::Value::Number(l) => calculate_string_operation_rhs(&l.to_string(), rhs, f), + types::Value::Text(l) => calculate_string_operation_rhs(&l, rhs, f), + types::Value::Iterator(_) | types::Value::Date(_) => { + types::Value::Error(types::Error::Value) + } + types::Value::Blank => calculate_string_operation_rhs("", rhs, f), + } +} + +pub fn compare_strings( + string1: String, + string2: String, + f: impl Fn(String, String) -> bool, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + types::Value::Boolean(f(string1, string2).into()) +} diff --git a/src/calculate/reference.rs b/src/calculate/reference.rs new file mode 100644 index 0000000..969eeab --- /dev/null +++ b/src/calculate/reference.rs @@ -0,0 +1,34 @@ +use super::calculate_formula; +use crate::{ + parse_formula, + types::{self, XlNum}, +}; +use std::{fmt::Debug, str::FromStr}; + +type NoCustomFunction<'a, N> = &'a fn(String, Vec) -> types::Value; + +pub fn calculate_reference( + string: String, + f: Option<&impl Fn(String) -> types::Value>, +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + match f { + Some(f) => match f(string) { + types::Value::Number(x) => types::Value::Number(x), + types::Value::Text(s) => calculate_formula( + parse_formula::parse_string_to_formula(&s, None::>), + Some(f), + ), + types::Value::Boolean(x) => types::Value::Boolean(x), + types::Value::Error(types::Error::Value) => types::Value::Error(types::Error::Value), + types::Value::Iterator(v) => types::Value::Iterator(v), + types::Value::Date(d) => types::Value::Date(d), + types::Value::Blank => types::Value::Blank, + _ => types::Value::Error(types::Error::Reference), + }, + None => types::Value::Error(types::Error::Reference), + } +} diff --git a/src/types.rs b/src/types.rs index 74f8326..c426807 100644 --- a/src/types.rs +++ b/src/types.rs @@ -42,23 +42,49 @@ pub enum Operator { } /// Defines error types. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, strum::Display)] pub enum Error { + #[strum(to_string = "#DIV/0!")] Div0, + #[strum(to_string = "#CAST!")] Cast, + #[strum(to_string = "#PARSE!")] Parse, + #[strum(to_string = "#VALUE!")] Value, + #[strum(to_string = "#ARG!")] Argument, + #[strum(to_string = "#REF!")] Reference, } /// Defines boolean types. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, strum::Display)] +#[strum(serialize_all = "UPPERCASE")] pub enum Boolean { True, False, } +impl From for Boolean { + fn from(value: bool) -> Self { + if value { + Boolean::True + } else { + Boolean::False + } + } +} + +impl From for bool { + fn from(value: Boolean) -> Self { + match value { + Boolean::True => true, + Boolean::False => false, + } + } +} + pub trait XlNum: Float + AsPrimitive From 3f057567b984f9057b06547513dfcba95a30b106 Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Tue, 9 Sep 2025 14:42:43 +0300 Subject: [PATCH 20/23] Add year/month/day functions --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/grammar.pest | 5 +++- src/parse_formula.rs | 6 +++++ src/types.rs | 3 +++ tests/test.rs | 56 +++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03d9cef..81a2893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,7 +255,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "xlformula_engine" -version = "0.4.0" +version = "0.4.1" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 76535ea..df11c1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.4.0" +version = "0.4.1" [dependencies] chrono = { version = "0.4.41", default-features = false } diff --git a/src/grammar.pest b/src/grammar.pest index 987e046..30a80e7 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -36,13 +36,16 @@ logical_operator = _{ equal | not_equal | greater_or_equal | greater greater_or_equal= { ">=" } less_or_equal = { "<=" } -function = _{ abs | sum | product | average | negate | days | right | left | iff | isblank | custom_function } +function = _{ abs | sum | product | average | negate | days | year | month | day | right | left | iff | isblank | custom_function } abs = { ^"ABS" ~ "(" ~ expr ~ ")" } sum = { ^"SUM" ~ function_param_with_atomic_expr} product = { ^"PRODUCT" ~ function_param_with_atomic_expr} average = { ^"AVERAGE" ~ function_param_with_atomic_expr} negate = { ^"-" ~ (function_param | iterator | reference) } days = { ^"DAYS" ~ function_param } + year = { ^"YEAR" ~ "(" ~ expr ~ ")" } + month = { ^"MONTH" ~ "(" ~ expr ~ ")" } + day = { ^"DAY" ~ "(" ~ expr ~ ")" } right = { ^"RIGHT" ~ function_param } left = { ^"LEFT" ~ function_param } iff = { ^"IF" ~ three_params } diff --git a/src/parse_formula.rs b/src/parse_formula.rs index afd446e..a9d0eb7 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -191,6 +191,9 @@ fn rule_to_function_operator(collective_operation: Rule) -> types::Operator { Rule::left => types::Operator::Function(types::Function::Left), Rule::iff => types::Operator::Function(types::Function::Iff), Rule::isblank => types::Operator::Function(types::Function::IsBlank), + Rule::year => types::Operator::Function(types::Function::Year), + Rule::month => types::Operator::Function(types::Function::Month), + Rule::day => types::Operator::Function(types::Function::Day), _ => unreachable!(), } } @@ -393,6 +396,9 @@ where Rule::negate => build_formula_unary_operator(Rule::negate, pair, f), Rule::expr => build_formula_with_parser(pair.into_inner(), f), Rule::days => build_formula_collective_operator(Rule::days, pair, f), + Rule::year => build_formula_collective_operator(Rule::year, pair, f), + Rule::month => build_formula_collective_operator(Rule::month, pair, f), + Rule::day => build_formula_collective_operator(Rule::day, pair, f), Rule::right => build_formula_collective_operator(Rule::right, pair, f), Rule::left => build_formula_collective_operator(Rule::left, pair, f), Rule::custom_function => build_formula_custom_function(pair, f), diff --git a/src/types.rs b/src/types.rs index c426807..90dcb8c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -21,6 +21,9 @@ pub enum Function { Left, Iff, IsBlank, + Year, + Month, + Day, } /// Defines Excel Operators. diff --git a/tests/test.rs b/tests/test.rs index 0d0f27f..b81c214 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, Duration, FixedOffset}; +use chrono::{DateTime, Datelike, Duration, FixedOffset}; use std::{fmt::Debug, str::FromStr}; use xlformula_engine::{ calculate, parse_formula, @@ -840,6 +840,42 @@ fn it_evaluate_date() { Some(&data_function), (end_date() - Duration::days(3)).to_string() ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=year(end)", + Some(&data_function), + end_date().year().to_string() + ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=month(end)", + Some(&data_function), + end_date().month().to_string() + ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=day(end)", + Some(&data_function), + end_date().day().to_string() + ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=year(end, 2)", + Some(&data_function), + "#REF!" + ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=month(end, 2)", + Some(&data_function), + "#REF!" + ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=day(end, 2)", + Some(&data_function), + "#REF!" + ); } #[test] @@ -1135,6 +1171,24 @@ fn it_evaluates_blanks_in_days_function() { Some(&data_function), -43495.0 ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=year(B)", + Some(&data_function), + "#VALUE!" + ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=month(B)", + Some(&data_function), + "#VALUE!" + ); + test_all_types_with_data!( + evaluate_formula_date_with_reference, + "=day(B)", + Some(&data_function), + "#VALUE!" + ); } #[test] From 8d7b2b8c1bc742dd0d5433f6062b07d2ea89052c Mon Sep 17 00:00:00 2001 From: Binyamin Klein Date: Tue, 10 Feb 2026 10:32:31 +0200 Subject: [PATCH 21/23] Add FIND, SEARCH, and ISERROR functions --- src/calculate/args.rs | 29 +++++ src/calculate/operation/function.rs | 108 +++++++++++++++++- src/calculate/operation/string.rs | 164 ++++++++++++++++++++++++++++ src/grammar.pest | 5 +- src/parse_formula.rs | 6 + src/types.rs | 3 + tests/common/mod.rs | 29 +++++ tests/find.rs | 82 ++++++++++++++ tests/iserror.rs | 48 ++++++++ tests/search.rs | 62 +++++++++++ 10 files changed, 532 insertions(+), 4 deletions(-) create mode 100644 tests/common/mod.rs create mode 100644 tests/find.rs create mode 100644 tests/iserror.rs create mode 100644 tests/search.rs diff --git a/src/calculate/args.rs b/src/calculate/args.rs index df574ed..6bf94a6 100644 --- a/src/calculate/args.rs +++ b/src/calculate/args.rs @@ -58,6 +58,35 @@ where (first, middle, last) } +/// Returns (find_text, within_text, start_num) for FIND/SEARCH. +/// start_num defaults to 1 if only two arguments are provided. +pub fn get_find_args( + mut exp: types::Expression, + f: Option<&impl Fn(String) -> types::Value>, +) -> (types::Value, types::Value, types::Value) +where + N: XlNum, + ::Err: Debug, +{ + let start_num = if exp.values.len() >= 3 { + match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Number(N::one()), + } + } else { + types::Value::Number(N::one()) + }; + let within_text = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }; + let find_text = match exp.values.pop() { + Some(formula) => calculate_formula(formula, f), + None => types::Value::Error(types::Error::Argument), + }; + (find_text, within_text, start_num) +} + pub fn get_number_and_string_values( mut exp: types::Expression, f: Option<&impl Fn(String) -> types::Value>, diff --git a/src/calculate/operation/function.rs b/src/calculate/operation/function.rs index c98b3ae..e937323 100644 --- a/src/calculate/operation/function.rs +++ b/src/calculate/operation/function.rs @@ -5,13 +5,17 @@ use super::{ calculate_abs, calculate_average, calculate_collective_operator, calculate_collective_product_operator, }, + string::{ + find_position_case_sensitive, search_position_with_wildcards, value_to_string_for_find, + CoerceForFind, + }, }; use crate::{ calculate::args::{ - get_binary_function_args, get_number_and_string_values, get_ternary_function_args, - get_unary_function_arg, + get_binary_function_args, get_find_args, get_number_and_string_values, + get_ternary_function_args, get_unary_function_arg, }, - types::{self, XlNum}, + types::{self, Error, XlNum}, }; use std::{fmt::Debug, str::FromStr}; @@ -135,5 +139,103 @@ where types::Function::Year => calculate_year(get_unary_function_arg(exp, f)), types::Function::Month => calculate_month(get_unary_function_arg(exp, f)), types::Function::Day => calculate_day(get_unary_function_arg(exp, f)), + types::Function::Find => calculate_find(get_find_args(exp, f)), + types::Function::Search => calculate_search(get_find_args(exp, f)), + types::Function::IsError => calculate_iserror(get_unary_function_arg(exp, f)), + } +} + +fn calculate_find( + (find_text, within_text, start_num): (types::Value, types::Value, types::Value), +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + if let types::Value::Error(e) = find_text { + return types::Value::Error(e); + } + if let types::Value::Error(e) = within_text { + return types::Value::Error(e); + } + if let types::Value::Error(e) = start_num { + return types::Value::Error(e); + } + let find_s = match value_to_string_for_find(&find_text) { + CoerceForFind::Coerced(s) => s, + CoerceForFind::Propagate(v) => return v, + }; + let within_s = match value_to_string_for_find(&within_text) { + CoerceForFind::Coerced(s) => s, + CoerceForFind::Propagate(v) => return v, + }; + let start_i64: i64 = match &start_num { + types::Value::Number(n) => n.as_(), + _ => return types::Value::Error(Error::Value), + }; + if start_i64 <= 0 { + return types::Value::Error(Error::Value); + } + let within_len = within_s.chars().count() as i64; + if start_i64 > within_len { + return types::Value::Error(Error::Value); + } + match find_position_case_sensitive(&find_s, &within_s, start_i64) { + Some(pos) => N::from_i64(pos) + .or_else(|| N::from_f64(1.0)) + .map(types::Value::Number) + .unwrap_or_else(|| types::Value::Error(Error::Value)), + None => types::Value::Error(Error::Value), + } +} + +fn calculate_search( + (find_text, within_text, start_num): (types::Value, types::Value, types::Value), +) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + if let types::Value::Error(e) = find_text { + return types::Value::Error(e); + } + if let types::Value::Error(e) = within_text { + return types::Value::Error(e); + } + if let types::Value::Error(e) = start_num { + return types::Value::Error(e); } + let find_s = match value_to_string_for_find(&find_text) { + CoerceForFind::Coerced(s) => s, + CoerceForFind::Propagate(v) => return v, + }; + let within_s = match value_to_string_for_find(&within_text) { + CoerceForFind::Coerced(s) => s, + CoerceForFind::Propagate(v) => return v, + }; + let start_i64: i64 = match &start_num { + types::Value::Number(n) => n.as_(), + _ => return types::Value::Error(Error::Value), + }; + if start_i64 <= 0 { + return types::Value::Error(Error::Value); + } + let within_len = within_s.chars().count() as i64; + if start_i64 > within_len { + return types::Value::Error(Error::Value); + } + match search_position_with_wildcards(&find_s, &within_s, start_i64) { + Some(pos) => N::from_i64(pos) + .or_else(|| N::from_f64(1.0)) + .map(types::Value::Number) + .unwrap_or_else(|| types::Value::Error(Error::Value)), + None => types::Value::Error(Error::Value), + } +} + +fn calculate_iserror(arg: types::Value) -> types::Value +where + N: XlNum, +{ + types::Value::Boolean(matches!(arg, types::Value::Error(_)).into()) } diff --git a/src/calculate/operation/string.rs b/src/calculate/operation/string.rs index b46f02c..2334d4a 100644 --- a/src/calculate/operation/string.rs +++ b/src/calculate/operation/string.rs @@ -56,3 +56,167 @@ where { types::Value::Boolean(f(string1, string2).into()) } + +/// Result of coercing a formula value to string for FIND/SEARCH (Excel semantics). +#[derive(Debug, Clone)] +pub enum CoerceForFind +where + N: XlNum, +{ + /// Value was coerced to string; use for FIND/SEARCH. + Coerced(String), + /// Propagate this value as the formula result (e.g. existing error or #VALUE! for Date/Iterator). + Propagate(types::Value), +} + +/// Coerce a formula value to string for FIND/SEARCH (Excel semantics). +pub fn value_to_string_for_find(v: &types::Value) -> CoerceForFind +where + N: XlNum, + ::Err: Debug, +{ + match v { + types::Value::Error(e) => CoerceForFind::Propagate(types::Value::Error(*e)), + types::Value::Number(n) => CoerceForFind::Coerced(n.to_string()), + types::Value::Text(s) => CoerceForFind::Coerced(s.clone()), + types::Value::Boolean(b) => CoerceForFind::Coerced(b.to_string()), + types::Value::Blank => CoerceForFind::Coerced(String::new()), + types::Value::Date(_) | types::Value::Iterator(_) => { + CoerceForFind::Propagate(types::Value::Error(types::Error::Value)) + } + } +} + +/// FIND: case-sensitive, no wildcards. Returns 1-based character position or None. +pub fn find_position_case_sensitive( + find_text: &str, + within_text: &str, + start_num_1based: i64, +) -> Option { + let start: usize = start_num_1based.checked_sub(1)?.try_into().ok()?; + // Character count (not byte len) for Excel 1-based character position semantics and UTF-8. + let char_count = within_text.chars().count(); + if start >= char_count { + return None; + } + if find_text.is_empty() { + return Some(start_num_1based); + } + let rest: String = within_text.chars().skip(start).collect(); + let byte_offset = rest.find(find_text)?; + let chars_before = rest[..byte_offset].chars().count(); + let one_based = (start + chars_before + 1) as i64; + Some(one_based) +} + +/// SEARCH: case-insensitive with wildcards ? * and ~ escape. Returns 1-based character position or None. +pub fn search_position_with_wildcards( + find_text: &str, + within_text: &str, + start_num_1based: i64, +) -> Option { + let start: usize = start_num_1based.checked_sub(1)?.try_into().ok()?; + // Character count (not byte len) for Excel 1-based character position semantics and UTF-8. + let char_count = within_text.chars().count(); + if start >= char_count { + return None; + } + if find_text.is_empty() { + return Some(start_num_1based); + } + let rest: String = within_text.chars().skip(start).collect(); + let rest_lower: String = rest.to_lowercase(); + let pattern_orig: Vec = find_text.chars().collect(); + let pattern_lower: Vec = find_text.to_lowercase().chars().collect(); + let rest_orig: Vec = rest.chars().collect(); + let rest_chars: Vec = rest_lower.chars().collect(); + let mut best: Option = None; + for i in 0..=rest_chars.len() { + if let Some(offset) = match_pattern( + &pattern_lower, + &pattern_orig, + &rest_chars, + &rest_orig, + i, + 0, + i, + ) { + let pos = start + offset + 1; + best = Some(match best { + None => pos, + Some(p) => p.min(pos), + }); + } + } + best.map(|p| p as i64) +} + +/// Match pattern (with ? * ~) against text from position. Returns Some(offset) where offset +/// is the 0-based position in text where the matched substring starts (for leading *, this +/// is where the remainder matches, not where * started). +fn match_pattern( + pattern_lower: &[char], + pattern_orig: &[char], + text_lower: &[char], + text_orig: &[char], + text_pos: usize, + pattern_pos: usize, + match_start: usize, +) -> Option { + if pattern_pos >= pattern_lower.len() { + return Some(match_start); + } + let mut p = pattern_pos; + let mut t = text_pos; + while p < pattern_lower.len() { + if t > text_lower.len() { + return None; + } + if p + 1 < pattern_orig.len() && pattern_orig[p] == '~' { + let next = pattern_orig[p + 1]; + if next == '?' || next == '*' || next == '~' { + if t >= text_orig.len() || text_orig[t] != next { + return None; + } + p += 2; + t += 1; + continue; + } + } + match pattern_lower[p] { + '?' => { + if t >= text_lower.len() { + return None; + } + p += 1; + t += 1; + } + '*' => { + let rest_pat_lower = &pattern_lower[p + 1..]; + let rest_pat_orig = &pattern_orig[p + 1..]; + for skip in 0..=text_lower.len().saturating_sub(t) { + if let Some(off) = match_pattern( + rest_pat_lower, + rest_pat_orig, + text_lower, + text_orig, + t + skip, + 0, + t + skip, + ) { + return Some(off); + } + } + return None; + } + c => { + if t >= text_lower.len() || text_lower[t] != c { + return None; + } + p += 1; + t += 1; + } + } + } + Some(match_start) +} diff --git a/src/grammar.pest b/src/grammar.pest index 30a80e7..0f72991 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -36,7 +36,7 @@ logical_operator = _{ equal | not_equal | greater_or_equal | greater greater_or_equal= { ">=" } less_or_equal = { "<=" } -function = _{ abs | sum | product | average | negate | days | year | month | day | right | left | iff | isblank | custom_function } +function = _{ abs | sum | product | average | negate | days | year | month | day | right | left | iff | isblank | find | search | iserror | custom_function } abs = { ^"ABS" ~ "(" ~ expr ~ ")" } sum = { ^"SUM" ~ function_param_with_atomic_expr} product = { ^"PRODUCT" ~ function_param_with_atomic_expr} @@ -50,6 +50,9 @@ function = _{ abs | sum | product | average | negate | days | year | month | day left = { ^"LEFT" ~ function_param } iff = { ^"IF" ~ three_params } isblank = { ^"ISBLANK" ~ function_param} + find = { ^"FIND" ~ function_param } + search = { ^"SEARCH" ~ function_param } + iserror = { ^"ISERROR" ~ function_param } custom_function = { reference ~ (function_param | empty_param) } logical_function = _{ or | and | xor | not } diff --git a/src/parse_formula.rs b/src/parse_formula.rs index a9d0eb7..7c0bbdc 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -194,6 +194,9 @@ fn rule_to_function_operator(collective_operation: Rule) -> types::Operator { Rule::year => types::Operator::Function(types::Function::Year), Rule::month => types::Operator::Function(types::Function::Month), Rule::day => types::Operator::Function(types::Function::Day), + Rule::find => types::Operator::Function(types::Function::Find), + Rule::search => types::Operator::Function(types::Function::Search), + Rule::iserror => types::Operator::Function(types::Function::IsError), _ => unreachable!(), } } @@ -404,6 +407,9 @@ where Rule::custom_function => build_formula_custom_function(pair, f), Rule::iff => build_formula_iff(pair, f), Rule::isblank => build_formula_collective_operator(Rule::isblank, pair, f), + Rule::find => build_formula_collective_operator(Rule::find, pair, f), + Rule::search => build_formula_collective_operator(Rule::search, pair, f), + Rule::iserror => build_formula_collective_operator(Rule::iserror, pair, f), _ => unreachable!(), }) .map_infix( diff --git a/src/types.rs b/src/types.rs index 90dcb8c..70d9bfb 100644 --- a/src/types.rs +++ b/src/types.rs @@ -24,6 +24,9 @@ pub enum Function { Year, Month, Day, + Find, + Search, + IsError, } /// Defines Excel Operators. diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..cbecd7d --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,29 @@ +use std::{fmt::Debug, str::FromStr}; +use xlformula_engine::{ + calculate, + parse_formula, + types::{self, XlNum}, + NoCustomFunction, NoReference, +}; + +/// Evaluate formula string and return the result as string (e.g. "1", "#VALUE!", "TRUE"). +pub fn evaluate_formula_string(s: &str) -> String +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); + let result = calculate::calculate_formula(formula, None::>); + calculate::result_to_string(result) +} + +/// Evaluate formula and return the raw Value (for checking Error variant etc.). +#[allow(dead_code)] +pub fn evaluate_formula_value(s: &str) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + let formula = parse_formula::parse_string_to_formula(s, None::>); + calculate::calculate_formula(formula, None::>) +} diff --git a/tests/find.rs b/tests/find.rs new file mode 100644 index 0000000..a15f049 --- /dev/null +++ b/tests/find.rs @@ -0,0 +1,82 @@ +mod common; + +use std::{fmt::Debug, str::FromStr}; +use xlformula_engine::types::XlNum; + +fn eval(s: &str) -> String +where + N: XlNum, + ::Err: Debug, +{ + common::evaluate_formula_string::(s) +} + +#[test] +fn find_basic() { + assert_eq!(eval::(r#"=FIND("a","abc")"#), "1"); + assert_eq!(eval::(r#"=FIND("b","abc")"#), "2"); + assert_eq!(eval::(r#"=FIND("c","abc")"#), "3"); +} + +#[test] +fn find_not_found() { + assert_eq!(eval::(r#"=FIND("x","abc")"#), "#VALUE!"); +} + +#[test] +fn find_from_position() { + assert_eq!(eval::(r#"=FIND("a","abc",2)"#), "#VALUE!"); + assert_eq!(eval::(r#"=FIND("b","abc",2)"#), "2"); + assert_eq!(eval::(r#"=FIND("Y","AYF0093.YoungMensApparel",8)"#), "9"); +} + +#[test] +fn find_empty_find_text() { + assert_eq!(eval::(r#"=FIND("","abc")"#), "1"); + assert_eq!(eval::(r#"=FIND("","abc",2)"#), "2"); + // empty find_text with start past length returns #VALUE! + assert_eq!(eval::(r#"=FIND("","abc",4)"#), "#VALUE!"); +} + +#[test] +fn find_case_sensitive() { + assert_eq!(eval::(r#"=FIND("A","abc")"#), "#VALUE!"); + // Second "M" in "MiriamMcGovern" starting from position 3 is at 1-based position 7 + assert_eq!(eval::(r#"=FIND("M","MiriamMcGovern",3)"#), "7"); +} + +#[test] +fn find_start_num_bounds() { + assert_eq!(eval::(r#"=FIND("a","abc",0)"#), "#VALUE!"); + assert_eq!(eval::(r#"=FIND("a","abc",-1)"#), "#VALUE!"); + assert_eq!(eval::(r#"=FIND("a","abc",4)"#), "#VALUE!"); +} + +#[test] +fn find_start_num_truncate() { + assert_eq!(eval::(r#"=FIND("b","abc",2.9)"#), "2"); +} + +#[test] +fn find_coercion() { + assert_eq!(eval::(r#"=FIND(1,"121")"#), "1"); + assert_eq!(eval::(r#"=FIND(TRUE,"TRUEx")"#), "1"); + assert_eq!(eval::(r#"=FIND("1","121",1)"#), "1"); +} + +#[test] +fn find_character_based() { + assert_eq!(eval::(r#"=FIND("ö","föö")"#), "2"); +} + +#[test] +fn find_error_propagation() { + assert_eq!(eval::(r#"=FIND(1/0,"abc")"#), "#DIV/0!"); + assert_eq!(eval::(r#"=FIND("a",1/0)"#), "#DIV/0!"); +} + +#[test] +fn find_f32() { + assert_eq!(eval::(r#"=FIND("a","abc")"#), "1"); + assert_eq!(eval::(r#"=FIND("x","abc")"#), "#VALUE!"); +} diff --git a/tests/iserror.rs b/tests/iserror.rs new file mode 100644 index 0000000..d795f3f --- /dev/null +++ b/tests/iserror.rs @@ -0,0 +1,48 @@ +mod common; + +use std::{fmt::Debug, str::FromStr}; +use xlformula_engine::types::{self, XlNum}; + +fn eval(s: &str) -> String +where + N: XlNum, + ::Err: Debug, +{ + common::evaluate_formula_string::(s) +} + +fn value(s: &str) -> types::Value +where + N: XlNum, + ::Err: Debug, +{ + common::evaluate_formula_value::(s) +} + +#[test] +fn iserror_true_for_errors() { + assert_eq!(eval::(r#"=ISERROR(1/0)"#), "TRUE"); + assert_eq!(eval::(r#"=ISERROR(FIND("x","abc"))"#), "TRUE"); +} + +#[test] +fn iserror_false_for_non_errors() { + assert_eq!(eval::(r#"=ISERROR(1)"#), "FALSE"); + assert_eq!(eval::(r#"=ISERROR("")"#), "FALSE"); + assert_eq!(eval::(r#"=ISERROR(TRUE)"#), "FALSE"); + assert_eq!(eval::(r#"=ISERROR(FIND("a","abc"))"#), "FALSE"); +} + +#[test] +fn iserror_detects_error_value() { + let v = value::(r#"=ISERROR(1/0)"#); + assert!(matches!(v, types::Value::Boolean(types::Boolean::True))); + let v = value::(r#"=ISERROR(42)"#); + assert!(matches!(v, types::Value::Boolean(types::Boolean::False))); +} + +#[test] +fn iserror_f32() { + assert_eq!(eval::(r#"=ISERROR(1/0)"#), "TRUE"); + assert_eq!(eval::(r#"=ISERROR(1)"#), "FALSE"); +} diff --git a/tests/search.rs b/tests/search.rs new file mode 100644 index 0000000..d4c819c --- /dev/null +++ b/tests/search.rs @@ -0,0 +1,62 @@ +mod common; + +use std::{fmt::Debug, str::FromStr}; +use xlformula_engine::types::XlNum; + +fn eval(s: &str) -> String +where + N: XlNum, + ::Err: Debug, +{ + common::evaluate_formula_string::(s) +} + +#[test] +fn search_case_insensitive() { + assert_eq!(eval::(r#"=SEARCH("m","MiriamMcGovern")"#), "1"); + assert_eq!(eval::(r#"=SEARCH("M","MiriamMcGovern")"#), "1"); + assert_eq!(eval::(r#"=SEARCH("gloves","Gloves (Youth)")"#), "1"); + assert_eq!(eval::(r#"=SEARCH("M","abc")"#), "#VALUE!"); +} + +#[test] +fn search_wildcard_question() { + assert_eq!(eval::(r#"=SEARCH("sm?th","smith")"#), "1"); + assert_eq!(eval::(r#"=SEARCH("sm?th","smyth")"#), "1"); +} + +#[test] +fn search_wildcard_star() { + // "*east" matches "east" in "Northeast"; "east" starts at 1-based position 6 + assert_eq!(eval::(r#"=SEARCH("*east","Northeast")"#), "6"); +} + +#[test] +fn search_literal_tilde() { + assert_eq!(eval::(r#"=SEARCH("~?","a?b")"#), "2"); +} + +#[test] +fn search_empty_find_text() { + assert_eq!(eval::(r#"=SEARCH("","abc")"#), "1"); + assert_eq!(eval::(r#"=SEARCH("","abc",2)"#), "2"); + // empty find_text with start past length returns #VALUE! + assert_eq!(eval::(r#"=SEARCH("","abc",4)"#), "#VALUE!"); +} + +#[test] +fn search_not_found() { + assert_eq!(eval::(r#"=SEARCH("x","abc")"#), "#VALUE!"); +} + +#[test] +fn search_start_num_bounds() { + assert_eq!(eval::(r#"=SEARCH("a","abc",0)"#), "#VALUE!"); + assert_eq!(eval::(r#"=SEARCH("a","abc",-1)"#), "#VALUE!"); + assert_eq!(eval::(r#"=SEARCH("a","abc",4)"#), "#VALUE!"); +} + +#[test] +fn search_f32() { + assert_eq!(eval::(r#"=SEARCH("m","Miriam")"#), "1"); +} From 439d9c4c66238ba9ea0890315a986fa177ff22ea Mon Sep 17 00:00:00 2001 From: Binyamin Klein Date: Tue, 10 Feb 2026 11:03:42 +0200 Subject: [PATCH 22/23] Bump version to 0.4.2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81a2893..e35ce08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,7 +255,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "xlformula_engine" -version = "0.4.1" +version = "0.4.2" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index df11c1a..9d10b7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.4.1" +version = "0.4.2" [dependencies] chrono = { version = "0.4.41", default-features = false } From d9fece7868c251705c770b672a01e0bab268c60f Mon Sep 17 00:00:00 2001 From: Ben Schreiber Date: Mon, 9 Mar 2026 16:24:54 +0200 Subject: [PATCH 23/23] Add BLANK() function --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/calculate/operation/function.rs | 1 + src/grammar.pest | 3 ++- src/parse_formula.rs | 10 ++++++++++ src/types.rs | 2 ++ tests/test.rs | 7 +++++++ 7 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e35ce08..847b457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,7 +255,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "xlformula_engine" -version = "0.4.2" +version = "0.4.3" dependencies = [ "assert_approx_eq", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 9d10b7b..d3a9e15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "xlformula_engine" readme = "README.md" repository = "https://github.com/jiradaherbst/XLFormula-Engine" -version = "0.4.2" +version = "0.4.3" [dependencies] chrono = { version = "0.4.41", default-features = false } diff --git a/src/calculate/operation/function.rs b/src/calculate/operation/function.rs index e937323..d036ab7 100644 --- a/src/calculate/operation/function.rs +++ b/src/calculate/operation/function.rs @@ -142,6 +142,7 @@ where types::Function::Find => calculate_find(get_find_args(exp, f)), types::Function::Search => calculate_search(get_find_args(exp, f)), types::Function::IsError => calculate_iserror(get_unary_function_arg(exp, f)), + types::Function::Blank => types::Value::Blank, } } diff --git a/src/grammar.pest b/src/grammar.pest index 0f72991..aa47f15 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -36,7 +36,7 @@ logical_operator = _{ equal | not_equal | greater_or_equal | greater greater_or_equal= { ">=" } less_or_equal = { "<=" } -function = _{ abs | sum | product | average | negate | days | year | month | day | right | left | iff | isblank | find | search | iserror | custom_function } +function = _{ abs | sum | product | average | negate | days | year | month | day | right | left | iff | isblank | find | search | iserror | blank_func | custom_function } abs = { ^"ABS" ~ "(" ~ expr ~ ")" } sum = { ^"SUM" ~ function_param_with_atomic_expr} product = { ^"PRODUCT" ~ function_param_with_atomic_expr} @@ -53,6 +53,7 @@ function = _{ abs | sum | product | average | negate | days | year | month | day find = { ^"FIND" ~ function_param } search = { ^"SEARCH" ~ function_param } iserror = { ^"ISERROR" ~ function_param } + blank_func = { ^"BLANK" ~ empty_param } custom_function = { reference ~ (function_param | empty_param) } logical_function = _{ or | and | xor | not } diff --git a/src/parse_formula.rs b/src/parse_formula.rs index 7c0bbdc..b757585 100644 --- a/src/parse_formula.rs +++ b/src/parse_formula.rs @@ -125,6 +125,14 @@ where types::Formula::Operation(operation) } +fn build_formula_blank_func() -> types::Formula +where + N: XlNum, + ::Err: Debug, +{ + types::Formula::Value(types::Value::Blank) +} + fn build_formula_reference(pair: pest::iterators::Pair) -> types::Formula where N: XlNum, @@ -197,6 +205,7 @@ fn rule_to_function_operator(collective_operation: Rule) -> types::Operator { Rule::find => types::Operator::Function(types::Function::Find), Rule::search => types::Operator::Function(types::Function::Search), Rule::iserror => types::Operator::Function(types::Function::IsError), + Rule::blank_func => types::Operator::Function(types::Function::Blank), _ => unreachable!(), } } @@ -410,6 +419,7 @@ where Rule::find => build_formula_collective_operator(Rule::find, pair, f), Rule::search => build_formula_collective_operator(Rule::search, pair, f), Rule::iserror => build_formula_collective_operator(Rule::iserror, pair, f), + Rule::blank_func => build_formula_blank_func(), _ => unreachable!(), }) .map_infix( diff --git a/src/types.rs b/src/types.rs index 70d9bfb..facce2e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,6 +4,7 @@ use std::{ fmt::{Debug, Display}, str::FromStr, }; + /// Defines Excel Functions. #[derive(Debug, Copy, Clone)] pub enum Function { @@ -27,6 +28,7 @@ pub enum Function { Find, Search, IsError, + Blank, } /// Defines Excel Operators. diff --git a/tests/test.rs b/tests/test.rs index b81c214..ae6f386 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -468,6 +468,13 @@ fn it_evaluate_wrong_parens1() { test_all_types!(evaluate_formula_string, "=Hello World", "#PARSE!"); } +//////////////////////////// Blank ////////////////////////////////// + +#[test] +fn it_evaluate_blank() { + test_all_types!(evaluate_formula_string, "=BLANK()", "0"); +} + //////////////////////////// Boolean ////////////////////////////////// #[test] fn it_evaluate_comparison_operators() {