Files
IronCalc/base/src/functions/mod.rs
Nicolás Hatcher e5ec75495a UPDATE: Introducing Arrays
# This PR introduces:

## Parsing arrays:

{1,2,3} and {1;2;3}

Note that array elements can be numbers, booleans and errors (#VALUE!)

## Evaluating arrays in the SUM function

=SUM({1,2,3}) works!

## Evaluating arithmetic operation with arrays

=SUM({1,2,3} * 8) or =SUM({1,2,3}+{2,4,5}) works

This is done with just one function (handle_arithmetic) for most operations

## Some mathematical functions implement arrays

=SUM(SIN({1,2,3})) works

This is done with macros. See fn_single_number
So that implementing new functions that supports array are easy


# Not done in this PR

## Most functions are not supporting arrays

When that happens we either through #N/IMPL! (not implemented error)
or do implicit intersection. Some functions will be rather trivial to "arraify" some will be hard

## The final result in a cell cannot be an array

The formula ={1,2,3} in a cell will result in #N/IMPL!

## Exporting arrays to Excel might not work correctly

Excel uses the cm (cell metadata) for formulas that contain dynamic arrays.
Although the present PR does not introduce dynamic arrays some formulas like =SUM(SIN({1,2,3}))
is considered a dynamic formula

## There are not a lot of tests in this delivery

The bulk of the tests will be added once we start going function by function# This PR introduces:

## Parsing arrays:

{1,2,3} and {1;2;3}

Note that array elements can be numbers, booleans and errors (#VALUE!)

## Evaluating arrays in the SUM function

=SUM({1,2,3}) works!

## Evaluating arithmetic operation with arrays

=SUM({1,2,3} * 8) or =SUM({1,2,3}+{2,4,5}) works

This is done with just one function (handle_arithmetic) for most operations

## Some mathematical functions implement arrays

=SUM(SIN({1,2,3})) works

This is done with macros. See fn_single_number
So that implementing new functions that supports array are easy


# Not done in this PR

## Most functions are not supporting arrays

When that happens we either through #N/IMPL! (not implemented error)
or do implicit intersection. Some functions will be rather trivial to "arraify" some will be hard

## The final result in a cell cannot be an array

The formula ={1,2,3} in a cell will result in #N/IMPL!

## Exporting arrays to Excel might not work correctly

Excel uses the cm (cell metadata) for formulas that contain dynamic arrays.
Although the present PR does not introduce dynamic arrays some formulas like =SUM(SIN({1,2,3}))
is considered a dynamic formula

## There are not a lot of tests in this delivery

The bulk of the tests will be added once we start going function by function

## The array parsing does not respect the locale

Locales that use ',' as a decimal separator need to use something different for arrays

## The might introduce a small performance penalty

We haven't been benchmarking, and having closures for every arithmetic operation and every function
evaluation will introduce a performance hit. Fixing that in he future is not so hard writing tailored
code for the operation
2025-03-17 20:04:47 +01:00

1230 lines
46 KiB
Rust

use core::fmt;
use std::array::IntoIter;
use crate::{
calc_result::CalcResult,
expressions::{parser::Node, token::Error, types::CellReferenceIndex},
model::Model,
};
pub(crate) mod binary_search;
mod date_and_time;
mod engineering;
mod financial;
mod financial_util;
mod information;
mod logical;
mod lookup_and_reference;
mod macros;
mod mathematical;
mod statistical;
mod subtotal;
mod text;
mod text_util;
pub(crate) mod util;
mod xlookup;
/// List of all implemented functions
#[derive(PartialEq, Clone, Debug)]
pub enum Function {
// Logical
And,
False,
If,
Iferror,
Ifna,
Ifs,
Not,
Or,
Switch,
True,
Xor,
// Mathematical and trigonometry
Abs,
Acos,
Acosh,
Asin,
Asinh,
Atan,
Atan2,
Atanh,
Choose,
Column,
Columns,
Cos,
Cosh,
Max,
Min,
Pi,
Power,
Product,
Rand,
Randbetween,
Round,
Rounddown,
Roundup,
Sin,
Sinh,
Sqrt,
Sqrtpi,
Sum,
Sumif,
Sumifs,
Tan,
Tanh,
// Information
ErrorType,
Formulatext,
Isblank,
Iserr,
Iserror,
Iseven,
Isformula,
Islogical,
Isna,
Isnontext,
Isnumber,
Isodd,
Isref,
Istext,
Na,
Sheet,
Type,
// Lookup and reference
Hlookup,
Index,
Indirect,
Lookup,
Match,
Offset,
Row,
Rows,
Vlookup,
Xlookup,
// Text
Concat,
Concatenate,
Exact,
Find,
Left,
Len,
Lower,
Mid,
Rept,
Right,
Search,
Substitute,
T,
Text,
Textafter,
Textbefore,
Textjoin,
Trim,
Unicode,
Upper,
Value,
Valuetotext,
// Statistical
Average,
Averagea,
Averageif,
Averageifs,
Count,
Counta,
Countblank,
Countif,
Countifs,
Maxifs,
Minifs,
Geomean,
// Date and time
Date,
Day,
Edate,
Eomonth,
Month,
Now,
Today,
Year,
// Financial
Cumipmt,
Cumprinc,
Db,
Ddb,
Dollarde,
Dollarfr,
Effect,
Fv,
Ipmt,
Irr,
Ispmt,
Mirr,
Nominal,
Nper,
Npv,
Pduration,
Pmt,
Ppmt,
Pv,
Rate,
Rri,
Sln,
Syd,
Tbilleq,
Tbillprice,
Tbillyield,
Xirr,
Xnpv,
// Engineering: Bessel and transcendental functions
Besseli,
Besselj,
Besselk,
Bessely,
Erf,
Erfc,
ErfcPrecise,
ErfPrecise,
// Engineering: Number systems
Bin2dec,
Bin2hex,
Bin2oct,
Dec2Bin,
Dec2hex,
Dec2oct,
Hex2bin,
Hex2dec,
Hex2oct,
Oct2bin,
Oct2dec,
Oct2hex,
// Engineering: Bit functions
Bitand,
Bitlshift,
Bitor,
Bitrshift,
Bitxor,
// Engineering: Complex functions
Complex,
Imabs,
Imaginary,
Imargument,
Imconjugate,
Imcos,
Imcosh,
Imcot,
Imcsc,
Imcsch,
Imdiv,
Imexp,
Imln,
Imlog10,
Imlog2,
Impower,
Improduct,
Imreal,
Imsec,
Imsech,
Imsin,
Imsinh,
Imsqrt,
Imsub,
Imsum,
Imtan,
// Engineering: Misc function
Convert,
Delta,
Gestep,
Subtotal,
}
impl Function {
pub fn into_iter() -> IntoIter<Function, 195> {
[
Function::And,
Function::False,
Function::If,
Function::Iferror,
Function::Ifna,
Function::Ifs,
Function::Not,
Function::Or,
Function::Switch,
Function::True,
Function::Xor,
Function::Sin,
Function::Cos,
Function::Tan,
Function::Asin,
Function::Acos,
Function::Atan,
Function::Sinh,
Function::Cosh,
Function::Tanh,
Function::Asinh,
Function::Acosh,
Function::Atanh,
Function::Abs,
Function::Pi,
Function::Sqrt,
Function::Sqrtpi,
Function::Atan2,
Function::Power,
Function::Max,
Function::Min,
Function::Product,
Function::Rand,
Function::Randbetween,
Function::Round,
Function::Rounddown,
Function::Roundup,
Function::Sum,
Function::Sumif,
Function::Sumifs,
Function::Choose,
Function::Column,
Function::Columns,
Function::Index,
Function::Indirect,
Function::Hlookup,
Function::Lookup,
Function::Match,
Function::Offset,
Function::Row,
Function::Rows,
Function::Vlookup,
Function::Xlookup,
Function::Concatenate,
Function::Exact,
Function::Value,
Function::T,
Function::Valuetotext,
Function::Concat,
Function::Find,
Function::Left,
Function::Len,
Function::Lower,
Function::Mid,
Function::Right,
Function::Search,
Function::Text,
Function::Trim,
Function::Unicode,
Function::Upper,
Function::Isnumber,
Function::Isnontext,
Function::Istext,
Function::Islogical,
Function::Isblank,
Function::Iserr,
Function::Iserror,
Function::Isna,
Function::Na,
Function::Isref,
Function::Isodd,
Function::Iseven,
Function::ErrorType,
Function::Formulatext,
Function::Isformula,
Function::Type,
Function::Sheet,
Function::Average,
Function::Averagea,
Function::Averageif,
Function::Averageifs,
Function::Count,
Function::Counta,
Function::Countblank,
Function::Countif,
Function::Countifs,
Function::Maxifs,
Function::Minifs,
Function::Geomean,
Function::Year,
Function::Day,
Function::Month,
Function::Eomonth,
Function::Date,
Function::Edate,
Function::Today,
Function::Now,
Function::Pmt,
Function::Pv,
Function::Rate,
Function::Nper,
Function::Fv,
Function::Ppmt,
Function::Ipmt,
Function::Npv,
Function::Mirr,
Function::Irr,
Function::Xirr,
Function::Xnpv,
Function::Rept,
Function::Textafter,
Function::Textbefore,
Function::Textjoin,
Function::Substitute,
Function::Ispmt,
Function::Rri,
Function::Sln,
Function::Syd,
Function::Nominal,
Function::Effect,
Function::Pduration,
Function::Tbillyield,
Function::Tbillprice,
Function::Tbilleq,
Function::Dollarde,
Function::Dollarfr,
Function::Ddb,
Function::Db,
Function::Cumprinc,
Function::Cumipmt,
Function::Besseli,
Function::Besselj,
Function::Besselk,
Function::Bessely,
Function::Erf,
Function::ErfPrecise,
Function::Erfc,
Function::ErfcPrecise,
Function::Bin2dec,
Function::Bin2hex,
Function::Bin2oct,
Function::Dec2Bin,
Function::Dec2hex,
Function::Dec2oct,
Function::Hex2bin,
Function::Hex2dec,
Function::Hex2oct,
Function::Oct2bin,
Function::Oct2dec,
Function::Oct2hex,
Function::Bitand,
Function::Bitlshift,
Function::Bitor,
Function::Bitrshift,
Function::Bitxor,
Function::Complex,
Function::Imabs,
Function::Imaginary,
Function::Imargument,
Function::Imconjugate,
Function::Imcos,
Function::Imcosh,
Function::Imcot,
Function::Imcsc,
Function::Imcsch,
Function::Imdiv,
Function::Imexp,
Function::Imln,
Function::Imlog10,
Function::Imlog2,
Function::Impower,
Function::Improduct,
Function::Imreal,
Function::Imsec,
Function::Imsech,
Function::Imsin,
Function::Imsinh,
Function::Imsqrt,
Function::Imsub,
Function::Imsum,
Function::Imtan,
Function::Convert,
Function::Delta,
Function::Gestep,
Function::Subtotal,
]
.into_iter()
}
}
impl Function {
/// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
pub fn to_xlsx_string(&self) -> String {
match self {
Function::Concat => "_xlfn.CONCAT".to_string(),
Function::Ifna => "_xlfn.IFNA".to_string(),
Function::Ifs => "_xlfn.IFS".to_string(),
Function::Maxifs => "_xlfn.MAXIFS".to_string(),
Function::Minifs => "_xlfn.MINIFS".to_string(),
Function::Switch => "_xlfn.SWITCH".to_string(),
Function::Xlookup => "_xlfn.XLOOKUP".to_string(),
Function::Xor => "_xlfn.XOR".to_string(),
Function::Textbefore => "_xlfn.TEXTBEFORE".to_string(),
Function::Textafter => "_xlfn.TEXTAFTER".to_string(),
Function::Textjoin => "_xlfn.TEXTJOIN".to_string(),
Function::Unicode => "_xlfn.UNICODE".to_string(),
Function::Rri => "_xlfn.RRI".to_string(),
Function::Pduration => "_xlfn.PDURATION".to_string(),
Function::Bitand => "_xlfn.BITAND".to_string(),
Function::Bitor => "_xlfn.BITOR".to_string(),
Function::Bitxor => "_xlfn.BITXOR".to_string(),
Function::Bitlshift => "_xlfn.BITLSHIFT".to_string(),
Function::Bitrshift => "_xlfn.BITRSHIFT".to_string(),
Function::Imtan => "_xlfn.IMTAN".to_string(),
Function::Imsinh => "_xlfn.IMSINH".to_string(),
Function::Imcosh => "_xlfn.IMCOSH".to_string(),
Function::Imcot => "_xlfn.IMCOT".to_string(),
Function::Imcsc => "_xlfn.IMCSC".to_string(),
Function::Imcsch => "_xlfn.IMCSCH".to_string(),
Function::Imsec => "_xlfn.IMSEC".to_string(),
Function::ErfcPrecise => "_xlfn.ERFC.PRECISE".to_string(),
Function::ErfPrecise => "_xlfn.ERF.PRECISE".to_string(),
Function::Valuetotext => "_xlfn.VALUETOTEXT".to_string(),
Function::Isformula => "_xlfn.ISFORMULA".to_string(),
Function::Sheet => "_xlfn.SHEET".to_string(),
Function::Formulatext => "_xlfn.FORMULATEXT".to_string(),
_ => self.to_string(),
}
}
pub(crate) fn returns_reference(&self) -> bool {
matches!(self, Function::Indirect | Function::Offset)
}
/// Gets the function from the name.
/// Note that in Excel some (modern) functions are prefixed by `_xlfn.`
pub fn get_function(name: &str) -> Option<Function> {
match name.to_ascii_uppercase().as_str() {
"AND" => Some(Function::And),
"FALSE" => Some(Function::False),
"IF" => Some(Function::If),
"IFERROR" => Some(Function::Iferror),
"IFNA" | "_XLFN.IFNA" => Some(Function::Ifna),
"IFS" | "_XLFN.IFS" => Some(Function::Ifs),
"NOT" => Some(Function::Not),
"OR" => Some(Function::Or),
"SWITCH" | "_XLFN.SWITCH" => Some(Function::Switch),
"TRUE" => Some(Function::True),
"XOR" | "_XLFN.XOR" => Some(Function::Xor),
"SIN" => Some(Function::Sin),
"COS" => Some(Function::Cos),
"TAN" => Some(Function::Tan),
"ASIN" => Some(Function::Asin),
"ACOS" => Some(Function::Acos),
"ATAN" => Some(Function::Atan),
"SINH" => Some(Function::Sinh),
"COSH" => Some(Function::Cosh),
"TANH" => Some(Function::Tanh),
"ASINH" => Some(Function::Asinh),
"ACOSH" => Some(Function::Acosh),
"ATANH" => Some(Function::Atanh),
"PI" => Some(Function::Pi),
"ABS" => Some(Function::Abs),
"SQRT" => Some(Function::Sqrt),
"SQRTPI" => Some(Function::Sqrtpi),
"POWER" => Some(Function::Power),
"ATAN2" => Some(Function::Atan2),
"MAX" => Some(Function::Max),
"MIN" => Some(Function::Min),
"PRODUCT" => Some(Function::Product),
"RAND" => Some(Function::Rand),
"RANDBETWEEN" => Some(Function::Randbetween),
"ROUND" => Some(Function::Round),
"ROUNDDOWN" => Some(Function::Rounddown),
"ROUNDUP" => Some(Function::Roundup),
"SUM" => Some(Function::Sum),
"SUMIF" => Some(Function::Sumif),
"SUMIFS" => Some(Function::Sumifs),
// Lookup and Reference
"CHOOSE" => Some(Function::Choose),
"COLUMN" => Some(Function::Column),
"COLUMNS" => Some(Function::Columns),
"INDEX" => Some(Function::Index),
"INDIRECT" => Some(Function::Indirect),
"HLOOKUP" => Some(Function::Hlookup),
"LOOKUP" => Some(Function::Lookup),
"MATCH" => Some(Function::Match),
"OFFSET" => Some(Function::Offset),
"ROW" => Some(Function::Row),
"ROWS" => Some(Function::Rows),
"VLOOKUP" => Some(Function::Vlookup),
"XLOOKUP" | "_XLFN.XLOOKUP" => Some(Function::Xlookup),
"CONCATENATE" => Some(Function::Concatenate),
"EXACT" => Some(Function::Exact),
"VALUE" => Some(Function::Value),
"T" => Some(Function::T),
"VALUETOTEXT" | "_XLFN.VALUETOTEXT" => Some(Function::Valuetotext),
"CONCAT" | "_XLFN.CONCAT" => Some(Function::Concat),
"FIND" => Some(Function::Find),
"LEFT" => Some(Function::Left),
"LEN" => Some(Function::Len),
"LOWER" => Some(Function::Lower),
"MID" => Some(Function::Mid),
"RIGHT" => Some(Function::Right),
"SEARCH" => Some(Function::Search),
"TEXT" => Some(Function::Text),
"TRIM" => Some(Function::Trim),
"UNICODE" | "_XLFN.UNICODE" => Some(Function::Unicode),
"UPPER" => Some(Function::Upper),
"REPT" => Some(Function::Rept),
"TEXTAFTER" | "_XLFN.TEXTAFTER" => Some(Function::Textafter),
"TEXTBEFORE" | "_XLFN.TEXTBEFORE" => Some(Function::Textbefore),
"TEXTJOIN" | "_XLFN.TEXTJOIN" => Some(Function::Textjoin),
"SUBSTITUTE" => Some(Function::Substitute),
"ISNUMBER" => Some(Function::Isnumber),
"ISNONTEXT" => Some(Function::Isnontext),
"ISTEXT" => Some(Function::Istext),
"ISLOGICAL" => Some(Function::Islogical),
"ISBLANK" => Some(Function::Isblank),
"ISERR" => Some(Function::Iserr),
"ISERROR" => Some(Function::Iserror),
"ISNA" => Some(Function::Isna),
"NA" => Some(Function::Na),
"ISREF" => Some(Function::Isref),
"ISODD" => Some(Function::Isodd),
"ISEVEN" => Some(Function::Iseven),
"ERROR.TYPE" => Some(Function::ErrorType),
"FORMULATEXT" | "_XLFN.FORMULATEXT" => Some(Function::Formulatext),
"ISFORMULA" | "_XLFN.ISFORMULA" => Some(Function::Isformula),
"TYPE" => Some(Function::Type),
"SHEET" | "_XLFN.SHEET" => Some(Function::Sheet),
"AVERAGE" => Some(Function::Average),
"AVERAGEA" => Some(Function::Averagea),
"AVERAGEIF" => Some(Function::Averageif),
"AVERAGEIFS" => Some(Function::Averageifs),
"COUNT" => Some(Function::Count),
"COUNTA" => Some(Function::Counta),
"COUNTBLANK" => Some(Function::Countblank),
"COUNTIF" => Some(Function::Countif),
"COUNTIFS" => Some(Function::Countifs),
"MAXIFS" | "_XLFN.MAXIFS" => Some(Function::Maxifs),
"MINIFS" | "_XLFN.MINIFS" => Some(Function::Minifs),
"GEOMEAN" => Some(Function::Geomean),
// Date and Time
"YEAR" => Some(Function::Year),
"DAY" => Some(Function::Day),
"EOMONTH" => Some(Function::Eomonth),
"MONTH" => Some(Function::Month),
"DATE" => Some(Function::Date),
"EDATE" => Some(Function::Edate),
"TODAY" => Some(Function::Today),
"NOW" => Some(Function::Now),
// Financial
"PMT" => Some(Function::Pmt),
"PV" => Some(Function::Pv),
"RATE" => Some(Function::Rate),
"NPER" => Some(Function::Nper),
"FV" => Some(Function::Fv),
"PPMT" => Some(Function::Ppmt),
"IPMT" => Some(Function::Ipmt),
"NPV" => Some(Function::Npv),
"XNPV" => Some(Function::Xnpv),
"MIRR" => Some(Function::Mirr),
"IRR" => Some(Function::Irr),
"XIRR" => Some(Function::Xirr),
"ISPMT" => Some(Function::Ispmt),
"RRI" | "_XLFN.RRI" => Some(Function::Rri),
"SLN" => Some(Function::Sln),
"SYD" => Some(Function::Syd),
"NOMINAL" => Some(Function::Nominal),
"EFFECT" => Some(Function::Effect),
"PDURATION" | "_XLFN.PDURATION" => Some(Function::Pduration),
"TBILLYIELD" => Some(Function::Tbillyield),
"TBILLPRICE" => Some(Function::Tbillprice),
"TBILLEQ" => Some(Function::Tbilleq),
"DOLLARDE" => Some(Function::Dollarde),
"DOLLARFR" => Some(Function::Dollarfr),
"DDB" => Some(Function::Ddb),
"DB" => Some(Function::Db),
"CUMPRINC" => Some(Function::Cumprinc),
"CUMIPMT" => Some(Function::Cumipmt),
"BESSELI" => Some(Function::Besseli),
"BESSELJ" => Some(Function::Besselj),
"BESSELK" => Some(Function::Besselk),
"BESSELY" => Some(Function::Bessely),
"ERF" => Some(Function::Erf),
"ERF.PRECISE" | "_XLFN.ERF.PRECISE" => Some(Function::ErfPrecise),
"ERFC" => Some(Function::Erfc),
"ERFC.PRECISE" | "_XLFN.ERFC.PRECISE" => Some(Function::ErfcPrecise),
"BIN2DEC" => Some(Function::Bin2dec),
"BIN2HEX" => Some(Function::Bin2hex),
"BIN2OCT" => Some(Function::Bin2oct),
"DEC2BIN" => Some(Function::Dec2Bin),
"DEC2HEX" => Some(Function::Dec2hex),
"DEC2OCT" => Some(Function::Dec2oct),
"HEX2BIN" => Some(Function::Hex2bin),
"HEX2DEC" => Some(Function::Hex2dec),
"HEX2OCT" => Some(Function::Hex2oct),
"OCT2BIN" => Some(Function::Oct2bin),
"OCT2DEC" => Some(Function::Oct2dec),
"OCT2HEX" => Some(Function::Oct2hex),
"BITAND" | "_XLFN.BITAND" => Some(Function::Bitand),
"BITLSHIFT" | "_XLFN.BITLSHIFT" => Some(Function::Bitlshift),
"BITOR" | "_XLFN.BITOR" => Some(Function::Bitor),
"BITRSHIFT" | "_XLFN.BITRSHIFT" => Some(Function::Bitrshift),
"BITXOR" | "_XLFN.BITXOR" => Some(Function::Bitxor),
"COMPLEX" => Some(Function::Complex),
"IMABS" => Some(Function::Imabs),
"IMAGINARY" => Some(Function::Imaginary),
"IMARGUMENT" => Some(Function::Imargument),
"IMCONJUGATE" => Some(Function::Imconjugate),
"IMCOS" => Some(Function::Imcos),
"IMCOSH" | "_XLFN.IMCOSH" => Some(Function::Imcosh),
"IMCOT" | "_XLFN.IMCOT" => Some(Function::Imcot),
"IMCSC" | "_XLFN.IMCSC" => Some(Function::Imcsc),
"IMCSCH" | "_XLFN.IMCSCH" => Some(Function::Imcsch),
"IMDIV" => Some(Function::Imdiv),
"IMEXP" => Some(Function::Imexp),
"IMLN" => Some(Function::Imln),
"IMLOG10" => Some(Function::Imlog10),
"IMLOG2" => Some(Function::Imlog2),
"IMPOWER" => Some(Function::Impower),
"IMPRODUCT" => Some(Function::Improduct),
"IMREAL" => Some(Function::Imreal),
"IMSEC" | "_XLFN.IMSEC" => Some(Function::Imsec),
"IMSECH" | "_XLFN.IMSECH" => Some(Function::Imsech),
"IMSIN" => Some(Function::Imsin),
"IMSINH" | "_XLFN.IMSINH" => Some(Function::Imsinh),
"IMSQRT" => Some(Function::Imsqrt),
"IMSUB" => Some(Function::Imsub),
"IMSUM" => Some(Function::Imsum),
"IMTAN" | "_XLFN.IMTAN" => Some(Function::Imtan),
"CONVERT" => Some(Function::Convert),
"DELTA" => Some(Function::Delta),
"GESTEP" => Some(Function::Gestep),
"SUBTOTAL" => Some(Function::Subtotal),
_ => None,
}
}
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Function::And => write!(f, "AND"),
Function::False => write!(f, "FALSE"),
Function::If => write!(f, "IF"),
Function::Iferror => write!(f, "IFERROR"),
Function::Ifna => write!(f, "IFNA"),
Function::Ifs => write!(f, "IFS"),
Function::Not => write!(f, "NOT"),
Function::Or => write!(f, "OR"),
Function::Switch => write!(f, "SWITCH"),
Function::True => write!(f, "TRUE"),
Function::Xor => write!(f, "XOR"),
Function::Sin => write!(f, "SIN"),
Function::Cos => write!(f, "COS"),
Function::Tan => write!(f, "TAN"),
Function::Asin => write!(f, "ASIN"),
Function::Acos => write!(f, "ACOS"),
Function::Atan => write!(f, "ATAN"),
Function::Sinh => write!(f, "SINH"),
Function::Cosh => write!(f, "COSH"),
Function::Tanh => write!(f, "TANH"),
Function::Asinh => write!(f, "ASINH"),
Function::Acosh => write!(f, "ACOSH"),
Function::Atanh => write!(f, "ATANH"),
Function::Abs => write!(f, "ABS"),
Function::Pi => write!(f, "PI"),
Function::Sqrt => write!(f, "SQRT"),
Function::Sqrtpi => write!(f, "SQRTPI"),
Function::Atan2 => write!(f, "ATAN2"),
Function::Power => write!(f, "POWER"),
Function::Max => write!(f, "MAX"),
Function::Min => write!(f, "MIN"),
Function::Product => write!(f, "PRODUCT"),
Function::Rand => write!(f, "RAND"),
Function::Randbetween => write!(f, "RANDBETWEEN"),
Function::Round => write!(f, "ROUND"),
Function::Rounddown => write!(f, "ROUNDDOWN"),
Function::Roundup => write!(f, "ROUNDUP"),
Function::Sum => write!(f, "SUM"),
Function::Sumif => write!(f, "SUMIF"),
Function::Sumifs => write!(f, "SUMIFS"),
Function::Choose => write!(f, "CHOOSE"),
Function::Column => write!(f, "COLUMN"),
Function::Columns => write!(f, "COLUMNS"),
Function::Index => write!(f, "INDEX"),
Function::Indirect => write!(f, "INDIRECT"),
Function::Hlookup => write!(f, "HLOOKUP"),
Function::Lookup => write!(f, "LOOKUP"),
Function::Match => write!(f, "MATCH"),
Function::Offset => write!(f, "OFFSET"),
Function::Row => write!(f, "ROW"),
Function::Rows => write!(f, "ROWS"),
Function::Vlookup => write!(f, "VLOOKUP"),
Function::Xlookup => write!(f, "XLOOKUP"),
Function::Concatenate => write!(f, "CONCATENATE"),
Function::Exact => write!(f, "EXACT"),
Function::Value => write!(f, "VALUE"),
Function::T => write!(f, "T"),
Function::Valuetotext => write!(f, "VALUETOTEXT"),
Function::Concat => write!(f, "CONCAT"),
Function::Find => write!(f, "FIND"),
Function::Left => write!(f, "LEFT"),
Function::Len => write!(f, "LEN"),
Function::Lower => write!(f, "LOWER"),
Function::Mid => write!(f, "MID"),
Function::Right => write!(f, "RIGHT"),
Function::Search => write!(f, "SEARCH"),
Function::Text => write!(f, "TEXT"),
Function::Trim => write!(f, "TRIM"),
Function::Unicode => write!(f, "UNICODE"),
Function::Upper => write!(f, "UPPER"),
Function::Isnumber => write!(f, "ISNUMBER"),
Function::Isnontext => write!(f, "ISNONTEXT"),
Function::Istext => write!(f, "ISTEXT"),
Function::Islogical => write!(f, "ISLOGICAL"),
Function::Isblank => write!(f, "ISBLANK"),
Function::Iserr => write!(f, "ISERR"),
Function::Iserror => write!(f, "ISERROR"),
Function::Isna => write!(f, "ISNA"),
Function::Na => write!(f, "NA"),
Function::Isref => write!(f, "ISREF"),
Function::Isodd => write!(f, "ISODD"),
Function::Iseven => write!(f, "ISEVEN"),
Function::ErrorType => write!(f, "ERROR.TYPE"),
Function::Formulatext => write!(f, "FORMULATEXT"),
Function::Isformula => write!(f, "ISFORMULA"),
Function::Type => write!(f, "TYPE"),
Function::Sheet => write!(f, "SHEET"),
Function::Average => write!(f, "AVERAGE"),
Function::Averagea => write!(f, "AVERAGEA"),
Function::Averageif => write!(f, "AVERAGEIF"),
Function::Averageifs => write!(f, "AVERAGEIFS"),
Function::Count => write!(f, "COUNT"),
Function::Counta => write!(f, "COUNTA"),
Function::Countblank => write!(f, "COUNTBLANK"),
Function::Countif => write!(f, "COUNTIF"),
Function::Countifs => write!(f, "COUNTIFS"),
Function::Maxifs => write!(f, "MAXIFS"),
Function::Minifs => write!(f, "MINIFS"),
Function::Geomean => write!(f, "GEOMEAN"),
Function::Year => write!(f, "YEAR"),
Function::Day => write!(f, "DAY"),
Function::Month => write!(f, "MONTH"),
Function::Eomonth => write!(f, "EOMONTH"),
Function::Date => write!(f, "DATE"),
Function::Edate => write!(f, "EDATE"),
Function::Today => write!(f, "TODAY"),
Function::Now => write!(f, "NOW"),
Function::Pmt => write!(f, "PMT"),
Function::Pv => write!(f, "PV"),
Function::Rate => write!(f, "RATE"),
Function::Nper => write!(f, "NPER"),
Function::Fv => write!(f, "FV"),
Function::Ppmt => write!(f, "PPMT"),
Function::Ipmt => write!(f, "IPMT"),
Function::Npv => write!(f, "NPV"),
Function::Mirr => write!(f, "MIRR"),
Function::Irr => write!(f, "IRR"),
Function::Xirr => write!(f, "XIRR"),
Function::Xnpv => write!(f, "XNPV"),
Function::Rept => write!(f, "REPT"),
Function::Textafter => write!(f, "TEXTAFTER"),
Function::Textbefore => write!(f, "TEXTBEFORE"),
Function::Textjoin => write!(f, "TEXTJOIN"),
Function::Substitute => write!(f, "SUBSTITUTE"),
Function::Ispmt => write!(f, "ISPMT"),
Function::Rri => write!(f, "RRI"),
Function::Sln => write!(f, "SLN"),
Function::Syd => write!(f, "SYD"),
Function::Nominal => write!(f, "NOMINAL"),
Function::Effect => write!(f, "EFFECT"),
Function::Pduration => write!(f, "PDURATION"),
Function::Tbillyield => write!(f, "TBILLYIELD"),
Function::Tbillprice => write!(f, "TBILLPRICE"),
Function::Tbilleq => write!(f, "TBILLEQ"),
Function::Dollarde => write!(f, "DOLLARDE"),
Function::Dollarfr => write!(f, "DOLLARFR"),
Function::Ddb => write!(f, "DDB"),
Function::Db => write!(f, "DB"),
Function::Cumprinc => write!(f, "CUMPRINC"),
Function::Cumipmt => write!(f, "CUMIPMT"),
Function::Besseli => write!(f, "BESSELI"),
Function::Besselj => write!(f, "BESSELJ"),
Function::Besselk => write!(f, "BESSELK"),
Function::Bessely => write!(f, "BESSELY"),
Function::Erf => write!(f, "ERF"),
Function::ErfPrecise => write!(f, "ERF.PRECISE"),
Function::Erfc => write!(f, "ERFC"),
Function::ErfcPrecise => write!(f, "ERFC.PRECISE"),
Function::Bin2dec => write!(f, "BIN2DEC"),
Function::Bin2hex => write!(f, "BIN2HEX"),
Function::Bin2oct => write!(f, "BIN2OCT"),
Function::Dec2Bin => write!(f, "DEC2BIN"),
Function::Dec2hex => write!(f, "DEC2HEX"),
Function::Dec2oct => write!(f, "DEC2OCT"),
Function::Hex2bin => write!(f, "HEX2BIN"),
Function::Hex2dec => write!(f, "HEX2DEC"),
Function::Hex2oct => write!(f, "HEX2OCT"),
Function::Oct2bin => write!(f, "OCT2BIN"),
Function::Oct2dec => write!(f, "OCT2DEC"),
Function::Oct2hex => write!(f, "OCT2HEX"),
Function::Bitand => write!(f, "BITAND"),
Function::Bitlshift => write!(f, "BITLSHIFT"),
Function::Bitor => write!(f, "BITOR"),
Function::Bitrshift => write!(f, "BITRSHIFT"),
Function::Bitxor => write!(f, "BITXOR"),
Function::Complex => write!(f, "COMPLEX"),
Function::Imabs => write!(f, "IMABS"),
Function::Imaginary => write!(f, "IMAGINARY"),
Function::Imargument => write!(f, "IMARGUMENT"),
Function::Imconjugate => write!(f, "IMCONJUGATE"),
Function::Imcos => write!(f, "IMCOS"),
Function::Imcosh => write!(f, "IMCOSH"),
Function::Imcot => write!(f, "IMCOT"),
Function::Imcsc => write!(f, "IMCSC"),
Function::Imcsch => write!(f, "IMCSCH"),
Function::Imdiv => write!(f, "IMDIV"),
Function::Imexp => write!(f, "IMEXP"),
Function::Imln => write!(f, "IMLN"),
Function::Imlog10 => write!(f, "IMLOG10"),
Function::Imlog2 => write!(f, "IMLOG2"),
Function::Impower => write!(f, "IMPOWER"),
Function::Improduct => write!(f, "IMPRODUCT"),
Function::Imreal => write!(f, "IMREAL"),
Function::Imsec => write!(f, "IMSEC"),
Function::Imsech => write!(f, "IMSECH"),
Function::Imsin => write!(f, "IMSIN"),
Function::Imsinh => write!(f, "IMSINH"),
Function::Imsqrt => write!(f, "IMSQRT"),
Function::Imsub => write!(f, "IMSUB"),
Function::Imsum => write!(f, "IMSUM"),
Function::Imtan => write!(f, "IMTAN"),
Function::Convert => write!(f, "CONVERT"),
Function::Delta => write!(f, "DELTA"),
Function::Gestep => write!(f, "GESTEP"),
Function::Subtotal => write!(f, "SUBTOTAL"),
}
}
}
/// Documentation for one function
pub struct Documentation {
pub name: String,
}
impl Model {
/// Produces documentation for all implemented functions
pub fn documentation() -> Vec<Documentation> {
let mut doc = Vec::new();
for function in Function::into_iter() {
doc.push(Documentation {
name: function.to_string(),
});
}
doc
}
pub(crate) fn evaluate_function(
&mut self,
kind: &Function,
args: &[Node],
cell: CellReferenceIndex,
) -> CalcResult {
match kind {
// Logical
Function::And => self.fn_and(args, cell),
Function::False => self.fn_false(args, cell),
Function::If => self.fn_if(args, cell),
Function::Iferror => self.fn_iferror(args, cell),
Function::Ifna => self.fn_ifna(args, cell),
Function::Ifs => self.fn_ifs(args, cell),
Function::Not => self.fn_not(args, cell),
Function::Or => self.fn_or(args, cell),
Function::Switch => self.fn_switch(args, cell),
Function::True => self.fn_true(args, cell),
Function::Xor => self.fn_xor(args, cell),
// Math and trigonometry
Function::Sin => self.fn_sin(args, cell),
Function::Cos => self.fn_cos(args, cell),
Function::Tan => self.fn_tan(args, cell),
Function::Asin => self.fn_asin(args, cell),
Function::Acos => self.fn_acos(args, cell),
Function::Atan => self.fn_atan(args, cell),
Function::Sinh => self.fn_sinh(args, cell),
Function::Cosh => self.fn_cosh(args, cell),
Function::Tanh => self.fn_tanh(args, cell),
Function::Asinh => self.fn_asinh(args, cell),
Function::Acosh => self.fn_acosh(args, cell),
Function::Atanh => self.fn_atanh(args, cell),
Function::Pi => self.fn_pi(args, cell),
Function::Abs => self.fn_abs(args, cell),
Function::Sqrt => self.fn_sqrt(args, cell),
Function::Sqrtpi => self.fn_sqrtpi(args, cell),
Function::Atan2 => self.fn_atan2(args, cell),
Function::Power => self.fn_power(args, cell),
Function::Max => self.fn_max(args, cell),
Function::Min => self.fn_min(args, cell),
Function::Product => self.fn_product(args, cell),
Function::Rand => self.fn_rand(args, cell),
Function::Randbetween => self.fn_randbetween(args, cell),
Function::Round => self.fn_round(args, cell),
Function::Rounddown => self.fn_rounddown(args, cell),
Function::Roundup => self.fn_roundup(args, cell),
Function::Sum => self.fn_sum(args, cell),
Function::Sumif => self.fn_sumif(args, cell),
Function::Sumifs => self.fn_sumifs(args, cell),
// Lookup and Reference
Function::Choose => self.fn_choose(args, cell),
Function::Column => self.fn_column(args, cell),
Function::Columns => self.fn_columns(args, cell),
Function::Index => self.fn_index(args, cell),
Function::Indirect => self.fn_indirect(args, cell),
Function::Hlookup => self.fn_hlookup(args, cell),
Function::Lookup => self.fn_lookup(args, cell),
Function::Match => self.fn_match(args, cell),
Function::Offset => self.fn_offset(args, cell),
Function::Row => self.fn_row(args, cell),
Function::Rows => self.fn_rows(args, cell),
Function::Vlookup => self.fn_vlookup(args, cell),
Function::Xlookup => self.fn_xlookup(args, cell),
// Text
Function::Concatenate => self.fn_concatenate(args, cell),
Function::Exact => self.fn_exact(args, cell),
Function::Value => self.fn_value(args, cell),
Function::T => self.fn_t(args, cell),
Function::Valuetotext => self.fn_valuetotext(args, cell),
Function::Concat => self.fn_concat(args, cell),
Function::Find => self.fn_find(args, cell),
Function::Left => self.fn_left(args, cell),
Function::Len => self.fn_len(args, cell),
Function::Lower => self.fn_lower(args, cell),
Function::Mid => self.fn_mid(args, cell),
Function::Right => self.fn_right(args, cell),
Function::Search => self.fn_search(args, cell),
Function::Text => self.fn_text(args, cell),
Function::Trim => self.fn_trim(args, cell),
Function::Unicode => self.fn_unicode(args, cell),
Function::Upper => self.fn_upper(args, cell),
// Information
Function::Isnumber => self.fn_isnumber(args, cell),
Function::Isnontext => self.fn_isnontext(args, cell),
Function::Istext => self.fn_istext(args, cell),
Function::Islogical => self.fn_islogical(args, cell),
Function::Isblank => self.fn_isblank(args, cell),
Function::Iserr => self.fn_iserr(args, cell),
Function::Iserror => self.fn_iserror(args, cell),
Function::Isna => self.fn_isna(args, cell),
Function::Na => CalcResult::new_error(Error::NA, cell, "".to_string()),
Function::Isref => self.fn_isref(args, cell),
Function::Isodd => self.fn_isodd(args, cell),
Function::Iseven => self.fn_iseven(args, cell),
Function::ErrorType => self.fn_errortype(args, cell),
Function::Formulatext => self.fn_formulatext(args, cell),
Function::Isformula => self.fn_isformula(args, cell),
Function::Type => self.fn_type(args, cell),
Function::Sheet => self.fn_sheet(args, cell),
// Statistical
Function::Average => self.fn_average(args, cell),
Function::Averagea => self.fn_averagea(args, cell),
Function::Averageif => self.fn_averageif(args, cell),
Function::Averageifs => self.fn_averageifs(args, cell),
Function::Count => self.fn_count(args, cell),
Function::Counta => self.fn_counta(args, cell),
Function::Countblank => self.fn_countblank(args, cell),
Function::Countif => self.fn_countif(args, cell),
Function::Countifs => self.fn_countifs(args, cell),
Function::Maxifs => self.fn_maxifs(args, cell),
Function::Minifs => self.fn_minifs(args, cell),
Function::Geomean => self.fn_geomean(args, cell),
// Date and Time
Function::Year => self.fn_year(args, cell),
Function::Day => self.fn_day(args, cell),
Function::Eomonth => self.fn_eomonth(args, cell),
Function::Month => self.fn_month(args, cell),
Function::Date => self.fn_date(args, cell),
Function::Edate => self.fn_edate(args, cell),
Function::Today => self.fn_today(args, cell),
Function::Now => self.fn_now(args, cell),
// Financial
Function::Pmt => self.fn_pmt(args, cell),
Function::Pv => self.fn_pv(args, cell),
Function::Rate => self.fn_rate(args, cell),
Function::Nper => self.fn_nper(args, cell),
Function::Fv => self.fn_fv(args, cell),
Function::Ppmt => self.fn_ppmt(args, cell),
Function::Ipmt => self.fn_ipmt(args, cell),
Function::Npv => self.fn_npv(args, cell),
Function::Mirr => self.fn_mirr(args, cell),
Function::Irr => self.fn_irr(args, cell),
Function::Xirr => self.fn_xirr(args, cell),
Function::Xnpv => self.fn_xnpv(args, cell),
Function::Rept => self.fn_rept(args, cell),
Function::Textafter => self.fn_textafter(args, cell),
Function::Textbefore => self.fn_textbefore(args, cell),
Function::Textjoin => self.fn_textjoin(args, cell),
Function::Substitute => self.fn_substitute(args, cell),
Function::Ispmt => self.fn_ispmt(args, cell),
Function::Rri => self.fn_rri(args, cell),
Function::Sln => self.fn_sln(args, cell),
Function::Syd => self.fn_syd(args, cell),
Function::Nominal => self.fn_nominal(args, cell),
Function::Effect => self.fn_effect(args, cell),
Function::Pduration => self.fn_pduration(args, cell),
Function::Tbillyield => self.fn_tbillyield(args, cell),
Function::Tbillprice => self.fn_tbillprice(args, cell),
Function::Tbilleq => self.fn_tbilleq(args, cell),
Function::Dollarde => self.fn_dollarde(args, cell),
Function::Dollarfr => self.fn_dollarfr(args, cell),
Function::Ddb => self.fn_ddb(args, cell),
Function::Db => self.fn_db(args, cell),
Function::Cumprinc => self.fn_cumprinc(args, cell),
Function::Cumipmt => self.fn_cumipmt(args, cell),
// Engineering
Function::Besseli => self.fn_besseli(args, cell),
Function::Besselj => self.fn_besselj(args, cell),
Function::Besselk => self.fn_besselk(args, cell),
Function::Bessely => self.fn_bessely(args, cell),
Function::Erf => self.fn_erf(args, cell),
Function::ErfPrecise => self.fn_erfprecise(args, cell),
Function::Erfc => self.fn_erfc(args, cell),
Function::ErfcPrecise => self.fn_erfcprecise(args, cell),
Function::Bin2dec => self.fn_bin2dec(args, cell),
Function::Bin2hex => self.fn_bin2hex(args, cell),
Function::Bin2oct => self.fn_bin2oct(args, cell),
Function::Dec2Bin => self.fn_dec2bin(args, cell),
Function::Dec2hex => self.fn_dec2hex(args, cell),
Function::Dec2oct => self.fn_dec2oct(args, cell),
Function::Hex2bin => self.fn_hex2bin(args, cell),
Function::Hex2dec => self.fn_hex2dec(args, cell),
Function::Hex2oct => self.fn_hex2oct(args, cell),
Function::Oct2bin => self.fn_oct2bin(args, cell),
Function::Oct2dec => self.fn_oct2dec(args, cell),
Function::Oct2hex => self.fn_oct2hex(args, cell),
Function::Bitand => self.fn_bitand(args, cell),
Function::Bitlshift => self.fn_bitlshift(args, cell),
Function::Bitor => self.fn_bitor(args, cell),
Function::Bitrshift => self.fn_bitrshift(args, cell),
Function::Bitxor => self.fn_bitxor(args, cell),
Function::Complex => self.fn_complex(args, cell),
Function::Imabs => self.fn_imabs(args, cell),
Function::Imaginary => self.fn_imaginary(args, cell),
Function::Imargument => self.fn_imargument(args, cell),
Function::Imconjugate => self.fn_imconjugate(args, cell),
Function::Imcos => self.fn_imcos(args, cell),
Function::Imcosh => self.fn_imcosh(args, cell),
Function::Imcot => self.fn_imcot(args, cell),
Function::Imcsc => self.fn_imcsc(args, cell),
Function::Imcsch => self.fn_imcsch(args, cell),
Function::Imdiv => self.fn_imdiv(args, cell),
Function::Imexp => self.fn_imexp(args, cell),
Function::Imln => self.fn_imln(args, cell),
Function::Imlog10 => self.fn_imlog10(args, cell),
Function::Imlog2 => self.fn_imlog2(args, cell),
Function::Impower => self.fn_impower(args, cell),
Function::Improduct => self.fn_improduct(args, cell),
Function::Imreal => self.fn_imreal(args, cell),
Function::Imsec => self.fn_imsec(args, cell),
Function::Imsech => self.fn_imsech(args, cell),
Function::Imsin => self.fn_imsin(args, cell),
Function::Imsinh => self.fn_imsinh(args, cell),
Function::Imsqrt => self.fn_imsqrt(args, cell),
Function::Imsub => self.fn_imsub(args, cell),
Function::Imsum => self.fn_imsum(args, cell),
Function::Imtan => self.fn_imtan(args, cell),
Function::Convert => self.fn_convert(args, cell),
Function::Delta => self.fn_delta(args, cell),
Function::Gestep => self.fn_gestep(args, cell),
Function::Subtotal => self.fn_subtotal(args, cell),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use std::{
fs::File,
io::{BufRead, BufReader},
};
use crate::functions::Function;
#[test]
fn function_iterator() {
// This checks that the number of functions in the enum is the same
// as the number of functions in the Iterator.
// This is tricky. In Rust we cannot loop over all the members of an enum.
// There are alternatives like using an external crate like strum.
// But I am not in the mood for that.
// What we do here is read this file , extract the functions in the enum
// and check they are the same as in the iterator
let file = File::open("src/functions/mod.rs").unwrap();
let reader = BufReader::new(file);
let mut start = false;
let mut list = Vec::new();
for line in reader.lines() {
let text = line.unwrap();
let text = text.trim().trim_end_matches(',');
if text == "pub enum Function {" {
start = true;
continue;
}
if start {
if text == "}" {
break;
}
if text.starts_with("//") {
// skip comments
continue;
}
if text.is_empty() {
// skip empty lines
continue;
}
list.push(text.to_owned());
}
}
// We make a list with their functions names, but we escape ".": ERROR.TYPE => ERRORTYPE
let iter_list = Function::into_iter()
.map(|f| format!("{}", f).replace('.', ""))
.collect::<Vec<_>>();
let len = iter_list.len();
assert_eq!(list.len(), len);
// We still need to check there are no duplicates. This will fail if a function in iter_list
// is included twice and one is missing
for function in list {
assert!(iter_list.contains(&function.to_uppercase()));
}
}
}