UPDATE: Dump of initial files

This commit is contained in:
Nicolás Hatcher
2023-11-18 21:26:18 +01:00
commit c5b8efd83d
279 changed files with 42654 additions and 0 deletions

View File

@@ -0,0 +1,843 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
utils::ParsedReference,
};
use super::util::{compare_values, from_wildcard_to_regex, result_matches_regex, values_are_equal};
impl Model {
pub(crate) fn fn_index(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let row_num;
let col_num;
if args.len() == 3 {
row_num = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if row_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
col_num = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if col_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
} else if args.len() == 2 {
row_num = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if row_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
col_num = -1.0;
} else {
return CalcResult::new_args_number_error(cell);
}
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Range { left, right } => {
let row;
let column;
if (col_num + 1.0).abs() < f64::EPSILON {
if left.row == right.row {
column = left.column + (row_num as i32) - 1;
row = left.row;
} else {
column = left.column;
row = left.row + (row_num as i32) - 1;
}
} else {
row = left.row + (row_num as i32) - 1;
column = left.column + (col_num as i32) - 1;
}
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Wrong reference".to_string(),
};
}
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Wrong reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Expecting a Range".to_string(),
},
}
}
// MATCH(lookup_value, lookup_array, [match_type])
// The MATCH function syntax has the following arguments:
// * lookup_value Required. The value that you want to match in lookup_array.
// The lookup_value argument can be a value (number, text, or logical value)
// or a cell reference to a number, text, or logical value.
// * lookup_array Required. The range of cells being searched.
// * match_type Optional. The number -1, 0, or 1.
// The match_type argument specifies how Excel matches lookup_value
// with values in lookup_array. The default value for this argument is 1.
// NOTE: Please read the caveat above in binary search
pub(crate) fn fn_match(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 3 || args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let target = self.evaluate_node_in_context(&args[0], cell);
if target.is_error() {
return target;
}
if matches!(target, CalcResult::EmptyCell) {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Cannot match empty cell".to_string(),
};
}
let match_type = if args.len() == 3 {
match self.get_number(&args[2], cell) {
Ok(v) => v as i32,
Err(s) => return s,
}
} else {
1
};
let match_range = self.evaluate_node_in_context(&args[1], cell);
match match_range {
CalcResult::Range { left, right } => {
match match_type {
-1 => {
// We apply binary search leftmost for value in the range
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
let row;
let column;
if is_row_vector {
row = left.row + m;
column = left.column;
} else {
column = left.column + m;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
if compare_values(&value, &target) >= 0 {
l = m + 1;
} else {
r = m;
}
}
// r is the number of elements less than target in the vector
// If target is less than the minimum return #N/A
if l == 0 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
// Now l points to the leftmost element
CalcResult::Number(l as f64)
}
0 => {
// We apply linear search
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &target {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| values_are_equal(x, &target))
};
for l in 0..n {
let row;
let column;
if is_row_vector {
row = left.row + l;
column = left.column;
} else {
column = left.column + l;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
if result_matches(&value) {
return CalcResult::Number(l as f64 + 1.0);
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
_ => {
// l is the number of elements less than target in the vector
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let l = self.binary_search(&target, &left, &right, is_row_vector);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
CalcResult::Number(l as f64 + 1.0)
}
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Invalid".to_string(),
},
}
}
/// HLOOKUP(lookup_value, table_array, row_index, [is_sorted])
/// We look for `lookup_value` in the first row of table array
/// We return the value in row `row_index` of the same column in `table_array`
/// `is_sorted` is true by default and assumes that values in first row are ordered
pub(crate) fn fn_hlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 4 || args.len() < 3 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
let row_index = match self.get_number(&args[2], cell) {
Ok(v) => v.floor() as i32,
Err(s) => return s,
};
let is_sorted = if args.len() == 4 {
match self.get_boolean(&args[3], cell) {
Ok(v) => v,
Err(s) => return s,
}
} else {
true
};
let range = self.evaluate_node_in_context(&args[1], cell);
match range {
CalcResult::Range { left, right } => {
if is_sorted {
// This assumes the values in row are in order
let l = self.binary_search(&lookup_value, &left, &right, false);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
let row = left.row + row_index - 1;
let column = left.column + l;
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
} else {
// Linear search for exact match
let n = right.column - left.column + 1;
let row = left.row + row_index - 1;
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, &lookup_value) == 0)
};
for l in 0..n {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row,
column: left.column + l,
});
if result_matches(&value) {
return self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column: left.column + l,
});
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
}
error @ CalcResult::Error { .. } => error,
CalcResult::String(_) => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
/// VLOOKUP(lookup_value, table_array, row_index, [is_sorted])
/// We look for `lookup_value` in the first column of table array
/// We return the value in column `column_index` of the same row in `table_array`
/// `is_sorted` is true by default and assumes that values in first column are ordered
pub(crate) fn fn_vlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 4 || args.len() < 3 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
let column_index = match self.get_number(&args[2], cell) {
Ok(v) => v.floor() as i32,
Err(s) => return s,
};
let is_sorted = if args.len() == 4 {
match self.get_boolean(&args[3], cell) {
Ok(v) => v,
Err(s) => return s,
}
} else {
true
};
let range = self.evaluate_node_in_context(&args[1], cell);
match range {
CalcResult::Range { left, right } => {
if is_sorted {
// This assumes the values in column are in order
let l = self.binary_search(&lookup_value, &left, &right, true);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
let row = left.row + l;
let column = left.column + column_index - 1;
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
} else {
// Linear search for exact match
let n = right.row - left.row + 1;
let column = left.column + column_index - 1;
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, &lookup_value) == 0)
};
for l in 0..n {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + l,
column: left.column,
});
if result_matches(&value) {
return self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + l,
column,
});
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
}
error @ CalcResult::Error { .. } => error,
CalcResult::String(_) => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
// LOOKUP(lookup_value, lookup_vector, [result_vector])
// Important: The values in lookup_vector must be placed in ascending order:
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE;
// otherwise, LOOKUP might not return the correct value.
// Uppercase and lowercase text are equivalent.
// TODO: Implement the other form of INDEX:
// INDEX(reference, row_num, [column_num], [area_num])
// NOTE: Please read the caveat above in binary search
pub(crate) fn fn_lookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 3 || args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let target = self.evaluate_node_in_context(&args[0], cell);
if target.is_error() {
return target;
}
let value = self.evaluate_node_in_context(&args[1], cell);
match value {
CalcResult::Range { left, right } => {
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Second argument must be a vector".to_string(),
};
}
let l = self.binary_search(&target, &left, &right, is_row_vector);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
if args.len() == 3 {
let target_range = self.evaluate_node_in_context(&args[2], cell);
match target_range {
CalcResult::Range {
left: l1,
right: _r1,
} => {
let row;
let column;
if is_row_vector {
row = l1.row + l;
column = l1.column;
} else {
column = l1.column + l;
row = l1.row;
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
} else {
let row;
let column;
if is_row_vector {
row = left.row + l;
column = left.column;
} else {
column = left.column + l;
row = left.row;
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
// ROW([reference])
// If reference is not present returns the row of the present cell.
// Otherwise returns the row number of reference
pub(crate) fn fn_row(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 1 {
return CalcResult::new_args_number_error(cell);
}
if args.is_empty() {
return CalcResult::Number(cell.row as f64);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number(c.left.row as f64),
Err(s) => s,
}
}
// ROWS(range)
// Returns the number of rows in range
pub(crate) fn fn_rows(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number((c.right.row - c.left.row + 1) as f64),
Err(s) => s,
}
}
// COLUMN([reference])
// If reference is not present returns the column of the present cell.
// Otherwise returns the column number of reference
pub(crate) fn fn_column(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 1 {
return CalcResult::new_args_number_error(cell);
}
if args.is_empty() {
return CalcResult::Number(cell.column as f64);
}
match self.get_reference(&args[0], cell) {
Ok(range) => CalcResult::Number(range.left.column as f64),
Err(s) => s,
}
}
/// CHOOSE(index_num, value1, [value2], ...)
/// Uses index_num to return a value from the list of value arguments.
pub(crate) fn fn_choose(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let index_num = match self.get_number(&args[0], cell) {
Ok(index_num) => index_num as usize,
Err(calc_err) => return calc_err,
};
if index_num < 1 || index_num > (args.len() - 1) {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Invalid index".to_string(),
};
}
self.evaluate_node_with_reference(&args[index_num], cell)
}
// COLUMNS(range)
// Returns the number of columns in range
pub(crate) fn fn_columns(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number((c.right.column - c.left.column + 1) as f64),
Err(s) => s,
}
}
// INDIRECT(ref_tex)
// Returns the reference specified by 'ref_text'
pub(crate) fn fn_indirect(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 2 || args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let value = self.get_string(&args[0], cell);
match value {
Ok(s) => {
if args.len() == 2 {
return CalcResult::Error {
error: Error::NIMPL,
origin: cell,
message: "Not implemented".to_string(),
};
}
let parsed_reference = ParsedReference::parse_reference_formula(
Some(cell.sheet),
&s,
&self.locale,
|name| self.get_sheet_index_by_name(name),
);
let parsed_reference = match parsed_reference {
Ok(reference) => reference,
Err(message) => {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message,
};
}
};
match parsed_reference {
ParsedReference::CellReference(reference) => CalcResult::Range {
left: reference,
right: reference,
},
ParsedReference::Range(left, right) => CalcResult::Range { left, right },
}
}
Err(v) => v,
}
}
// OFFSET(reference, rows, cols, [height], [width])
// Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
// The reference that is returned can be a single cell or a range of cells.
// You can specify the number of rows and the number of columns to be returned.
pub(crate) fn fn_offset(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let l = args.len();
if !(3..=5).contains(&l) {
return CalcResult::new_args_number_error(cell);
}
let reference = match self.get_reference(&args[0], cell) {
Ok(c) => c,
Err(s) => return s,
};
let rows = match self.get_number(&args[1], cell) {
Ok(c) => {
if c < 0.0 {
c.ceil() as i32
} else {
c.floor() as i32
}
}
Err(s) => return s,
};
let cols = match self.get_number(&args[2], cell) {
Ok(c) => {
if c < 0.0 {
c.ceil() as i32
} else {
c.floor() as i32
}
}
Err(s) => return s,
};
let row_start = reference.left.row + rows;
let column_start = reference.left.column + cols;
let width;
let height;
if l == 4 {
height = match self.get_number(&args[3], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
width = reference.right.column - reference.left.column;
} else if l == 5 {
height = match self.get_number(&args[3], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
width = match self.get_number(&args[4], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
} else {
width = reference.right.column - reference.left.column;
height = reference.right.row - reference.left.row;
}
// This is what Excel does
if width == -1 || height == -1 {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
// NB: Excel documentation says that negative values of width and height are not valid
// but in practice they are valid. We follow the documentation and not Excel
if width < -1 || height < -1 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "width and height cannot be negative".to_string(),
};
}
let column_end = column_start + width;
let row_end = row_start + height;
if row_start < 1 || row_end > LAST_ROW || column_start < 1 || column_end > LAST_COLUMN {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let left = CellReference {
sheet: reference.left.sheet,
row: row_start,
column: column_start,
};
let right = CellReference {
sheet: reference.right.sheet,
row: row_end,
column: column_end,
};
CalcResult::Range { left, right }
}
}