FIX: Refactor some finatial functions to use common code
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
49ae2d8915
commit
420ea9829c
@@ -194,16 +194,24 @@ fn compute_ppmt(
|
|||||||
// In these formulas the payment (pmt) is normally negative
|
// In these formulas the payment (pmt) is normally negative
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
// FIXME: These three functions (get_array_of_numbers..) need to be refactored
|
fn get_array_of_numbers_generic(
|
||||||
// They are really similar expect for small issues
|
|
||||||
fn get_array_of_numbers(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
arg: &Node,
|
arg: &Node,
|
||||||
cell: &CellReferenceIndex,
|
cell: &CellReferenceIndex,
|
||||||
|
accept_number_node: bool,
|
||||||
|
handle_empty_cell: impl Fn() -> Result<Option<f64>, CalcResult>,
|
||||||
|
handle_non_number_cell: impl Fn() -> Result<Option<f64>, CalcResult>,
|
||||||
) -> Result<Vec<f64>, CalcResult> {
|
) -> Result<Vec<f64>, CalcResult> {
|
||||||
let mut values = Vec::new();
|
let mut values = Vec::new();
|
||||||
match self.evaluate_node_in_context(arg, *cell) {
|
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 } => {
|
CalcResult::Range { left, right } => {
|
||||||
if left.sheet != right.sheet {
|
if left.sheet != right.sheet {
|
||||||
return Err(CalcResult::new_error(
|
return Err(CalcResult::new_error(
|
||||||
@@ -212,6 +220,7 @@ impl Model {
|
|||||||
"Ranges are in different sheets".to_string(),
|
"Ranges are in different sheets".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let sheet = left.sheet;
|
||||||
let row1 = left.row;
|
let row1 = left.row;
|
||||||
let mut row2 = right.row;
|
let mut row2 = right.row;
|
||||||
let column1 = left.column;
|
let column1 = left.column;
|
||||||
@@ -219,12 +228,12 @@ impl Model {
|
|||||||
if row1 == 1 && row2 == LAST_ROW {
|
if row1 == 1 && row2 == LAST_ROW {
|
||||||
row2 = self
|
row2 = self
|
||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(sheet)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
CalcResult::new_error(
|
CalcResult::new_error(
|
||||||
Error::ERROR,
|
Error::ERROR,
|
||||||
*cell,
|
*cell,
|
||||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
format!("Invalid worksheet index: '{}'", sheet),
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.dimension()
|
.dimension()
|
||||||
@@ -233,30 +242,32 @@ impl Model {
|
|||||||
if column1 == 1 && column2 == LAST_COLUMN {
|
if column1 == 1 && column2 == LAST_COLUMN {
|
||||||
column2 = self
|
column2 = self
|
||||||
.workbook
|
.workbook
|
||||||
.worksheet(left.sheet)
|
.worksheet(sheet)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
CalcResult::new_error(
|
CalcResult::new_error(
|
||||||
Error::ERROR,
|
Error::ERROR,
|
||||||
*cell,
|
*cell,
|
||||||
format!("Invalid worksheet index: '{}'", left.sheet),
|
format!("Invalid worksheet index: '{}'", sheet),
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.dimension()
|
.dimension()
|
||||||
.max_column;
|
.max_column;
|
||||||
}
|
}
|
||||||
for row in row1..row2 + 1 {
|
for row in row1..=row2 {
|
||||||
for column in column1..(column2 + 1) {
|
for column in column1..=column2 {
|
||||||
match self.evaluate_cell(CellReferenceIndex {
|
let cell_ref = CellReferenceIndex { sheet, row, column };
|
||||||
sheet: left.sheet,
|
match self.evaluate_cell(cell_ref) {
|
||||||
row,
|
CalcResult::Number(value) => values.push(value),
|
||||||
column,
|
|
||||||
}) {
|
|
||||||
CalcResult::Number(value) => {
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
error @ CalcResult::Error { .. } => return Err(error),
|
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),
|
error @ CalcResult::Error { .. } => return Err(error),
|
||||||
_ => {
|
_ => {
|
||||||
// We ignore booleans and strings
|
handle_non_number_cell()?;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Ok(values)
|
Ok(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_array_of_numbers(
|
||||||
|
&mut self,
|
||||||
|
arg: &Node,
|
||||||
|
cell: &CellReferenceIndex,
|
||||||
|
) -> Result<Vec<f64>, 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(
|
fn get_array_of_numbers_xpnv(
|
||||||
&mut self,
|
&mut self,
|
||||||
arg: &Node,
|
arg: &Node,
|
||||||
cell: &CellReferenceIndex,
|
cell: &CellReferenceIndex,
|
||||||
error: Error,
|
error: Error,
|
||||||
) -> Result<Vec<f64>, CalcResult> {
|
) -> Result<Vec<f64>, CalcResult> {
|
||||||
let mut values = Vec::new();
|
self.get_array_of_numbers_generic(
|
||||||
match self.evaluate_node_in_context(arg, *cell) {
|
arg,
|
||||||
CalcResult::Number(value) => values.push(value),
|
cell,
|
||||||
CalcResult::Range { left, right } => {
|
true, // accept_number_node
|
||||||
if left.sheet != right.sheet {
|
|| {
|
||||||
return Err(CalcResult::new_error(
|
Err(CalcResult::new_error(
|
||||||
Error::VALUE,
|
Error::NUM,
|
||||||
*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,
|
|
||||||
*cell,
|
*cell,
|
||||||
"Expected number".to_string(),
|
"Expected number".to_string(),
|
||||||
));
|
))
|
||||||
}
|
},
|
||||||
};
|
|| {
|
||||||
Ok(values)
|
Err(CalcResult::new_error(
|
||||||
|
error.clone(),
|
||||||
|
*cell,
|
||||||
|
"Expected number".to_string(),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_array_of_numbers_xirr(
|
fn get_array_of_numbers_xirr(
|
||||||
@@ -365,81 +327,19 @@ impl Model {
|
|||||||
arg: &Node,
|
arg: &Node,
|
||||||
cell: &CellReferenceIndex,
|
cell: &CellReferenceIndex,
|
||||||
) -> Result<Vec<f64>, CalcResult> {
|
) -> Result<Vec<f64>, CalcResult> {
|
||||||
let mut values = Vec::new();
|
self.get_array_of_numbers_generic(
|
||||||
match self.evaluate_node_in_context(arg, *cell) {
|
arg,
|
||||||
CalcResult::Range { left, right } => {
|
cell,
|
||||||
if left.sheet != right.sheet {
|
false, // Do not accept a single number node
|
||||||
return Err(CalcResult::new_error(
|
|| Ok(Some(0.0)), // Treat empty cells as zero
|
||||||
Error::VALUE,
|
|| {
|
||||||
*cell,
|
Err(CalcResult::new_error(
|
||||||
"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(
|
|
||||||
Error::VALUE,
|
Error::VALUE,
|
||||||
*cell,
|
*cell,
|
||||||
"Expected number".to_string(),
|
"Expected number".to_string(),
|
||||||
));
|
))
|
||||||
}
|
},
|
||||||
};
|
)
|
||||||
Ok(values)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PMT(rate, nper, pv, [fv], [type])
|
/// PMT(rate, nper, pv, [fv], [type])
|
||||||
|
|||||||
Reference in New Issue
Block a user