mod date_time;
mod engineering;
mod info_lookup;
mod math_trig;
mod stats;
mod text;
use super::{Context, LetScope, Sheet};
use crate::core::engine::cell::{Dependency, EngineError, EvalError};
use crate::core::engine::result_data::ResultData;
use crate::core::parser::Expr;
#[derive(Clone, Copy)]
pub(super) struct FnCall<'a> {
pub upper_name: &'a str,
pub args: &'a [Expr],
pub evaluated_args: &'a [ResultData],
pub arg_is_direct: &'a [bool],
pub context: Option<&'a Context<'a>>,
pub row: Option<usize>,
pub col: Option<usize>,
pub scope: &'a LetScope<'a>,
}
pub(super) fn res_to_rd(res: Result<f64, String>) -> Result<ResultData, EngineError> {
match res {
Ok(v) => Ok(ResultData::Float(v)),
Err(e) => Ok(ResultData::Error(e)),
}
}
fn post_process(r: Result<ResultData, EngineError>) -> Result<ResultData, EngineError> {
match r {
Ok(ResultData::Float(f)) if !f.is_finite() => Ok(ResultData::Error("#NUM!".to_string())),
other => other,
}
}
impl Sheet {
pub(crate) fn call_worksheet_function(
&self,
name: &str,
args: &[Expr],
context: Option<&Context>,
) -> Result<ResultData, EngineError> {
let mut deps = Vec::new();
self.evaluate_function(name, args, context, None, None, &mut deps, &LetScope::Empty)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn evaluate_function(
&self,
name: &str,
args: &[crate::core::parser::Expr],
context: Option<&Context>,
row: Option<usize>,
col: Option<usize>,
deps: &mut Vec<Dependency>,
scope: &LetScope<'_>,
) -> Result<ResultData, EngineError> {
use crate::core::parser::Expr;
let mut upper_name = name.to_uppercase();
if upper_name.starts_with("_XLFN.") {
upper_name = upper_name["_XLFN.".len()..].to_string();
}
if upper_name.starts_with("_XLWS.") {
upper_name = upper_name["_XLWS.".len()..].to_string();
}
if upper_name == "LET" {
return self.evaluate_let(args, context, row, col, deps, scope);
}
if upper_name == "IF" {
if args.len() < 3 {
return Err(EngineError::EvalError(EvalError::UnknownFunction(
"IF requires 3 arguments".to_string(),
)));
}
let cond_val = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
if let ResultData::Error(_) = cond_val {
return Ok(cond_val);
}
let condition = match self.to_bool_opt(&cond_val) {
Some(b) => b,
None => return Ok(ResultData::Error("#VALUE!".to_string())),
};
if condition {
return self.evaluate_ast(&args[1], context, row, col, deps, scope);
} else {
return self.evaluate_ast(&args[2], context, row, col, deps, scope);
}
}
if upper_name == "IFERROR" {
if args.len() < 2 {
return Err(EngineError::EvalError(EvalError::UnknownFunction(
"IFERROR requires 2 arguments".to_string(),
)));
}
let first_res = self.evaluate_ast(&args[0], context, row, col, deps, scope);
match first_res {
Ok(ResultData::Error(_)) | Err(_) => {
return self.evaluate_ast(&args[1], context, row, col, deps, scope);
}
Ok(val) => return Ok(val),
}
}
if upper_name == "IFNA" {
if args.len() < 2 {
return Err(EngineError::EvalError(EvalError::UnknownFunction(
"IFNA requires 2 arguments".to_string(),
)));
}
let first_val = match self.evaluate_ast(&args[0], context, row, col, deps, scope) {
Ok(v) => v,
Err(EngineError::EvalError(EvalError::UnknownFunction(e)))
if e.starts_with('#') =>
{
ResultData::Error(e)
}
Err(e) => return Err(e),
};
if let ResultData::Error(ref e) = first_val
&& e == "#N/A"
{
return self.evaluate_ast(&args[1], context, row, col, deps, scope);
}
return Ok(first_val);
}
if upper_name == "IFS" {
let mut i = 0;
while i + 1 < args.len() {
let cond = self.evaluate_ast(&args[i], context, row, col, deps, scope)?;
if let ResultData::Error(_) = cond {
return Ok(cond);
}
if self.to_bool(&cond) {
return self.evaluate_ast(&args[i + 1], context, row, col, deps, scope);
}
i += 2;
}
return Ok(ResultData::Error("#N/A".to_string()));
}
if upper_name == "SWITCH" {
if args.len() < 3 {
return Ok(ResultData::Error("#VALUE!".to_string()));
}
let target = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
if let ResultData::Error(_) = target {
return Ok(target);
}
let mut i = 1;
while i + 1 < args.len() {
let case = self.evaluate_ast(&args[i], context, row, col, deps, scope)?;
if let ResultData::Error(_) = case {
return Ok(case);
}
if target.to_string() == case.to_string() {
return self.evaluate_ast(&args[i + 1], context, row, col, deps, scope);
}
i += 2;
}
if i < args.len() {
return self.evaluate_ast(&args[i], context, row, col, deps, scope);
}
return Ok(ResultData::Error("#N/A".to_string()));
}
if upper_name == "CHOOSE" {
if args.len() < 2 {
return Err(EngineError::EvalError(EvalError::UnknownFunction(
"CHOOSE requires at least 2 arguments".to_string(),
)));
}
let idx_val = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
if let ResultData::Error(_) = idx_val {
return Ok(idx_val);
}
let idx = match self.to_f64(&idx_val) {
Some(f) => f.round() as isize,
None => return Ok(ResultData::Error("#VALUE!".to_string())),
};
let choices = &args[1..];
if idx >= 1 && (idx as usize) <= choices.len() {
return self.evaluate_ast(
&choices[(idx - 1) as usize],
context,
row,
col,
deps,
scope,
);
} else {
return Ok(ResultData::Error("#VALUE!".to_string()));
}
}
if upper_name == "LAMBDA" {
return Ok(ResultData::Error("#CALC!".to_string()));
}
if matches!(
upper_name.as_str(),
"MAP" | "BYROW" | "BYCOL" | "REDUCE" | "SCAN" | "MAKEARRAY"
) {
return self.evaluate_lambda_function(
upper_name.as_str(),
args,
context,
row,
col,
deps,
scope,
);
}
if upper_name == "ISOMITTED" {
let is_omitted = match args.first() {
Some(Expr::Identifier(name)) => scope.get(name).is_none(),
_ => false,
};
return Ok(ResultData::Boolean(is_omitted));
}
if matches!(
upper_name.as_str(),
"ROW"
| "ROWS"
| "COLUMN"
| "COLUMNS"
| "AREAS"
| "ISREF"
| "FORMULATEXT"
| "ISFORMULA"
| "INDIRECT"
| "OFFSET"
| "SHEET"
| "SHEETS"
| "CELL"
| "INFO"
) {
return self.evaluate_range_info_function(
upper_name.as_str(),
args,
context,
row,
col,
deps,
scope,
);
}
if matches!(
upper_name.as_str(),
"TRANSPOSE"
| "HSTACK"
| "VSTACK"
| "CHOOSEROWS"
| "CHOOSECOLS"
| "DROP"
| "EXPAND"
| "TAKE"
| "TOCOL"
| "TOROW"
| "WRAPROWS"
| "WRAPCOLS"
| "UNIQUE"
| "SORT"
| "SORTBY"
| "FILTER"
| "TRIMRANGE"
) {
return self.evaluate_array_reshape_function(
upper_name.as_str(),
args,
context,
row,
col,
deps,
scope,
);
}
if upper_name == "GETPIVOTDATA" {
return self.evaluate_getpivotdata(args, context, row, col, deps, scope);
}
if upper_name == "ISERROR" {
if args.is_empty() {
return Ok(ResultData::Boolean(false));
}
let res = self.evaluate_ast(&args[0], context, row, col, deps, scope);
return match res {
Ok(ResultData::Error(_)) | Err(_) => Ok(ResultData::Boolean(true)),
_ => Ok(ResultData::Boolean(false)),
};
}
if upper_name == "ISNA" {
if args.is_empty() {
return Ok(ResultData::Boolean(false));
}
let res = self.evaluate_ast(&args[0], context, row, col, deps, scope);
return match res {
Ok(ResultData::Error(e)) => Ok(ResultData::Boolean(e.contains("#N/A"))),
Err(EngineError::EvalError(EvalError::UnknownFunction(e)))
if e.starts_with('#') =>
{
Ok(ResultData::Boolean(e.contains("#N/A")))
}
_ => Ok(ResultData::Boolean(false)),
};
}
let mut evaluated_args = Vec::new();
let mut arg_is_direct = Vec::new();
for arg in args {
let is_direct_arg = match arg {
Expr::CellRef { .. } | Expr::RangeRef { .. } | Expr::StructuredRef { .. } => false,
Expr::FunctionCall { name, .. } => {
let n = name.to_uppercase();
n != "IF" && n != "IFERROR" && n != "CHOOSE"
}
_ => true,
};
arg_is_direct.push(is_direct_arg);
let eval_res = match self.evaluate_ast(arg, context, row, col, deps, scope) {
Ok(r) => r,
Err(EngineError::EvalError(EvalError::UnknownFunction(err_str)))
if err_str.starts_with('#') =>
{
ResultData::Error(err_str)
}
Err(e) => return Err(e),
};
evaluated_args.push(eval_res);
}
let uses_ordered_arg_error_check = matches!(
upper_name.as_str(),
"SUM" | "AVERAGE" | "MIN" | "MAX" | "PRODUCT"
);
let inspects_errors = matches!(
upper_name.as_str(),
"IFERROR"
| "ISERROR"
| "ISNA"
| "ISERR"
| "ERROR.TYPE"
| "TYPE"
| "ISTEXT"
| "ISNONTEXT"
| "ISNUMBER"
| "ISLOGICAL"
| "ISBLANK"
);
if !inspects_errors
&& upper_name != "COUNTA"
&& upper_name != "COUNT"
&& upper_name != "COUNTBLANK"
&& upper_name != "AGGREGATE"
&& !matches!(
upper_name.as_str(),
"CORREL"
| "PEARSON"
| "COVAR"
| "COVARIANCE.P"
| "COVARIANCE.S"
| "SLOPE"
| "INTERCEPT"
| "RSQ"
| "STEYX"
| "FORECAST"
| "FORECAST.LINEAR"
| "SUMX2MY2"
| "SUMX2PY2"
| "SUMXMY2"
| "CHISQ.TEST"
| "CHITEST"
| "LOG"
| "ATAN2"
| "GCD"
| "LCM"
)
&& !uses_ordered_arg_error_check
&& let Some(err) = Self::find_error_in_args(&evaluated_args)
{
return Ok(err);
}
let call = FnCall {
upper_name: upper_name.as_str(),
args,
evaluated_args: &evaluated_args,
arg_is_direct: &arg_is_direct,
context,
row,
col,
scope,
};
if let Some(r) = self.eval_stats_fn(call, deps) {
return post_process(r);
}
if let Some(r) = self.eval_math_trig_fn(call, deps) {
return post_process(r);
}
if let Some(r) = self.eval_text_fn(call, deps) {
return post_process(r);
}
if let Some(r) = self.eval_date_time_fn(call, deps) {
return post_process(r);
}
if let Some(r) = self.eval_engineering_fn(call, deps) {
return post_process(r);
}
if let Some(r) = self.eval_info_lookup_fn(call, deps) {
return post_process(r);
}
post_process(Err(EngineError::EvalError(EvalError::UnknownFunction(
format!("Unknown function: {}", name),
))))
}
}