Skip to main content

formualizer_eval/builtins/
logical_ext.rs

1use super::utils::ARG_ANY_ONE;
2use crate::args::ArgSchema;
3use crate::function::Function;
4use crate::function_contract::FunctionDependencyContract;
5use crate::traits::{ArgumentHandle, FunctionContext};
6use formualizer_common::{ExcelError, LiteralValue};
7use formualizer_macros::func_caps;
8
9/* Additional logical & error-handling functions: NOT, XOR, IFERROR, IFNA, IFS */
10
11#[derive(Debug)]
12pub struct NotFn;
13/// Reverses the logical value of its argument.
14///
15/// `NOT` converts the input to a logical value, then flips it.
16///
17/// # Remarks
18/// - Numbers are coerced (`0` -> TRUE after inversion, non-zero -> FALSE after inversion).
19/// - Blank values are treated as FALSE, so `NOT(blank)` returns TRUE.
20/// - Text and other non-coercible values return `#VALUE!`.
21/// - Errors are propagated unchanged.
22///
23/// # Examples
24///
25/// ```yaml,sandbox
26/// title: "Invert boolean"
27/// formula: '=NOT(TRUE)'
28/// expected: false
29/// ```
30///
31/// ```yaml,sandbox
32/// title: "Invert numeric truthiness"
33/// formula: '=NOT(0)'
34/// expected: true
35/// ```
36///
37/// ```yaml,docs
38/// related:
39///   - AND
40///   - OR
41///   - XOR
42/// faq:
43///   - q: "How does NOT treat blanks?"
44///     a: "Blank is treated as FALSE first, so NOT(blank) returns TRUE."
45/// ```
46/// [formualizer-docgen:schema:start]
47/// Name: NOT
48/// Type: NotFn
49/// Min args: 1
50/// Max args: 1
51/// Variadic: false
52/// Signature: NOT(arg1: any@scalar)
53/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
54/// Caps: PURE
55/// [formualizer-docgen:schema:end]
56impl Function for NotFn {
57    func_caps!(PURE);
58    fn name(&self) -> &'static str {
59        "NOT"
60    }
61    fn min_args(&self) -> usize {
62        1
63    }
64    fn dependency_contract(&self, arity: usize) -> Option<FunctionDependencyContract> {
65        FunctionDependencyContract::static_scalar_all_args(arity)
66    }
67    fn arg_schema(&self) -> &'static [ArgSchema] {
68        &ARG_ANY_ONE[..]
69    }
70    fn eval<'a, 'b, 'c>(
71        &self,
72        args: &'c [ArgumentHandle<'a, 'b>],
73        _ctx: &dyn FunctionContext<'b>,
74    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
75        if args.len() != 1 {
76            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
77                ExcelError::new_value(),
78            )));
79        }
80        let v = args[0].value()?.into_literal();
81        let b = match v {
82            LiteralValue::Boolean(b) => !b,
83            LiteralValue::Number(n) => n == 0.0,
84            LiteralValue::Int(i) => i == 0,
85            LiteralValue::Empty => true,
86            LiteralValue::Error(e) => {
87                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
88            }
89            _ => {
90                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
91                    ExcelError::new_value(),
92                )));
93            }
94        };
95        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(b)))
96    }
97}
98
99#[derive(Debug)]
100pub struct XorFn;
101/// Returns TRUE when an odd number of arguments evaluate to TRUE.
102///
103/// `XOR` aggregates all values and checks parity of truthy inputs.
104///
105/// # Remarks
106/// - Booleans and numbers are accepted (`0` is FALSE, non-zero is TRUE).
107/// - Blank values are ignored.
108/// - Text and other non-coercible values produce `#VALUE!`.
109/// - If no coercion error occurs first, encountered formula errors are propagated.
110///
111/// # Examples
112///
113/// ```yaml,sandbox
114/// title: "Odd count of TRUE values"
115/// formula: '=XOR(TRUE, FALSE, TRUE, TRUE)'
116/// expected: true
117/// ```
118///
119/// ```yaml,sandbox
120/// title: "Text input triggers VALUE error"
121/// formula: '=XOR(1, "x")'
122/// expected: "#VALUE!"
123/// ```
124///
125/// ```yaml,docs
126/// related:
127///   - AND
128///   - OR
129///   - NOT
130/// faq:
131///   - q: "What determines XOR's final result?"
132///     a: "XOR returns TRUE when the count of truthy inputs is odd; blanks are ignored."
133/// ```
134/// [formualizer-docgen:schema:start]
135/// Name: XOR
136/// Type: XorFn
137/// Min args: 1
138/// Max args: variadic
139/// Variadic: true
140/// Signature: XOR(arg1...: any@scalar)
141/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
142/// Caps: PURE, REDUCTION, BOOL_ONLY
143/// [formualizer-docgen:schema:end]
144impl Function for XorFn {
145    func_caps!(PURE, REDUCTION, BOOL_ONLY);
146    fn name(&self) -> &'static str {
147        "XOR"
148    }
149    fn min_args(&self) -> usize {
150        1
151    }
152    fn variadic(&self) -> bool {
153        true
154    }
155    fn arg_schema(&self) -> &'static [ArgSchema] {
156        &ARG_ANY_ONE[..]
157    }
158    fn eval<'a, 'b, 'c>(
159        &self,
160        args: &'c [ArgumentHandle<'a, 'b>],
161        _ctx: &dyn FunctionContext<'b>,
162    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
163        let mut true_count = 0usize;
164        let mut first_error: Option<LiteralValue> = None;
165        for a in args {
166            if let Ok(view) = a.range_view() {
167                let mut err: Option<LiteralValue> = None;
168                view.for_each_cell(&mut |val| {
169                    match val {
170                        LiteralValue::Boolean(b) => {
171                            if *b {
172                                true_count += 1;
173                            }
174                        }
175                        LiteralValue::Number(n) => {
176                            if *n != 0.0 {
177                                true_count += 1;
178                            }
179                        }
180                        LiteralValue::Int(i) => {
181                            if *i != 0 {
182                                true_count += 1;
183                            }
184                        }
185                        LiteralValue::Empty => {}
186                        LiteralValue::Error(_) => {
187                            if first_error.is_none() {
188                                err = Some(val.clone());
189                            }
190                        }
191                        _ => {
192                            if first_error.is_none() {
193                                err = Some(LiteralValue::Error(ExcelError::from_error_string(
194                                    "#VALUE!",
195                                )));
196                            }
197                        }
198                    }
199                    Ok(())
200                })?;
201                if first_error.is_none() {
202                    first_error = err;
203                }
204            } else {
205                let v = a.value()?.into_literal();
206                match v {
207                    LiteralValue::Boolean(b) => {
208                        if b {
209                            true_count += 1;
210                        }
211                    }
212                    LiteralValue::Number(n) => {
213                        if n != 0.0 {
214                            true_count += 1;
215                        }
216                    }
217                    LiteralValue::Int(i) => {
218                        if i != 0 {
219                            true_count += 1;
220                        }
221                    }
222                    LiteralValue::Empty => {}
223                    LiteralValue::Error(e) => {
224                        if first_error.is_none() {
225                            first_error = Some(LiteralValue::Error(e));
226                        }
227                    }
228                    _ => {
229                        if first_error.is_none() {
230                            first_error = Some(LiteralValue::Error(ExcelError::from_error_string(
231                                "#VALUE!",
232                            )));
233                        }
234                    }
235                }
236            }
237        }
238        if let Some(err) = first_error {
239            return Ok(crate::traits::CalcValue::Scalar(err));
240        }
241        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
242            true_count % 2 == 1,
243        )))
244    }
245}
246
247#[derive(Debug)]
248pub struct IfErrorFn; // IFERROR(value, fallback)
249/// Returns a fallback when the first expression evaluates to any error.
250///
251/// `IFERROR(value, value_if_error)` is useful for user-friendly error handling.
252///
253/// # Remarks
254/// - Any error kind in the first argument triggers the fallback branch.
255/// - Non-error results pass through unchanged.
256/// - Evaluation failures surfaced as interpreter errors are also caught.
257/// - Exactly two arguments are required; other arities return `#VALUE!`.
258///
259/// # Examples
260///
261/// ```yaml,sandbox
262/// title: "Replace division error"
263/// formula: '=IFERROR(1/0, "n/a")'
264/// expected: "n/a"
265/// ```
266///
267/// ```yaml,sandbox
268/// title: "Pass through non-error"
269/// formula: '=IFERROR(42, 0)'
270/// expected: 42
271/// ```
272///
273/// ```yaml,docs
274/// related:
275///   - IFNA
276///   - IF
277///   - ISERROR
278/// faq:
279///   - q: "Does IFERROR catch all error types?"
280///     a: "Yes. Any error kind in the first argument triggers the fallback value."
281/// ```
282/// [formualizer-docgen:schema:start]
283/// Name: IFERROR
284/// Type: IfErrorFn
285/// Min args: 2
286/// Max args: 2
287/// Variadic: false
288/// Signature: IFERROR(arg1: any@scalar, arg2: any@scalar)
289/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
290/// Caps: PURE
291/// [formualizer-docgen:schema:end]
292impl Function for IfErrorFn {
293    func_caps!(PURE);
294    fn name(&self) -> &'static str {
295        "IFERROR"
296    }
297    fn min_args(&self) -> usize {
298        2
299    }
300    fn variadic(&self) -> bool {
301        false
302    }
303    fn arg_schema(&self) -> &'static [ArgSchema] {
304        use std::sync::LazyLock;
305        // value, fallback (any scalar)
306        static TWO: LazyLock<Vec<ArgSchema>> =
307            LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
308        &TWO[..]
309    }
310    fn eval<'a, 'b, 'c>(
311        &self,
312        args: &'c [ArgumentHandle<'a, 'b>],
313        _ctx: &dyn FunctionContext<'b>,
314    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
315        if args.len() != 2 {
316            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
317                ExcelError::new_value(),
318            )));
319        }
320        match args[0].value() {
321            Ok(cv) => match cv.into_literal() {
322                LiteralValue::Error(_) => args[1].value(),
323                other => Ok(crate::traits::CalcValue::Scalar(other)),
324            },
325            Err(_) => args[1].value(),
326        }
327    }
328}
329
330#[derive(Debug)]
331pub struct IfNaFn; // IFNA(value, fallback)
332/// Returns a fallback only when the first expression is `#N/A`.
333///
334/// `IFNA(value, value_if_na)` is narrower than `IFERROR`.
335///
336/// # Remarks
337/// - Only `#N/A` triggers fallback.
338/// - Other error kinds are returned unchanged.
339/// - Non-error results pass through unchanged.
340/// - Exactly two arguments are required; other arities return `#VALUE!`.
341///
342/// # Examples
343///
344/// ```yaml,sandbox
345/// title: "Catch N/A"
346/// formula: '=IFNA(NA(), "missing")'
347/// expected: "missing"
348/// ```
349///
350/// ```yaml,sandbox
351/// title: "Do not catch other errors"
352/// formula: '=IFNA(1/0, "missing")'
353/// expected: "#DIV/0!"
354/// ```
355///
356/// ```yaml,docs
357/// related:
358///   - IFERROR
359///   - ISNA
360///   - NA
361/// faq:
362///   - q: "Which errors does IFNA intercept?"
363///     a: "Only #N/A is intercepted; all other errors pass through unchanged."
364/// ```
365/// [formualizer-docgen:schema:start]
366/// Name: IFNA
367/// Type: IfNaFn
368/// Min args: 2
369/// Max args: 2
370/// Variadic: false
371/// Signature: IFNA(arg1: any@scalar, arg2: any@scalar)
372/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
373/// Caps: PURE
374/// [formualizer-docgen:schema:end]
375impl Function for IfNaFn {
376    func_caps!(PURE);
377    fn name(&self) -> &'static str {
378        "IFNA"
379    }
380    fn min_args(&self) -> usize {
381        2
382    }
383    fn variadic(&self) -> bool {
384        false
385    }
386    fn arg_schema(&self) -> &'static [ArgSchema] {
387        use std::sync::LazyLock;
388        static TWO: LazyLock<Vec<ArgSchema>> =
389            LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
390        &TWO[..]
391    }
392    fn eval<'a, 'b, 'c>(
393        &self,
394        args: &'c [ArgumentHandle<'a, 'b>],
395        _ctx: &dyn FunctionContext<'b>,
396    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
397        if args.len() != 2 {
398            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
399                ExcelError::new_value(),
400            )));
401        }
402        let v = args[0].value()?.into_literal();
403        match v {
404            LiteralValue::Error(ref e) if e.kind == formualizer_common::ExcelErrorKind::Na => {
405                args[1].value()
406            }
407            other => Ok(crate::traits::CalcValue::Scalar(other)),
408        }
409    }
410}
411
412#[derive(Debug)]
413pub struct IfsFn; // IFS(cond1, val1, cond2, val2, ...)
414/// Returns the value for the first TRUE condition in condition-value pairs.
415///
416/// `IFS(cond1, value1, cond2, value2, ...)` evaluates left to right and short-circuits.
417///
418/// # Remarks
419/// - Arguments must be provided as pairs; odd argument counts return `#VALUE!`.
420/// - Conditions accept booleans and numbers (`0` FALSE, non-zero TRUE); blank is FALSE.
421/// - Text conditions return `#VALUE!`; error conditions propagate.
422/// - If no condition is TRUE, returns `#N/A`.
423///
424/// # Examples
425///
426/// ```yaml,sandbox
427/// title: "First matching condition wins"
428/// formula: '=IFS(2<1, "a", 3>2, "b", TRUE, "c")'
429/// expected: "b"
430/// ```
431///
432/// ```yaml,sandbox
433/// title: "No conditions matched"
434/// formula: '=IFS(FALSE, 1, 0, 2)'
435/// expected: "#N/A"
436/// ```
437///
438/// ```yaml,docs
439/// related:
440///   - IF
441///   - AND
442///   - OR
443/// faq:
444///   - q: "What errors can IFS return on structure issues?"
445///     a: "Odd argument counts return #VALUE!, and no TRUE condition returns #N/A."
446/// ```
447/// [formualizer-docgen:schema:start]
448/// Name: IFS
449/// Type: IfsFn
450/// Min args: 2
451/// Max args: variadic
452/// Variadic: true
453/// Signature: IFS(arg1...: any@scalar)
454/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
455/// Caps: PURE, SHORT_CIRCUIT
456/// [formualizer-docgen:schema:end]
457impl Function for IfsFn {
458    func_caps!(PURE, SHORT_CIRCUIT);
459    fn name(&self) -> &'static str {
460        "IFS"
461    }
462    fn min_args(&self) -> usize {
463        2
464    }
465    fn variadic(&self) -> bool {
466        true
467    }
468    fn arg_schema(&self) -> &'static [ArgSchema] {
469        &ARG_ANY_ONE[..]
470    }
471    fn eval<'a, 'b, 'c>(
472        &self,
473        args: &'c [ArgumentHandle<'a, 'b>],
474        _ctx: &dyn FunctionContext<'b>,
475    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
476        if args.len() < 2 || !args.len().is_multiple_of(2) {
477            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
478                ExcelError::new_value(),
479            )));
480        }
481        for pair in args.chunks(2) {
482            let cond = pair[0].value()?.into_literal();
483            let is_true = match cond {
484                LiteralValue::Boolean(b) => b,
485                LiteralValue::Number(n) => n != 0.0,
486                LiteralValue::Int(i) => i != 0,
487                LiteralValue::Empty => false,
488                LiteralValue::Error(e) => {
489                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
490                }
491                _ => {
492                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
493                        ExcelError::from_error_string("#VALUE!"),
494                    )));
495                }
496            };
497            if is_true {
498                return pair[1].value();
499            }
500        }
501        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
502            ExcelError::new_na(),
503        )))
504    }
505}
506
507/// Returns the result corresponding to the first matching candidate value.
508///
509/// `SWITCH(expression, value1, result1, [value2, result2], ..., [default])`
510/// compares `expression` against each candidate from left to right.
511///
512/// # Remarks
513/// - Matching is case-insensitive for text values.
514/// - Numeric comparisons treat `Int` and `Number` values as compatible.
515/// - A trailing unmatched argument acts as the default result.
516/// - When no candidate matches and no default is supplied, returns `#N/A`.
517/// - Errors in `expression` propagate immediately.
518///
519/// # Examples
520///
521/// ```excel
522/// =SWITCH("gold","silver",1,"gold",2,0)
523/// ```
524///
525/// ```yaml,sandbox
526/// title: "Match a text label"
527/// formula: '=SWITCH("gold","silver",1,"gold",2,0)'
528/// expected: 2
529/// ```
530///
531/// ```yaml,sandbox
532/// title: "Fall back to default"
533/// formula: '=SWITCH(3,1,"one",2,"two","other")'
534/// expected: "other"
535/// ```
536///
537/// ```yaml,docs
538/// related:
539///   - IF
540///   - IFS
541///   - CHOOSE
542/// faq:
543///   - q: "Does SWITCH compare text case-sensitively?"
544///     a: "No. Text comparisons are case-insensitive in this implementation."
545///   - q: "What happens when nothing matches?"
546///     a: "SWITCH returns the trailing default when provided; otherwise it returns #N/A."
547/// ```
548/// [formualizer-docgen:schema:start]
549/// Name: SWITCH
550/// Type: SwitchFn
551/// Min args: 3
552/// Max args: variadic
553/// Variadic: true
554/// Signature: SWITCH(arg1: any@scalar, arg2...: any@scalar)
555/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
556/// Caps: PURE, SHORT_CIRCUIT
557/// [formualizer-docgen:schema:end]
558#[derive(Debug)]
559pub struct SwitchFn;
560/// [formualizer-docgen:schema:start]
561/// Name: SWITCH
562/// Type: SwitchFn
563/// Min args: 3
564/// Max args: variadic
565/// Variadic: true
566/// Signature: SWITCH(arg1...: any@scalar)
567/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
568/// Caps: PURE, SHORT_CIRCUIT
569/// [formualizer-docgen:schema:end]
570impl Function for SwitchFn {
571    func_caps!(PURE, SHORT_CIRCUIT);
572    fn name(&self) -> &'static str {
573        "SWITCH"
574    }
575    fn min_args(&self) -> usize {
576        3
577    }
578    fn variadic(&self) -> bool {
579        true
580    }
581    fn arg_schema(&self) -> &'static [ArgSchema] {
582        &ARG_ANY_ONE[..]
583    }
584    fn eval<'a, 'b, 'c>(
585        &self,
586        args: &'c [ArgumentHandle<'a, 'b>],
587        _ctx: &dyn FunctionContext<'b>,
588    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
589        if args.len() < 3 {
590            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
591                ExcelError::new_value(),
592            )));
593        }
594        let expr = args[0].value()?.into_literal();
595        if let LiteralValue::Error(e) = &expr {
596            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
597                e.clone(),
598            )));
599        }
600        // args[1..] are value/result pairs with optional trailing default
601        let rest = &args[1..];
602        let has_default = rest.len() % 2 == 1;
603        let pairs = if has_default {
604            rest.len() - 1
605        } else {
606            rest.len()
607        };
608        for chunk in rest[..pairs].chunks(2) {
609            let candidate = chunk[0].value()?.into_literal();
610            if switch_values_equal(&expr, &candidate) {
611                return chunk[1].value();
612            }
613        }
614        if has_default {
615            return rest.last().unwrap().value();
616        }
617        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
618            ExcelError::new_na(),
619        )))
620    }
621}
622
623fn switch_values_equal(a: &LiteralValue, b: &LiteralValue) -> bool {
624    match (a, b) {
625        (LiteralValue::Number(x), LiteralValue::Number(y)) => (x - y).abs() < 1e-12,
626        (LiteralValue::Int(x), LiteralValue::Int(y)) => x == y,
627        (LiteralValue::Number(x), LiteralValue::Int(y)) => (x - *y as f64).abs() < 1e-12,
628        (LiteralValue::Int(x), LiteralValue::Number(y)) => (*x as f64 - y).abs() < 1e-12,
629        (LiteralValue::Boolean(x), LiteralValue::Boolean(y)) => x == y,
630        (LiteralValue::Text(x), LiteralValue::Text(y)) => x.eq_ignore_ascii_case(y),
631        (LiteralValue::Empty, LiteralValue::Empty) => true,
632        _ => false,
633    }
634}
635
636pub fn register_builtins() {
637    use std::sync::Arc;
638    crate::function_registry::register_function(Arc::new(NotFn));
639    crate::function_registry::register_function(Arc::new(XorFn));
640    crate::function_registry::register_function(Arc::new(IfErrorFn));
641    crate::function_registry::register_function(Arc::new(IfNaFn));
642    crate::function_registry::register_function(Arc::new(IfsFn));
643    crate::function_registry::register_function(Arc::new(SwitchFn));
644}
645
646#[cfg(test)]
647mod tests {
648    use super::*;
649    use crate::test_workbook::TestWorkbook;
650    use crate::traits::ArgumentHandle;
651    use formualizer_common::LiteralValue;
652    use formualizer_parse::parser::{ASTNode, ASTNodeType};
653
654    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
655        wb.interpreter()
656    }
657
658    #[test]
659    fn not_basic() {
660        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
661        let ctx = interp(&wb);
662        let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
663        let args = vec![ArgumentHandle::new(&t, &ctx)];
664        let f = ctx.context.get_function("", "NOT").unwrap();
665        assert_eq!(
666            f.dispatch(&args, &ctx.function_context(None))
667                .unwrap()
668                .into_literal(),
669            LiteralValue::Boolean(false)
670        );
671    }
672
673    #[test]
674    fn xor_range_and_scalars() {
675        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
676        let ctx = interp(&wb);
677        let arr = ASTNode::new(
678            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
679                LiteralValue::Int(1),
680                LiteralValue::Int(0),
681                LiteralValue::Int(2),
682            ]])),
683            None,
684        );
685        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
686        let args = vec![
687            ArgumentHandle::new(&arr, &ctx),
688            ArgumentHandle::new(&zero, &ctx),
689        ];
690        let f = ctx.context.get_function("", "XOR").unwrap();
691        // 1,true,true -> 2 trues => even => FALSE
692        assert_eq!(
693            f.dispatch(&args, &ctx.function_context(None))
694                .unwrap()
695                .into_literal(),
696            LiteralValue::Boolean(false)
697        );
698    }
699
700    #[test]
701    fn iferror_fallback() {
702        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
703        let ctx = interp(&wb);
704        let err = ASTNode::new(
705            ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
706                "#DIV/0!",
707            ))),
708            None,
709        );
710        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
711        let args = vec![
712            ArgumentHandle::new(&err, &ctx),
713            ArgumentHandle::new(&fb, &ctx),
714        ];
715        let f = ctx.context.get_function("", "IFERROR").unwrap();
716        assert_eq!(
717            f.dispatch(&args, &ctx.function_context(None))
718                .unwrap()
719                .into_literal(),
720            LiteralValue::Int(5)
721        );
722    }
723
724    #[test]
725    fn iferror_passthrough_non_error() {
726        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
727        let ctx = interp(&wb);
728        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(11)), None);
729        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
730        let args = vec![
731            ArgumentHandle::new(&val, &ctx),
732            ArgumentHandle::new(&fb, &ctx),
733        ];
734        let f = ctx.context.get_function("", "IFERROR").unwrap();
735        assert_eq!(
736            f.dispatch(&args, &ctx.function_context(None))
737                .unwrap()
738                .into_literal(),
739            LiteralValue::Int(11)
740        );
741    }
742
743    #[test]
744    fn ifna_only_handles_na() {
745        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
746        let ctx = interp(&wb);
747        let na = ASTNode::new(
748            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
749            None,
750        );
751        let other_err = ASTNode::new(
752            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
753            None,
754        );
755        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(7)), None);
756        let args_na = vec![
757            ArgumentHandle::new(&na, &ctx),
758            ArgumentHandle::new(&fb, &ctx),
759        ];
760        let args_val = vec![
761            ArgumentHandle::new(&other_err, &ctx),
762            ArgumentHandle::new(&fb, &ctx),
763        ];
764        let f = ctx.context.get_function("", "IFNA").unwrap();
765        assert_eq!(
766            f.dispatch(&args_na, &ctx.function_context(None))
767                .unwrap()
768                .into_literal(),
769            LiteralValue::Int(7)
770        );
771        match f
772            .dispatch(&args_val, &ctx.function_context(None))
773            .unwrap()
774            .into_literal()
775        {
776            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
777            _ => panic!(),
778        }
779    }
780
781    #[test]
782    fn ifna_value_passthrough() {
783        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
784        let ctx = interp(&wb);
785        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(22)), None);
786        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
787        let args = vec![
788            ArgumentHandle::new(&val, &ctx),
789            ArgumentHandle::new(&fb, &ctx),
790        ];
791        let f = ctx.context.get_function("", "IFNA").unwrap();
792        assert_eq!(
793            f.dispatch(&args, &ctx.function_context(None))
794                .unwrap()
795                .into_literal(),
796            LiteralValue::Int(22)
797        );
798    }
799
800    #[test]
801    fn ifs_short_circuits() {
802        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
803        let ctx = interp(&wb);
804        let cond_true = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
805        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
806        let cond_false = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
807        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
808        let args = vec![
809            ArgumentHandle::new(&cond_true, &ctx),
810            ArgumentHandle::new(&val1, &ctx),
811            ArgumentHandle::new(&cond_false, &ctx),
812            ArgumentHandle::new(&val2, &ctx),
813        ];
814        let f = ctx.context.get_function("", "IFS").unwrap();
815        assert_eq!(
816            f.dispatch(&args, &ctx.function_context(None))
817                .unwrap()
818                .into_literal(),
819            LiteralValue::Int(9)
820        );
821    }
822
823    #[test]
824    fn ifs_no_match_returns_na_error() {
825        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
826        let ctx = interp(&wb);
827        let cond_false1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
828        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
829        let cond_false2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
830        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
831        let args = vec![
832            ArgumentHandle::new(&cond_false1, &ctx),
833            ArgumentHandle::new(&val1, &ctx),
834            ArgumentHandle::new(&cond_false2, &ctx),
835            ArgumentHandle::new(&val2, &ctx),
836        ];
837        let f = ctx.context.get_function("", "IFS").unwrap();
838        match f
839            .dispatch(&args, &ctx.function_context(None))
840            .unwrap()
841            .into_literal()
842        {
843            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
844            other => panic!("expected #N/A got {other:?}"),
845        }
846    }
847
848    #[test]
849    fn not_number_zero_and_nonzero() {
850        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
851        let ctx = interp(&wb);
852        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
853        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
854        let f = ctx.context.get_function("", "NOT").unwrap();
855        assert_eq!(
856            f.dispatch(
857                &[ArgumentHandle::new(&zero, &ctx)],
858                &ctx.function_context(None)
859            )
860            .unwrap()
861            .into_literal(),
862            LiteralValue::Boolean(true)
863        );
864        assert_eq!(
865            f.dispatch(
866                &[ArgumentHandle::new(&one, &ctx)],
867                &ctx.function_context(None)
868            )
869            .unwrap()
870            .into_literal(),
871            LiteralValue::Boolean(false)
872        );
873    }
874
875    #[test]
876    fn xor_error_propagation() {
877        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
878        let ctx = interp(&wb);
879        let err = ASTNode::new(
880            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
881            None,
882        );
883        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
884        let f = ctx.context.get_function("", "XOR").unwrap();
885        match f
886            .dispatch(
887                &[
888                    ArgumentHandle::new(&err, &ctx),
889                    ArgumentHandle::new(&one, &ctx),
890                ],
891                &ctx.function_context(None),
892            )
893            .unwrap()
894            .into_literal()
895        {
896            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
897            _ => panic!("expected value error"),
898        }
899    }
900
901    #[derive(Debug)]
902    struct ThrowNameFn;
903
904    impl Function for ThrowNameFn {
905        func_caps!(PURE);
906
907        fn name(&self) -> &'static str {
908            "THROWNAME"
909        }
910
911        fn eval<'a, 'b, 'c>(
912            &self,
913            _args: &'c [ArgumentHandle<'a, 'b>],
914            _ctx: &dyn FunctionContext<'b>,
915        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
916            Err(ExcelError::new_name())
917        }
918    }
919
920    #[test]
921    fn switch_match_and_default() {
922        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
923        let ctx = interp(&wb);
924        let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
925        let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
926        let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
927        let c2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
928        let v2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("b".into())), None);
929        let f = ctx.context.get_function("", "SWITCH").unwrap();
930        let args = vec![
931            ArgumentHandle::new(&expr, &ctx),
932            ArgumentHandle::new(&c1, &ctx),
933            ArgumentHandle::new(&v1, &ctx),
934            ArgumentHandle::new(&c2, &ctx),
935            ArgumentHandle::new(&v2, &ctx),
936        ];
937        assert_eq!(
938            f.dispatch(&args, &ctx.function_context(None))
939                .unwrap()
940                .into_literal(),
941            LiteralValue::Text("b".into())
942        );
943    }
944
945    #[test]
946    fn switch_no_match_no_default_returns_na() {
947        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
948        let ctx = interp(&wb);
949        let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(99)), None);
950        let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
951        let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
952        let f = ctx.context.get_function("", "SWITCH").unwrap();
953        let args = vec![
954            ArgumentHandle::new(&expr, &ctx),
955            ArgumentHandle::new(&c1, &ctx),
956            ArgumentHandle::new(&v1, &ctx),
957        ];
958        match f
959            .dispatch(&args, &ctx.function_context(None))
960            .unwrap()
961            .into_literal()
962        {
963            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
964            other => panic!("expected #N/A got {other:?}"),
965        }
966    }
967
968    #[test]
969    fn switch_with_default() {
970        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
971        let ctx = interp(&wb);
972        let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(99)), None);
973        let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
974        let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
975        let def = ASTNode::new(
976            ASTNodeType::Literal(LiteralValue::Text("default".into())),
977            None,
978        );
979        let f = ctx.context.get_function("", "SWITCH").unwrap();
980        let args = vec![
981            ArgumentHandle::new(&expr, &ctx),
982            ArgumentHandle::new(&c1, &ctx),
983            ArgumentHandle::new(&v1, &ctx),
984            ArgumentHandle::new(&def, &ctx),
985        ];
986        assert_eq!(
987            f.dispatch(&args, &ctx.function_context(None))
988                .unwrap()
989                .into_literal(),
990            LiteralValue::Text("default".into())
991        );
992    }
993
994    #[test]
995    fn iferror_catches_evaluation_errors_returned_as_err() {
996        let wb = TestWorkbook::new()
997            .with_function(std::sync::Arc::new(IfErrorFn))
998            .with_function(std::sync::Arc::new(ThrowNameFn));
999        let ctx = interp(&wb);
1000
1001        let throw = ASTNode::new(
1002            ASTNodeType::Function {
1003                name: "THROWNAME".to_string(),
1004                args: vec![],
1005            },
1006            None,
1007        );
1008        let fallback = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(42)), None);
1009
1010        let args = vec![
1011            ArgumentHandle::new(&throw, &ctx),
1012            ArgumentHandle::new(&fallback, &ctx),
1013        ];
1014        let f = ctx.context.get_function("", "IFERROR").unwrap();
1015
1016        assert_eq!(
1017            f.dispatch(&args, &ctx.function_context(None))
1018                .unwrap()
1019                .into_literal(),
1020            LiteralValue::Int(42)
1021        );
1022    }
1023}