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,384 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use super::{
binary_search::{
binary_search_descending_or_greater, binary_search_descending_or_smaller,
binary_search_or_greater, binary_search_or_smaller,
},
util::{compare_values, from_wildcard_to_regex, result_matches_regex},
};
#[derive(PartialEq)]
enum SearchMode {
StartAtFirstItem = 1,
StartAtLastItem = -1,
BinarySearchDescending = -2,
BinarySearchAscending = 2,
}
#[derive(PartialEq)]
enum MatchMode {
ExactMatchSmaller = -1,
ExactMatch = 0,
ExactMatchLarger = 1,
WildcardMatch = 2,
}
// lookup_value in array, match_mode search_mode
fn linear_search(
lookup_value: &CalcResult,
array: &[CalcResult],
search_mode: SearchMode,
match_mode: MatchMode,
) -> Option<usize> {
let length = array.len();
match match_mode {
MatchMode::ExactMatch => {
// exact match
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
if compare_values(value, lookup_value) == 0 {
return Some(index);
}
}
return None;
}
MatchMode::ExactMatchSmaller | MatchMode::ExactMatchLarger => {
// exact match, if none found return the next smaller/larger item
let mut found_index = 0;
let mut approx = None;
let m_mode = match_mode as i32;
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
let c = compare_values(value, lookup_value);
if c == 0 {
return Some(index);
} else if c == m_mode {
match approx {
None => {
approx = Some(value.clone());
found_index = index;
}
Some(ref p) => {
if compare_values(p, value) == m_mode {
approx = Some(value.clone());
found_index = index;
}
}
}
}
}
if approx.is_none() {
return None;
} else {
return Some(found_index);
}
}
MatchMode::WildcardMatch => {
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..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
if result_matches(value) {
return Some(index);
}
}
}
}
None
}
impl Model {
/// The XLOOKUP function searches a range or an array, and then returns the item corresponding
/// to the first match it finds. If no match exists, then XLOOKUP can return the closest (approximate) match.
/// =XLOOKUP(lookup_value, lookup_array, return_array, [if_not_found], [match_mode], [search_mode])
///
/// lookup_array and return_array must be column or row arrays and of the same dimension.
/// Otherwise #VALUE! is returned
/// [if_not_found]
/// Where a valid match is not found, return the [if_not_found] text you supply.
/// If a valid match is not found, and [if_not_found] is missing, #N/A is returned.
///
/// [match_mode]
/// Specify the match type:
/// * 0 - Exact match. If none found, return #N/A. This is the default.
/// * -1 - Exact match. If none found, return the next smaller item.
/// * 1 - Exact match. If none found, return the next larger item.
/// * 2 - A wildcard match where *, ?, and ~ have special meaning.
///
/// [search_mode]
/// Specify the search mode to use:
/// * 1 - Perform a search starting at the first item. This is the default.
/// * -1 - Perform a reverse search starting at the last item.
/// * 2 - Perform a binary search that relies on lookup_array being sorted
/// in ascending order. If not sorted, invalid results will be returned.
/// * -2 - Perform a binary search that relies on lookup_array being sorted
/// in descending order. If not sorted, invalid results will be returned.
pub(crate) fn fn_xlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 3 || args.len() > 6 {
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;
}
// Get optional arguments
let if_not_found = if args.len() >= 4 {
let v = self.evaluate_node_in_context(&args[3], cell);
match v {
CalcResult::EmptyArg => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
},
_ => v,
}
} else {
// default
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
};
let match_mode = if args.len() >= 5 {
match self.get_number(&args[4], cell) {
Ok(c) => match c.floor() as i32 {
-1 => MatchMode::ExactMatchSmaller,
1 => MatchMode::ExactMatchLarger,
0 => MatchMode::ExactMatch,
2 => MatchMode::WildcardMatch,
_ => {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Unexpected number".to_string(),
};
}
},
Err(s) => return s,
}
} else {
// default
MatchMode::ExactMatch
};
let search_mode = if args.len() == 6 {
match self.get_number(&args[5], cell) {
Ok(c) => match c.floor() as i32 {
1 => SearchMode::StartAtFirstItem,
-1 => SearchMode::StartAtLastItem,
-2 => SearchMode::BinarySearchDescending,
2 => SearchMode::BinarySearchAscending,
_ => {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Unexpected number".to_string(),
};
}
},
Err(s) => return s,
}
} else {
// default
SearchMode::StartAtFirstItem
};
// lookup_array
match self.evaluate_node_in_context(&args[1], cell) {
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(),
};
}
// return array
match self.evaluate_node_in_context(&args[2], cell) {
CalcResult::Range {
left: result_left,
right: result_right,
} => {
if result_right.row - result_left.row != right.row - left.row
|| result_right.column - result_left.column
!= right.column - left.column
{
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Arrays must be of the same size".to_string(),
};
}
let mut row2 = right.row;
let row1 = left.row;
let mut column2 = right.column;
let column1 = left.column;
if row1 == 1 && row2 == LAST_ROW {
row2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
let left = CellReference {
sheet: left.sheet,
column: column1,
row: row1,
};
let right = CellReference {
sheet: left.sheet,
column: column2,
row: row2,
};
match search_mode {
SearchMode::StartAtFirstItem | SearchMode::StartAtLastItem => {
let array = &self.prepare_array(&left, &right, is_row_vector);
match linear_search(&lookup_value, array, search_mode, match_mode) {
Some(index) => {
let row_index =
if is_row_vector { index as i32 } else { 0 };
let column_index =
if is_row_vector { 0 } else { index as i32 };
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row: result_left.row + row_index,
column: result_left.column + column_index,
})
}
None => if_not_found,
}
}
SearchMode::BinarySearchAscending
| SearchMode::BinarySearchDescending => {
let index = if match_mode == MatchMode::ExactMatchLarger {
if search_mode == SearchMode::BinarySearchAscending {
binary_search_or_greater(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
} else {
binary_search_descending_or_greater(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
}
} else if search_mode == SearchMode::BinarySearchAscending {
binary_search_or_smaller(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
} else {
binary_search_descending_or_smaller(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
};
match index {
None => if_not_found,
Some(l) => {
let row =
result_left.row + if is_row_vector { l } else { 0 };
let column =
result_left.column + if is_row_vector { 0 } else { l };
if match_mode == MatchMode::ExactMatch {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + if is_row_vector { l } else { 0 },
column: left.column
+ if is_row_vector { 0 } else { l },
});
if compare_values(&value, &lookup_value) == 0 {
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row,
column,
})
} else {
if_not_found
}
} else if match_mode == MatchMode::ExactMatchSmaller
|| match_mode == MatchMode::ExactMatchLarger
{
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row,
column,
})
} else {
CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Cannot use wildcard in binary search"
.to_string(),
}
}
}
}
}
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
}