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