use crate::{ calc_result::{CalcResult, Range}, expressions::{ parser::{ArrayNode, Node}, token::Error, types::CellReferenceIndex, }, formatter::format::parse_formatted_number, model::Model, }; pub(crate) enum NumberOrArray { Number(f64), Array(Vec>), } impl Model { pub(crate) fn cast_number(&self, s: &str) -> Option { match s.trim().parse::() { Ok(f) => Some(f), _ => { let currency = &self.locale.currency.symbol; let mut currencies = vec!["$", "€"]; if !currencies.iter().any(|e| *e == currency) { currencies.push(currency); } // Try to parse as a formatted number (e.g., dates, currencies, percentages) if let Ok((v, _number_format)) = parse_formatted_number(s, ¤cies) { return Some(v); } None } } } pub(crate) fn get_number_or_array( &mut self, node: &Node, cell: CellReferenceIndex, ) -> Result { match self.evaluate_node_in_context(node, cell) { CalcResult::Number(f) => Ok(NumberOrArray::Number(f)), CalcResult::String(s) => match self.cast_number(&s) { Some(f) => Ok(NumberOrArray::Number(f)), None => Err(CalcResult::new_error( Error::VALUE, cell, "Expecting number".to_string(), )), }, CalcResult::Boolean(f) => { if f { Ok(NumberOrArray::Number(1.0)) } else { Ok(NumberOrArray::Number(0.0)) } } CalcResult::EmptyCell | CalcResult::EmptyArg => Ok(NumberOrArray::Number(0.0)), CalcResult::Range { left, right } => { let sheet = left.sheet; if sheet != right.sheet { return Err(CalcResult::Error { error: Error::ERROR, origin: cell, message: "3D ranges are not allowed".to_string(), }); } // we need to convert the range into an array let mut array = Vec::new(); for row in left.row..=right.row { let mut row_data = Vec::new(); for column in left.column..=right.column { let value = match self.evaluate_cell(CellReferenceIndex { sheet, column, row }) { CalcResult::String(s) => ArrayNode::String(s), CalcResult::Number(f) => ArrayNode::Number(f), CalcResult::Boolean(b) => ArrayNode::Boolean(b), CalcResult::Error { error, .. } => ArrayNode::Error(error), CalcResult::Range { .. } => { // if we do things right this can never happen. // the evaluation of a cell should never return a range ArrayNode::Number(0.0) } CalcResult::EmptyCell => ArrayNode::Number(0.0), CalcResult::EmptyArg => ArrayNode::Number(0.0), CalcResult::Array(_) => { // if we do things right this can never happen. // the evaluation of a cell should never return an array ArrayNode::Number(0.0) } }; row_data.push(value); } array.push(row_data); } Ok(NumberOrArray::Array(array)) } CalcResult::Array(s) => Ok(NumberOrArray::Array(s)), error @ CalcResult::Error { .. } => Err(error), } } pub(crate) fn get_number( &mut self, node: &Node, cell: CellReferenceIndex, ) -> Result { let result = self.evaluate_node_in_context(node, cell); self.cast_to_number(result, cell) } pub(crate) fn cast_to_number( &mut self, result: CalcResult, cell: CellReferenceIndex, ) -> Result { match result { CalcResult::Number(f) => Ok(f), CalcResult::String(s) => match self.cast_number(&s) { Some(f) => Ok(f), None => Err(CalcResult::new_error( Error::VALUE, cell, "Expecting number".to_string(), )), }, CalcResult::Boolean(f) => { if f { Ok(1.0) } else { Ok(0.0) } } CalcResult::EmptyCell | CalcResult::EmptyArg => Ok(0.0), error @ CalcResult::Error { .. } => Err(error), CalcResult::Range { .. } => Err(CalcResult::Error { error: Error::NIMPL, origin: cell, message: "Arrays not supported yet".to_string(), }), CalcResult::Array(_) => Err(CalcResult::Error { error: Error::NIMPL, origin: cell, message: "Arrays not supported yet".to_string(), }), } } pub(crate) fn get_number_no_bools( &mut self, node: &Node, cell: CellReferenceIndex, ) -> Result { let result = self.evaluate_node_in_context(node, cell); if matches!(result, CalcResult::Boolean(_)) { return Err(CalcResult::new_error( Error::VALUE, cell, "Expecting number".to_string(), )); } self.cast_to_number(result, cell) } pub(crate) fn get_string( &mut self, node: &Node, cell: CellReferenceIndex, ) -> Result { let result = self.evaluate_node_in_context(node, cell); self.cast_to_string(result, cell) } pub(crate) fn cast_to_string( &mut self, result: CalcResult, cell: CellReferenceIndex, ) -> Result { // FIXME: I think when casting a number we should convert it to_precision(x, 15) // See function Exact match result { CalcResult::Number(f) => Ok(format!("{f}")), CalcResult::String(s) => Ok(s), CalcResult::Boolean(f) => { if f { Ok("TRUE".to_string()) } else { Ok("FALSE".to_string()) } } CalcResult::EmptyCell | CalcResult::EmptyArg => Ok("".to_string()), error @ CalcResult::Error { .. } => Err(error), CalcResult::Range { .. } => Err(CalcResult::Error { error: Error::NIMPL, origin: cell, message: "Arrays not supported yet".to_string(), }), CalcResult::Array(_) => Err(CalcResult::Error { error: Error::NIMPL, origin: cell, message: "Arrays not supported yet".to_string(), }), } } pub(crate) fn get_boolean( &mut self, node: &Node, cell: CellReferenceIndex, ) -> Result { let result = self.evaluate_node_in_context(node, cell); self.cast_to_bool(result, cell) } fn cast_to_bool( &mut self, result: CalcResult, cell: CellReferenceIndex, ) -> Result { match result { CalcResult::Number(f) => { if f == 0.0 { return Ok(false); } Ok(true) } CalcResult::String(s) => { if s.to_lowercase() == *"true" { return Ok(true); } else if s.to_lowercase() == *"false" { return Ok(false); } Err(CalcResult::Error { error: Error::VALUE, origin: cell, message: "Expected boolean".to_string(), }) } CalcResult::Boolean(b) => Ok(b), CalcResult::EmptyCell | CalcResult::EmptyArg => Ok(false), error @ CalcResult::Error { .. } => Err(error), CalcResult::Range { .. } => Err(CalcResult::Error { error: Error::NIMPL, origin: cell, message: "Arrays not supported yet".to_string(), }), CalcResult::Array(_) => Err(CalcResult::Error { error: Error::NIMPL, origin: cell, message: "Arrays not supported yet".to_string(), }), } } // tries to return a reference. That is either a reference or a formula that evaluates to a range/reference pub(crate) fn get_reference( &mut self, node: &Node, cell: CellReferenceIndex, ) -> Result { match node { Node::ReferenceKind { column, absolute_column, row, absolute_row, sheet_index, sheet_name: _, } => { let left = CellReferenceIndex { sheet: *sheet_index, row: if *absolute_row { *row } else { *row + cell.row }, column: if *absolute_column { *column } else { *column + cell.column }, }; Ok(Range { left, right: left }) } _ => { let value = self.evaluate_node_in_context(node, cell); if value.is_error() { return Err(value); } if let CalcResult::Range { left, right } = value { Ok(Range { left, right }) } else { Err(CalcResult::Error { error: Error::VALUE, origin: cell, message: "Expected reference".to_string(), }) } } } } }