288 lines
11 KiB
Rust
288 lines
11 KiB
Rust
use crate::{
|
|
calc_result::CalcResult,
|
|
expressions::{parser::Node, token::Error, types::CellReferenceIndex},
|
|
model::Model,
|
|
};
|
|
|
|
use super::util::compare_values;
|
|
|
|
impl Model {
|
|
pub(crate) fn fn_true(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.is_empty() {
|
|
CalcResult::Boolean(true)
|
|
} else {
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn fn_false(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.is_empty() {
|
|
CalcResult::Boolean(false)
|
|
} else {
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn fn_if(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 2 || args.len() == 3 {
|
|
let cond_result = self.get_boolean(&args[0], cell);
|
|
let cond = match cond_result {
|
|
Ok(f) => f,
|
|
Err(s) => {
|
|
return s;
|
|
}
|
|
};
|
|
if cond {
|
|
return self.evaluate_node_in_context(&args[1], cell);
|
|
} else if args.len() == 3 {
|
|
return self.evaluate_node_in_context(&args[2], cell);
|
|
} else {
|
|
return CalcResult::Boolean(false);
|
|
}
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_iferror(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 2 {
|
|
let value = self.evaluate_node_in_context(&args[0], cell);
|
|
match value {
|
|
CalcResult::Error { .. } => {
|
|
return self.evaluate_node_in_context(&args[1], cell);
|
|
}
|
|
_ => return value,
|
|
}
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_ifna(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 2 {
|
|
let value = self.evaluate_node_in_context(&args[0], cell);
|
|
if let CalcResult::Error { error, .. } = &value {
|
|
if error == &Error::NA {
|
|
return self.evaluate_node_in_context(&args[1], cell);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_not(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if args.len() == 1 {
|
|
match self.get_boolean(&args[0], cell) {
|
|
Ok(f) => return CalcResult::Boolean(!f),
|
|
Err(s) => {
|
|
return s;
|
|
}
|
|
};
|
|
}
|
|
CalcResult::new_args_number_error(cell)
|
|
}
|
|
|
|
pub(crate) fn fn_and(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
self.logical_nary(
|
|
args,
|
|
cell,
|
|
|acc, value| acc.unwrap_or(true) && value,
|
|
Some(false),
|
|
)
|
|
}
|
|
|
|
pub(crate) fn fn_or(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
self.logical_nary(
|
|
args,
|
|
cell,
|
|
|acc, value| acc.unwrap_or(false) || value,
|
|
Some(true),
|
|
)
|
|
}
|
|
|
|
pub(crate) fn fn_xor(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
self.logical_nary(args, cell, |acc, value| acc.unwrap_or(false) ^ value, None)
|
|
}
|
|
|
|
/// Base function for AND, OR, XOR. These are all n-ary functions that perform a boolean operation on a series of
|
|
/// boolean values. These boolean values are sourced from `args`. Note that there is not a 1-1 relationship between
|
|
/// arguments and boolean values evaluated (see how Ranges are handled for example).
|
|
///
|
|
/// Each argument in `args` is evaluated and the resulting value is interpreted as a boolean as follows:
|
|
/// - Boolean: The value is used directly.
|
|
/// - Number: 0 is FALSE, all other values are TRUE.
|
|
/// - Range: Each cell in the range is evaluated as if they were individual arguments with some caveats
|
|
/// - Empty arg: FALSE
|
|
/// - Empty cell & String: Ignored, behaves exactly like the argument wasn't passed in at all
|
|
/// - Error: Propagated
|
|
///
|
|
/// If no arguments are provided, or all arguments are ignored, the function returns a #VALUE! error
|
|
///
|
|
/// **`fold_fn`:** The function that combines the running result with the next value boolean value. The running result
|
|
/// starts as `None`.
|
|
///
|
|
/// **`short_circuit_value`:** If the running result reaches `short_circuit_value`, the function returns early.
|
|
fn logical_nary(
|
|
&mut self,
|
|
args: &[Node],
|
|
cell: CellReferenceIndex,
|
|
fold_fn: fn(Option<bool>, bool) -> bool,
|
|
short_circuit_value: Option<bool>,
|
|
) -> CalcResult {
|
|
if args.is_empty() {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
|
|
let mut result = None;
|
|
for arg in args {
|
|
match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::Boolean(value) => result = Some(fold_fn(result, value)),
|
|
CalcResult::Number(value) => result = Some(fold_fn(result, value != 0.0)),
|
|
CalcResult::Range { left, right } => {
|
|
if left.sheet != right.sheet {
|
|
return CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"Ranges are in different sheets".to_string(),
|
|
);
|
|
}
|
|
for row in left.row..(right.row + 1) {
|
|
for column in left.column..(right.column + 1) {
|
|
match self.evaluate_cell(CellReferenceIndex {
|
|
sheet: left.sheet,
|
|
row,
|
|
column,
|
|
}) {
|
|
CalcResult::Boolean(value) => result = Some(fold_fn(result, value)),
|
|
CalcResult::Number(value) => {
|
|
result = Some(fold_fn(result, value != 0.0))
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::EmptyArg => {} // unreachable
|
|
CalcResult::Range { .. }
|
|
| CalcResult::String { .. }
|
|
| CalcResult::EmptyCell => {}
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
}
|
|
if let (Some(current_result), Some(short_circuit_value)) =
|
|
(result, short_circuit_value)
|
|
{
|
|
if current_result == short_circuit_value {
|
|
return CalcResult::Boolean(current_result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error @ CalcResult::Error { .. } => return error,
|
|
CalcResult::EmptyArg => result = Some(result.unwrap_or(false)),
|
|
// Strings are ignored unless they are "TRUE" or "FALSE" (case insensitive). EXCEPT if the string value
|
|
// comes from a reference, in which case it is always ignored regardless of its value.
|
|
CalcResult::String(..) => {
|
|
if !matches!(arg, Node::ReferenceKind { .. }) {
|
|
if let Ok(f) = self.get_boolean(arg, cell) {
|
|
result = Some(fold_fn(result, f));
|
|
}
|
|
}
|
|
}
|
|
// References to empty cells are ignored. If all args are ignored the result is #VALUE!
|
|
CalcResult::EmptyCell => {}
|
|
CalcResult::Array(_) => {
|
|
return CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Arrays not supported yet".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
if let (Some(current_result), Some(short_circuit_value)) = (result, short_circuit_value)
|
|
{
|
|
if current_result == short_circuit_value {
|
|
return CalcResult::Boolean(current_result);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(result) = result {
|
|
CalcResult::Boolean(result)
|
|
} else {
|
|
CalcResult::new_error(
|
|
Error::VALUE,
|
|
cell,
|
|
"No logical values in argument list".to_string(),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// =SWITCH(expression, case1, value1, [case, value]*, [default])
|
|
pub(crate) fn fn_switch(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let args_count = args.len();
|
|
if args_count < 3 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
// TODO add implicit intersection
|
|
let expr = self.evaluate_node_in_context(&args[0], cell);
|
|
if expr.is_error() {
|
|
return expr;
|
|
}
|
|
|
|
// How many cases we have?
|
|
// 3, 4 args -> 1 case
|
|
let case_count = (args_count - 1) / 2;
|
|
for case_index in 0..case_count {
|
|
let case = self.evaluate_node_in_context(&args[2 * case_index + 1], cell);
|
|
if case.is_error() {
|
|
return case;
|
|
}
|
|
if compare_values(&expr, &case) == 0 {
|
|
return self.evaluate_node_in_context(&args[2 * case_index + 2], cell);
|
|
}
|
|
}
|
|
// None of the cases matched so we return the default
|
|
// If there is an even number of args is the last one otherwise is #N/A
|
|
if args_count.is_multiple_of(2) {
|
|
return self.evaluate_node_in_context(&args[args_count - 1], cell);
|
|
}
|
|
CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "Did not find a match".to_string(),
|
|
}
|
|
}
|
|
|
|
/// =IFS(condition1, value, [condition, value]*)
|
|
pub(crate) fn fn_ifs(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
let args_count = args.len();
|
|
if args_count < 2 {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
if !args_count.is_multiple_of(2) {
|
|
// Missing value for last condition
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
let case_count = args_count / 2;
|
|
for case_index in 0..case_count {
|
|
let value = self.get_boolean(&args[2 * case_index], cell);
|
|
match value {
|
|
Ok(b) => {
|
|
if b {
|
|
return self.evaluate_node_in_context(&args[2 * case_index + 1], cell);
|
|
}
|
|
}
|
|
Err(s) => return s,
|
|
}
|
|
}
|
|
CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "Did not find a match".to_string(),
|
|
}
|
|
}
|
|
}
|