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..81a2893 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" @@ -228,11 +255,12 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "xlformula_engine" -version = "0.4.0" +version = "0.4.1" dependencies = [ "assert_approx_eq", "chrono", "num-traits", "pest", "pest_derive", + "strum", ] diff --git a/Cargo.toml b/Cargo.toml index cb60cc4..df11c1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,11 @@ 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 } +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/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 74f8326..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. @@ -42,23 +45,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 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]