diff --git a/base/src/expressions/parser/mod.rs b/base/src/expressions/parser/mod.rs index f396a75..d8de57d 100644 --- a/base/src/expressions/parser/mod.rs +++ b/base/src/expressions/parser/mod.rs @@ -49,18 +49,7 @@ pub mod stringify; pub mod walk; #[cfg(test)] -mod test; - -#[cfg(test)] -mod test_ranges; - -#[cfg(test)] -mod test_move_formula; -#[cfg(test)] -mod test_tables; - -#[cfg(test)] -mod test_issue_155; +mod tests; pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> { let mut lexer = lexer::Lexer::new( diff --git a/base/src/expressions/parser/stringify.rs b/base/src/expressions/parser/stringify.rs index 352b285..f9ed35c 100644 --- a/base/src/expressions/parser/stringify.rs +++ b/base/src/expressions/parser/stringify.rs @@ -456,11 +456,65 @@ fn stringify( }; format!("{}{}{}", x, kind, y) } - OpPowerKind { left, right } => format!( - "{}^{}", - stringify(left, context, displace_data, use_original_name), - stringify(right, context, displace_data, use_original_name) - ), + OpPowerKind { left, right } => { + let x = match **left { + BooleanKind(_) + | NumberKind(_) + | StringKind(_) + | ReferenceKind { .. } + | RangeKind { .. } + | WrongReferenceKind { .. } + | VariableKind(_) + | WrongRangeKind { .. } => { + stringify(left, context, displace_data, use_original_name) + } + OpRangeKind { .. } + | OpConcatenateKind { .. } + | OpProductKind { .. } + | OpPowerKind { .. } + | FunctionKind { .. } + | InvalidFunctionKind { .. } + | ArrayKind(_) + | UnaryKind { .. } + | ErrorKind(_) + | ParseErrorKind { .. } + | OpSumKind { .. } + | CompareKind { .. } + | EmptyArgKind => format!( + "({})", + stringify(left, context, displace_data, use_original_name) + ), + }; + let y = match **right { + BooleanKind(_) + | NumberKind(_) + | StringKind(_) + | ReferenceKind { .. } + | RangeKind { .. } + | WrongReferenceKind { .. } + | VariableKind(_) + | WrongRangeKind { .. } => { + stringify(right, context, displace_data, use_original_name) + } + OpRangeKind { .. } + | OpConcatenateKind { .. } + | OpProductKind { .. } + | OpPowerKind { .. } + | FunctionKind { .. } + | InvalidFunctionKind { .. } + | ArrayKind(_) + | UnaryKind { .. } + | ErrorKind(_) + | ParseErrorKind { .. } + | OpSumKind { .. } + | CompareKind { .. } + | EmptyArgKind => format!( + "({})", + stringify(right, context, displace_data, use_original_name) + ), + }; + format!("{}^{}", x, y) + } InvalidFunctionKind { name, args } => { format_function(name, args, context, displace_data, use_original_name) } diff --git a/base/src/expressions/parser/tests/mod.rs b/base/src/expressions/parser/tests/mod.rs new file mode 100644 index 0000000..7661338 --- /dev/null +++ b/base/src/expressions/parser/tests/mod.rs @@ -0,0 +1,6 @@ +mod test_general; +mod test_issue_155; +mod test_move_formula; +mod test_ranges; +mod test_stringify; +mod test_tables; diff --git a/base/src/expressions/parser/test.rs b/base/src/expressions/parser/tests/test_general.rs similarity index 98% rename from base/src/expressions/parser/test.rs rename to base/src/expressions/parser/tests/test_general.rs index aff3df4..2882b34 100644 --- a/base/src/expressions/parser/test.rs +++ b/base/src/expressions/parser/tests/test_general.rs @@ -3,17 +3,11 @@ use std::collections::HashMap; use crate::expressions::lexer::LexerMode; -use crate::expressions::parser::stringify::DisplaceData; - -use super::super::types::CellReferenceRC; -use super::Parser; -use super::{ - super::parser::{ - stringify::{to_rc_format, to_string}, - Node, - }, - stringify::to_string_displaced, +use crate::expressions::parser::stringify::{ + to_rc_format, to_string, to_string_displaced, DisplaceData, }; +use crate::expressions::parser::{Node, Parser}; +use crate::expressions::types::CellReferenceRC; struct Formula<'a> { initial: &'a str, diff --git a/base/src/expressions/parser/test_issue_155.rs b/base/src/expressions/parser/tests/test_issue_155.rs similarity index 93% rename from base/src/expressions/parser/test_issue_155.rs rename to base/src/expressions/parser/tests/test_issue_155.rs index 7144cc6..5c0191d 100644 --- a/base/src/expressions/parser/test_issue_155.rs +++ b/base/src/expressions/parser/tests/test_issue_155.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; -use super::super::parser::stringify::to_string; -use super::super::types::CellReferenceRC; -use super::Parser; +use crate::expressions::parser::stringify::to_string; +use crate::expressions::parser::Parser; +use crate::expressions::types::CellReferenceRC; #[test] fn issue_155_parser() { diff --git a/base/src/expressions/parser/test_move_formula.rs b/base/src/expressions/parser/tests/test_move_formula.rs similarity index 99% rename from base/src/expressions/parser/test_move_formula.rs rename to base/src/expressions/parser/tests/test_move_formula.rs index 0b84811..63196f0 100644 --- a/base/src/expressions/parser/test_move_formula.rs +++ b/base/src/expressions/parser/tests/test_move_formula.rs @@ -1,10 +1,8 @@ use std::collections::HashMap; use crate::expressions::parser::move_formula::{move_formula, MoveContext}; -use crate::expressions::types::Area; - -use super::super::types::CellReferenceRC; -use super::Parser; +use crate::expressions::parser::Parser; +use crate::expressions::types::{Area, CellReferenceRC}; #[test] fn test_move_formula() { diff --git a/base/src/expressions/parser/test_ranges.rs b/base/src/expressions/parser/tests/test_ranges.rs similarity index 94% rename from base/src/expressions/parser/test_ranges.rs rename to base/src/expressions/parser/tests/test_ranges.rs index 2e876dc..adc0f5e 100644 --- a/base/src/expressions/parser/test_ranges.rs +++ b/base/src/expressions/parser/tests/test_ranges.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use crate::expressions::lexer::LexerMode; -use super::super::parser::stringify::{to_rc_format, to_string}; -use super::super::types::CellReferenceRC; -use super::Parser; +use crate::expressions::parser::stringify::{to_rc_format, to_string}; +use crate::expressions::parser::Parser; +use crate::expressions::types::CellReferenceRC; struct Formula<'a> { formula_a1: &'a str, diff --git a/base/src/expressions/parser/tests/test_stringify.rs b/base/src/expressions/parser/tests/test_stringify.rs new file mode 100644 index 0000000..3d6d50d --- /dev/null +++ b/base/src/expressions/parser/tests/test_stringify.rs @@ -0,0 +1,34 @@ +#![allow(clippy::panic)] + +use std::collections::HashMap; + +use crate::expressions::parser::stringify::to_string; +use crate::expressions::parser::Parser; +use crate::expressions::types::CellReferenceRC; + +#[test] +fn exp_order() { + let worksheets = vec!["Sheet1".to_string()]; + let mut parser = Parser::new(worksheets, HashMap::new()); + + // Reference cell is Sheet1!A1 + let cell_reference = CellReferenceRC { + sheet: "Sheet1".to_string(), + row: 1, + column: 1, + }; + let t = parser.parse("(1 + 2)^3 + 4", &Some(cell_reference.clone())); + assert_eq!(to_string(&t, &cell_reference), "(1+2)^3+4"); + + let t = parser.parse("(C5 + 3)^R4", &Some(cell_reference.clone())); + assert_eq!(to_string(&t, &cell_reference), "(C5+3)^R4"); + + let t = parser.parse("(C5 + 3)^(R4*6)", &Some(cell_reference.clone())); + assert_eq!(to_string(&t, &cell_reference), "(C5+3)^(R4*6)"); + + let t = parser.parse("(C5)^(R4)", &Some(cell_reference.clone())); + assert_eq!(to_string(&t, &cell_reference), "C5^R4"); + + let t = parser.parse("(5)^(4)", &Some(cell_reference.clone())); + assert_eq!(to_string(&t, &cell_reference), "5^4"); +} diff --git a/base/src/expressions/parser/test_tables.rs b/base/src/expressions/parser/tests/test_tables.rs similarity index 97% rename from base/src/expressions/parser/test_tables.rs rename to base/src/expressions/parser/tests/test_tables.rs index 89fd880..ebb177a 100644 --- a/base/src/expressions/parser/test_tables.rs +++ b/base/src/expressions/parser/tests/test_tables.rs @@ -6,8 +6,8 @@ use crate::expressions::parser::stringify::to_string; use crate::expressions::utils::{number_to_column, parse_reference_a1}; use crate::types::{Table, TableColumn, TableStyleInfo}; -use super::super::types::CellReferenceRC; -use super::Parser; +use crate::expressions::parser::Parser; +use crate::expressions::types::CellReferenceRC; fn create_test_table( table_name: &str, diff --git a/base/src/functions/financial.rs b/base/src/functions/financial.rs index 9a6f11c..766eb7d 100644 --- a/base/src/functions/financial.rs +++ b/base/src/functions/financial.rs @@ -89,6 +89,9 @@ fn compute_future_value( if rate == 0.0 { return Ok(-pv - pmt * nper); } + if rate == -1.0 && nper < 0.0 { + return Err((Error::DIV, "Divide by zero".to_string())); + } let rate_nper = (1.0 + rate).powf(nper); let fv = if period_start { diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index f53465d..fcacb71 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -51,6 +51,7 @@ mod test_number_format; mod test_escape_quotes; mod test_extend; +mod test_fn_fv; mod test_fn_type; mod test_frozen_rows_and_columns; mod test_geomean; diff --git a/base/src/test/test_fn_formulatext.rs b/base/src/test/test_fn_formulatext.rs index 4825990..bfe338d 100644 --- a/base/src/test/test_fn_formulatext.rs +++ b/base/src/test/test_fn_formulatext.rs @@ -2,9 +2,6 @@ use crate::test::util::new_empty_model; -#[test] -fn simple_cases() {} - #[test] fn wrong_number_of_arguments() { let mut model = new_empty_model(); diff --git a/base/src/test/test_fn_fv.rs b/base/src/test/test_fn_fv.rs new file mode 100644 index 0000000..69b72d0 --- /dev/null +++ b/base/src/test/test_fn_fv.rs @@ -0,0 +1,36 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn computation() { + let i2 = "=-C2*(1+D2)^E2-F2*((D2+1)*((1+D2)^E2-1))/D2"; + + let mut model = new_empty_model(); + model._set("C2", "1"); + model._set("D2", "2"); + model._set("E2", "3"); + model._set("F2", "4"); + + model._set("I2", i2); + + model.evaluate(); + + assert_eq!(model._get_text("I2"), "-183"); + assert_eq!(model._get_formula("I2"), i2); +} + +#[test] +fn format_as_currency() { + let mut model = new_empty_model(); + model._set("C2", "1"); + model._set("D2", "2"); + model._set("E2", "3"); + model._set("F2", "4"); + + model._set("I2", "=FV(D2,E2,F2,C2,1)"); + + model.evaluate(); + + assert_eq!(model._get_text("I2"), "-$183.00"); +} diff --git a/base/src/units.rs b/base/src/units.rs index 9b9ae68..3c82e14 100644 --- a/base/src/units.rs +++ b/base/src/units.rs @@ -309,6 +309,7 @@ impl Model { Function::Sum => self.units_fn_sum_like(args, cell), Function::Average => self.units_fn_sum_like(args, cell), Function::Pmt => self.units_fn_currency(args, cell), + Function::Fv => self.units_fn_currency(args, cell), Function::Nper => self.units_fn_currency(args, cell), Function::Npv => self.units_fn_currency(args, cell), Function::Irr => self.units_fn_percentage(args, cell), diff --git a/xlsx/tests/calc_tests/FV.xlsx b/xlsx/tests/calc_tests/FV.xlsx index 9df63db..8bb9726 100644 Binary files a/xlsx/tests/calc_tests/FV.xlsx and b/xlsx/tests/calc_tests/FV.xlsx differ