From 420ea9829c71d963ffc35a628a7aaaab67951334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Sat, 16 Nov 2024 23:01:44 +0100 Subject: [PATCH] FIX: Refactor some finatial functions to use common code --- base/src/functions/financial.rs | 246 ++++++++++---------------------- 1 file changed, 73 insertions(+), 173 deletions(-) diff --git a/base/src/functions/financial.rs b/base/src/functions/financial.rs index 797905a..965a7c2 100644 --- a/base/src/functions/financial.rs +++ b/base/src/functions/financial.rs @@ -194,16 +194,24 @@ fn compute_ppmt( // In these formulas the payment (pmt) is normally negative impl Model { - // FIXME: These three functions (get_array_of_numbers..) need to be refactored - // They are really similar expect for small issues - fn get_array_of_numbers( + fn get_array_of_numbers_generic( &mut self, arg: &Node, cell: &CellReferenceIndex, + accept_number_node: bool, + handle_empty_cell: impl Fn() -> Result, CalcResult>, + handle_non_number_cell: impl Fn() -> Result, CalcResult>, ) -> Result, CalcResult> { let mut values = Vec::new(); match self.evaluate_node_in_context(arg, *cell) { - CalcResult::Number(value) => values.push(value), + CalcResult::Number(value) if accept_number_node => values.push(value), + CalcResult::Number(_) => { + return Err(CalcResult::new_error( + Error::VALUE, + *cell, + "Expected range of numbers".to_string(), + )); + } CalcResult::Range { left, right } => { if left.sheet != right.sheet { return Err(CalcResult::new_error( @@ -212,6 +220,7 @@ impl Model { "Ranges are in different sheets".to_string(), )); } + let sheet = left.sheet; let row1 = left.row; let mut row2 = right.row; let column1 = left.column; @@ -219,12 +228,12 @@ impl Model { if row1 == 1 && row2 == LAST_ROW { row2 = self .workbook - .worksheet(left.sheet) + .worksheet(sheet) .map_err(|_| { CalcResult::new_error( Error::ERROR, *cell, - format!("Invalid worksheet index: '{}'", left.sheet), + format!("Invalid worksheet index: '{}'", sheet), ) })? .dimension() @@ -233,30 +242,32 @@ impl Model { if column1 == 1 && column2 == LAST_COLUMN { column2 = self .workbook - .worksheet(left.sheet) + .worksheet(sheet) .map_err(|_| { CalcResult::new_error( Error::ERROR, *cell, - format!("Invalid worksheet index: '{}'", left.sheet), + format!("Invalid worksheet index: '{}'", sheet), ) })? .dimension() .max_column; } - for row in row1..row2 + 1 { - for column in column1..(column2 + 1) { - match self.evaluate_cell(CellReferenceIndex { - sheet: left.sheet, - row, - column, - }) { - CalcResult::Number(value) => { - values.push(value); - } + for row in row1..=row2 { + for column in column1..=column2 { + let cell_ref = CellReferenceIndex { sheet, row, column }; + match self.evaluate_cell(cell_ref) { + CalcResult::Number(value) => values.push(value), error @ CalcResult::Error { .. } => return Err(error), + CalcResult::EmptyCell => { + if let Some(value) = handle_empty_cell()? { + values.push(value); + } + } _ => { - // We ignore booleans and strings + if let Some(value) = handle_non_number_cell()? { + values.push(value); + } } } } @@ -264,100 +275,51 @@ impl Model { } error @ CalcResult::Error { .. } => return Err(error), _ => { - // We ignore booleans and strings + handle_non_number_cell()?; } - }; + } Ok(values) } + fn get_array_of_numbers( + &mut self, + arg: &Node, + cell: &CellReferenceIndex, + ) -> Result, CalcResult> { + self.get_array_of_numbers_generic( + arg, + cell, + true, // accept_number_node + || Ok(None), // Ignore empty cells + || Ok(None), // Ignore non-number cells + ) + } + fn get_array_of_numbers_xpnv( &mut self, arg: &Node, cell: &CellReferenceIndex, error: Error, ) -> Result, CalcResult> { - let mut values = Vec::new(); - match self.evaluate_node_in_context(arg, *cell) { - CalcResult::Number(value) => values.push(value), - CalcResult::Range { left, right } => { - if left.sheet != right.sheet { - return Err(CalcResult::new_error( - Error::VALUE, - *cell, - "Ranges are in different sheets".to_string(), - )); - } - let row1 = left.row; - let mut row2 = right.row; - let column1 = left.column; - let mut column2 = right.column; - if row1 == 1 && row2 == LAST_ROW { - row2 = self - .workbook - .worksheet(left.sheet) - .map_err(|_| { - CalcResult::new_error( - Error::ERROR, - *cell, - format!("Invalid worksheet index: '{}'", left.sheet), - ) - })? - .dimension() - .max_row; - } - if column1 == 1 && column2 == LAST_COLUMN { - column2 = self - .workbook - .worksheet(left.sheet) - .map_err(|_| { - CalcResult::new_error( - Error::ERROR, - *cell, - format!("Invalid worksheet index: '{}'", left.sheet), - ) - })? - .dimension() - .max_column; - } - for row in row1..row2 + 1 { - for column in column1..(column2 + 1) { - match self.evaluate_cell(CellReferenceIndex { - sheet: left.sheet, - row, - column, - }) { - CalcResult::Number(value) => { - values.push(value); - } - error @ CalcResult::Error { .. } => return Err(error), - CalcResult::EmptyCell => { - return Err(CalcResult::new_error( - Error::NUM, - *cell, - "Expected number".to_string(), - )); - } - _ => { - return Err(CalcResult::new_error( - error, - *cell, - "Expected number".to_string(), - )); - } - } - } - } - } - error @ CalcResult::Error { .. } => return Err(error), - _ => { - return Err(CalcResult::new_error( - error, + self.get_array_of_numbers_generic( + arg, + cell, + true, // accept_number_node + || { + Err(CalcResult::new_error( + Error::NUM, *cell, "Expected number".to_string(), - )); - } - }; - Ok(values) + )) + }, + || { + Err(CalcResult::new_error( + error.clone(), + *cell, + "Expected number".to_string(), + )) + }, + ) } fn get_array_of_numbers_xirr( @@ -365,81 +327,19 @@ impl Model { arg: &Node, cell: &CellReferenceIndex, ) -> Result, CalcResult> { - let mut values = Vec::new(); - match self.evaluate_node_in_context(arg, *cell) { - CalcResult::Range { left, right } => { - if left.sheet != right.sheet { - return Err(CalcResult::new_error( - Error::VALUE, - *cell, - "Ranges are in different sheets".to_string(), - )); - } - let row1 = left.row; - let mut row2 = right.row; - let column1 = left.column; - let mut column2 = right.column; - if row1 == 1 && row2 == LAST_ROW { - row2 = self - .workbook - .worksheet(left.sheet) - .map_err(|_| { - CalcResult::new_error( - Error::ERROR, - *cell, - format!("Invalid worksheet index: '{}'", left.sheet), - ) - })? - .dimension() - .max_row; - } - if column1 == 1 && column2 == LAST_COLUMN { - column2 = self - .workbook - .worksheet(left.sheet) - .map_err(|_| { - CalcResult::new_error( - Error::ERROR, - *cell, - format!("Invalid worksheet index: '{}'", left.sheet), - ) - })? - .dimension() - .max_column; - } - for row in row1..row2 + 1 { - for column in column1..(column2 + 1) { - match self.evaluate_cell(CellReferenceIndex { - sheet: left.sheet, - row, - column, - }) { - CalcResult::Number(value) => { - values.push(value); - } - error @ CalcResult::Error { .. } => return Err(error), - CalcResult::EmptyCell => values.push(0.0), - _ => { - return Err(CalcResult::new_error( - Error::VALUE, - *cell, - "Expected number".to_string(), - )); - } - } - } - } - } - error @ CalcResult::Error { .. } => return Err(error), - _ => { - return Err(CalcResult::new_error( + self.get_array_of_numbers_generic( + arg, + cell, + false, // Do not accept a single number node + || Ok(Some(0.0)), // Treat empty cells as zero + || { + Err(CalcResult::new_error( Error::VALUE, *cell, "Expected number".to_string(), - )); - } - }; - Ok(values) + )) + }, + ) } /// PMT(rate, nper, pv, [fv], [type])