diff --git a/base/src/expressions/parser/static_analysis.rs b/base/src/expressions/parser/static_analysis.rs index e4f64e2..6253d82 100644 --- a/base/src/expressions/parser/static_analysis.rs +++ b/base/src/expressions/parser/static_analysis.rs @@ -871,6 +871,11 @@ fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec args_signature_scalars(arg_count, 2, 0), Function::Combina => args_signature_scalars(arg_count, 2, 0), Function::Sumsq => vec![Signature::Vector; arg_count], + + Function::N => args_signature_scalars(arg_count, 1, 0), + Function::Sheets => args_signature_scalars(arg_count, 0, 1), + Function::Cell => args_signature_scalars(arg_count, 1, 1), + Function::Info => args_signature_scalars(arg_count, 1, 1), } } @@ -1130,5 +1135,9 @@ fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult { Function::Combin => scalar_arguments(args), Function::Combina => scalar_arguments(args), Function::Sumsq => StaticResult::Scalar, + Function::N => scalar_arguments(args), + Function::Sheets => scalar_arguments(args), + Function::Cell => scalar_arguments(args), + Function::Info => scalar_arguments(args), } } diff --git a/base/src/functions/information.rs b/base/src/functions/information.rs index 8121c65..2acb431 100644 --- a/base/src/functions/information.rs +++ b/base/src/functions/information.rs @@ -1,6 +1,6 @@ use crate::{ calc_result::CalcResult, - expressions::{parser::Node, token::Error, types::CellReferenceIndex}, + expressions::{parser::Node, token::Error, types::CellReferenceIndex, utils::number_to_column}, model::{Model, ParsedDefinedName}, }; @@ -320,4 +320,150 @@ impl Model { message: "Invalid name".to_string(), } } + + pub(crate) fn fn_n(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + let arg_count = args.len(); + if arg_count != 1 { + return CalcResult::new_args_number_error(cell); + } + let value = match self.evaluate_node_in_context(&args[0], cell) { + CalcResult::Number(n) => n, + CalcResult::String(_) => 0.0, + CalcResult::Boolean(f) => { + if f { + 1.0 + } else { + 0.0 + } + } + CalcResult::EmptyCell | CalcResult::EmptyArg => 0.0, + error @ CalcResult::Error { .. } => return error, + CalcResult::Range { .. } => { + return CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Arrays not supported yet".to_string(), + } + } + CalcResult::Array(_) => { + return CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Arrays not supported yet".to_string(), + } + } + }; + + CalcResult::Number(value) + } + + pub(crate) fn fn_sheets(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + let arg_count = args.len(); + if arg_count > 1 { + return CalcResult::new_args_number_error(cell); + } + if arg_count == 1 { + return CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Sheets function with an argument is not implemented".to_string(), + }; + } + let sheet_count = self.workbook.worksheets.len() as f64; + CalcResult::Number(sheet_count) + } + + pub(crate) fn fn_cell(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + let arg_count = args.len(); + if arg_count == 0 || arg_count > 2 { + return CalcResult::new_args_number_error(cell); + } + let reference = if arg_count == 2 { + match self.evaluate_node_with_reference(&args[1], cell) { + CalcResult::Range { left, right: _ } => { + // we just take the left cell of the range + left + } + _ => { + return CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "Argument must be a reference".to_string(), + } + } + } + } else { + CellReferenceIndex { + sheet: cell.sheet, + row: cell.row, + column: cell.column, + } + }; + let info_type = match self.get_string(&args[0], cell) { + Ok(s) => s.to_uppercase(), + Err(e) => return e, + }; + match info_type.as_str() { + "ADDRESS" => { + if reference.sheet != cell.sheet { + return CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "References to other sheets not implemented".to_string(), + }; + } + let column = match number_to_column(reference.column) { + Some(c) => c, + None => { + return CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "Invalid column".to_string(), + } + } + }; + let address = format!("${}${}", column, reference.row); + CalcResult::String(address) + } + "COL" => CalcResult::Number(reference.column as f64), + "COLOR" | "FILENAME" | "FORMAT" | "PARENTHESES" | "PREFIX" | "PROTECT" | "WIDTH" => { + CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "info_type not implemented".to_string(), + } + } + "CONTENTS" => self.evaluate_cell(reference), + "ROW" => CalcResult::Number(reference.row as f64), + "TYPE" => { + let cell_type = match self.evaluate_cell(reference) { + CalcResult::EmptyCell => "b", + CalcResult::String(_) => "l", + CalcResult::Number(_) => "v", + CalcResult::Boolean(_) => "v", + CalcResult::Error { .. } => "v", + CalcResult::Range { .. } => "v", + CalcResult::EmptyArg => "v", + CalcResult::Array(_) => "v", + }; + CalcResult::String(cell_type.to_string()) + } + _ => CalcResult::Error { + error: Error::VALUE, + origin: cell, + message: "Invalid info_type".to_string(), + }, + } + } + + pub(crate) fn fn_info(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + if args.is_empty() || args.len() > 2 { + return CalcResult::new_args_number_error(cell); + } + CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Info function not implemented".to_string(), + } + } } diff --git a/base/src/functions/mod.rs b/base/src/functions/mod.rs index 5e994e0..53a2868 100644 --- a/base/src/functions/mod.rs +++ b/base/src/functions/mod.rs @@ -134,6 +134,11 @@ pub enum Function { Sheet, Type, + Sheets, + N, + Cell, + Info, + // Lookup and reference Hlookup, Index, @@ -308,7 +313,7 @@ pub enum Function { } impl Function { - pub fn into_iter() -> IntoIter { + pub fn into_iter() -> IntoIter { [ Function::And, Function::False, @@ -562,6 +567,10 @@ impl Function { Function::Combin, Function::Combina, Function::Sumsq, + Function::N, + Function::Cell, + Function::Info, + Function::Sheets, ] .into_iter() } @@ -614,6 +623,7 @@ impl Function { Function::Decimal => "_xlfn.DECIMAL".to_string(), Function::Arabic => "_xlfn.ARABIC".to_string(), Function::Combina => "_xlfn.COMBINA".to_string(), + Function::Sheets => "_xlfn.SHEETS".to_string(), _ => self.to_string(), } @@ -894,6 +904,11 @@ impl Function { "SUBTOTAL" => Some(Function::Subtotal), + "N" => Some(Function::N), + "CELL" => Some(Function::Cell), + "INFO" => Some(Function::Info), + "SHEETS" | "_XLFN.SHEETS" => Some(Function::Sheets), + _ => None, } } @@ -1154,6 +1169,11 @@ impl fmt::Display for Function { Function::Combin => write!(f, "COMBIN"), Function::Combina => write!(f, "COMBINA"), Function::Sumsq => write!(f, "SUMSQ"), + + Function::N => write!(f, "N"), + Function::Cell => write!(f, "CELL"), + Function::Info => write!(f, "INFO"), + Function::Sheets => write!(f, "SHEETS"), } } } @@ -1434,6 +1454,10 @@ impl Model { Function::Combin => self.fn_combin(args, cell), Function::Combina => self.fn_combina(args, cell), Function::Sumsq => self.fn_sumsq(args, cell), + Function::N => self.fn_n(args, cell), + Function::Cell => self.fn_cell(args, cell), + Function::Info => self.fn_info(args, cell), + Function::Sheets => self.fn_sheets(args, cell), } } } diff --git a/xlsx/tests/calc_tests/N_CELL_INFO_SHEETS.xlsx b/xlsx/tests/calc_tests/N_CELL_INFO_SHEETS.xlsx new file mode 100644 index 0000000..f9d9300 Binary files /dev/null and b/xlsx/tests/calc_tests/N_CELL_INFO_SHEETS.xlsx differ