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, SHORT_CIRCUIT
291/// [formualizer-docgen:schema:end]
292impl Function for IfErrorFn {
293    // SHORT_CIRCUIT: dispatch must not eagerly evaluate the fallback arm —
294    // the eval body below evaluates arg0 first and touches arg1 only when
295    // arg0 produced an error (same defect class as the IF fix in #118).
296    func_caps!(PURE, SHORT_CIRCUIT);
297    fn name(&self) -> &'static str {
298        "IFERROR"
299    }
300    fn min_args(&self) -> usize {
301        2
302    }
303    fn variadic(&self) -> bool {
304        false
305    }
306    fn arg_schema(&self) -> &'static [ArgSchema] {
307        use std::sync::LazyLock;
308        // value, fallback (any scalar)
309        static TWO: LazyLock<Vec<ArgSchema>> =
310            LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
311        &TWO[..]
312    }
313    fn eval<'a, 'b, 'c>(
314        &self,
315        args: &'c [ArgumentHandle<'a, 'b>],
316        _ctx: &dyn FunctionContext<'b>,
317    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
318        if args.len() != 2 {
319            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
320                ExcelError::new_value(),
321            )));
322        }
323        match args[0].value() {
324            Ok(cv) => match cv.into_literal() {
325                LiteralValue::Error(_) => args[1].value(),
326                other => Ok(crate::traits::CalcValue::Scalar(other)),
327            },
328            Err(_) => args[1].value(),
329        }
330    }
331}
332
333#[derive(Debug)]
334pub struct IfNaFn; // IFNA(value, fallback)
335/// Returns a fallback only when the first expression is `#N/A`.
336///
337/// `IFNA(value, value_if_na)` is narrower than `IFERROR`.
338///
339/// # Remarks
340/// - Only `#N/A` triggers fallback.
341/// - Other error kinds are returned unchanged.
342/// - Non-error results pass through unchanged.
343/// - Exactly two arguments are required; other arities return `#VALUE!`.
344///
345/// # Examples
346///
347/// ```yaml,sandbox
348/// title: "Catch N/A"
349/// formula: '=IFNA(NA(), "missing")'
350/// expected: "missing"
351/// ```
352///
353/// ```yaml,sandbox
354/// title: "Do not catch other errors"
355/// formula: '=IFNA(1/0, "missing")'
356/// expected: "#DIV/0!"
357/// ```
358///
359/// ```yaml,docs
360/// related:
361///   - IFERROR
362///   - ISNA
363///   - NA
364/// faq:
365///   - q: "Which errors does IFNA intercept?"
366///     a: "Only #N/A is intercepted; all other errors pass through unchanged."
367/// ```
368/// [formualizer-docgen:schema:start]
369/// Name: IFNA
370/// Type: IfNaFn
371/// Min args: 2
372/// Max args: 2
373/// Variadic: false
374/// Signature: IFNA(arg1: any@scalar, arg2: any@scalar)
375/// 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}
376/// Caps: PURE, SHORT_CIRCUIT
377/// [formualizer-docgen:schema:end]
378impl Function for IfNaFn {
379    // SHORT_CIRCUIT: the fallback arm is evaluated only when arg0 is #N/A;
380    // all other values/errors pass through without touching arg1.
381    func_caps!(PURE, SHORT_CIRCUIT);
382    fn name(&self) -> &'static str {
383        "IFNA"
384    }
385    fn min_args(&self) -> usize {
386        2
387    }
388    fn variadic(&self) -> bool {
389        false
390    }
391    fn arg_schema(&self) -> &'static [ArgSchema] {
392        use std::sync::LazyLock;
393        static TWO: LazyLock<Vec<ArgSchema>> =
394            LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
395        &TWO[..]
396    }
397    fn eval<'a, 'b, 'c>(
398        &self,
399        args: &'c [ArgumentHandle<'a, 'b>],
400        _ctx: &dyn FunctionContext<'b>,
401    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
402        if args.len() != 2 {
403            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
404                ExcelError::new_value(),
405            )));
406        }
407        let v = args[0].value()?.into_literal();
408        match v {
409            LiteralValue::Error(ref e) if e.kind == formualizer_common::ExcelErrorKind::Na => {
410                args[1].value()
411            }
412            other => Ok(crate::traits::CalcValue::Scalar(other)),
413        }
414    }
415}
416
417#[derive(Debug)]
418pub struct IfsFn; // IFS(cond1, val1, cond2, val2, ...)
419/// Returns the value for the first TRUE condition in condition-value pairs.
420///
421/// `IFS(cond1, value1, cond2, value2, ...)` evaluates left to right and short-circuits.
422///
423/// # Remarks
424/// - Arguments must be provided as pairs; odd argument counts return `#VALUE!`.
425/// - Conditions accept booleans and numbers (`0` FALSE, non-zero TRUE); blank is FALSE.
426/// - Text conditions return `#VALUE!`; error conditions propagate.
427/// - If no condition is TRUE, returns `#N/A`.
428///
429/// # Examples
430///
431/// ```yaml,sandbox
432/// title: "First matching condition wins"
433/// formula: '=IFS(2<1, "a", 3>2, "b", TRUE, "c")'
434/// expected: "b"
435/// ```
436///
437/// ```yaml,sandbox
438/// title: "No conditions matched"
439/// formula: '=IFS(FALSE, 1, 0, 2)'
440/// expected: "#N/A"
441/// ```
442///
443/// ```yaml,docs
444/// related:
445///   - IF
446///   - AND
447///   - OR
448/// faq:
449///   - q: "What errors can IFS return on structure issues?"
450///     a: "Odd argument counts return #VALUE!, and no TRUE condition returns #N/A."
451/// ```
452/// [formualizer-docgen:schema:start]
453/// Name: IFS
454/// Type: IfsFn
455/// Min args: 2
456/// Max args: variadic
457/// Variadic: true
458/// Signature: IFS(arg1...: any@scalar)
459/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
460/// Caps: PURE, SHORT_CIRCUIT
461/// [formualizer-docgen:schema:end]
462impl Function for IfsFn {
463    func_caps!(PURE, SHORT_CIRCUIT);
464    fn name(&self) -> &'static str {
465        "IFS"
466    }
467    fn min_args(&self) -> usize {
468        2
469    }
470    fn variadic(&self) -> bool {
471        true
472    }
473    fn arg_schema(&self) -> &'static [ArgSchema] {
474        &ARG_ANY_ONE[..]
475    }
476    fn eval<'a, 'b, 'c>(
477        &self,
478        args: &'c [ArgumentHandle<'a, 'b>],
479        _ctx: &dyn FunctionContext<'b>,
480    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
481        if args.len() < 2 || !args.len().is_multiple_of(2) {
482            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
483                ExcelError::new_value(),
484            )));
485        }
486        for pair in args.chunks(2) {
487            let cond = pair[0].value()?.into_literal();
488            let is_true = match cond {
489                LiteralValue::Boolean(b) => b,
490                LiteralValue::Number(n) => n != 0.0,
491                LiteralValue::Int(i) => i != 0,
492                LiteralValue::Empty => false,
493                LiteralValue::Error(e) => {
494                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
495                }
496                _ => {
497                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
498                        ExcelError::from_error_string("#VALUE!"),
499                    )));
500                }
501            };
502            if is_true {
503                return pair[1].value();
504            }
505        }
506        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
507            ExcelError::new_na(),
508        )))
509    }
510}
511
512/// Returns the result corresponding to the first matching candidate value.
513///
514/// `SWITCH(expression, value1, result1, [value2, result2], ..., [default])`
515/// compares `expression` against each candidate from left to right.
516///
517/// # Remarks
518/// - Matching is case-insensitive for text values.
519/// - Numeric comparisons treat `Int` and `Number` values as compatible.
520/// - A trailing unmatched argument acts as the default result.
521/// - When no candidate matches and no default is supplied, returns `#N/A`.
522/// - Errors in `expression` propagate immediately.
523///
524/// # Examples
525///
526/// ```excel
527/// =SWITCH("gold","silver",1,"gold",2,0)
528/// ```
529///
530/// ```yaml,sandbox
531/// title: "Match a text label"
532/// formula: '=SWITCH("gold","silver",1,"gold",2,0)'
533/// expected: 2
534/// ```
535///
536/// ```yaml,sandbox
537/// title: "Fall back to default"
538/// formula: '=SWITCH(3,1,"one",2,"two","other")'
539/// expected: "other"
540/// ```
541///
542/// ```yaml,docs
543/// related:
544///   - IF
545///   - IFS
546///   - CHOOSE
547/// faq:
548///   - q: "Does SWITCH compare text case-sensitively?"
549///     a: "No. Text comparisons are case-insensitive in this implementation."
550///   - q: "What happens when nothing matches?"
551///     a: "SWITCH returns the trailing default when provided; otherwise it returns #N/A."
552/// ```
553/// [formualizer-docgen:schema:start]
554/// Name: SWITCH
555/// Type: SwitchFn
556/// Min args: 3
557/// Max args: variadic
558/// Variadic: true
559/// Signature: SWITCH(arg1: any@scalar, arg2...: any@scalar)
560/// 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}
561/// Caps: PURE, SHORT_CIRCUIT
562/// [formualizer-docgen:schema:end]
563#[derive(Debug)]
564pub struct SwitchFn;
565/// [formualizer-docgen:schema:start]
566/// Name: SWITCH
567/// Type: SwitchFn
568/// Min args: 3
569/// Max args: variadic
570/// Variadic: true
571/// Signature: SWITCH(arg1...: any@scalar)
572/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
573/// Caps: PURE, SHORT_CIRCUIT
574/// [formualizer-docgen:schema:end]
575impl Function for SwitchFn {
576    func_caps!(PURE, SHORT_CIRCUIT);
577    fn name(&self) -> &'static str {
578        "SWITCH"
579    }
580    fn min_args(&self) -> usize {
581        3
582    }
583    fn variadic(&self) -> bool {
584        true
585    }
586    fn arg_schema(&self) -> &'static [ArgSchema] {
587        &ARG_ANY_ONE[..]
588    }
589    fn eval<'a, 'b, 'c>(
590        &self,
591        args: &'c [ArgumentHandle<'a, 'b>],
592        _ctx: &dyn FunctionContext<'b>,
593    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
594        if args.len() < 3 {
595            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
596                ExcelError::new_value(),
597            )));
598        }
599        let expr = args[0].value()?.into_literal();
600        if let LiteralValue::Error(e) = &expr {
601            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
602                e.clone(),
603            )));
604        }
605        // args[1..] are value/result pairs with optional trailing default
606        let rest = &args[1..];
607        let has_default = rest.len() % 2 == 1;
608        let pairs = if has_default {
609            rest.len() - 1
610        } else {
611            rest.len()
612        };
613        for chunk in rest[..pairs].chunks(2) {
614            let candidate = chunk[0].value()?.into_literal();
615            if switch_values_equal(&expr, &candidate) {
616                return chunk[1].value();
617            }
618        }
619        if has_default {
620            return rest.last().unwrap().value();
621        }
622        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
623            ExcelError::new_na(),
624        )))
625    }
626}
627
628fn switch_values_equal(a: &LiteralValue, b: &LiteralValue) -> bool {
629    match (a, b) {
630        (LiteralValue::Number(x), LiteralValue::Number(y)) => (x - y).abs() < 1e-12,
631        (LiteralValue::Int(x), LiteralValue::Int(y)) => x == y,
632        (LiteralValue::Number(x), LiteralValue::Int(y)) => (x - *y as f64).abs() < 1e-12,
633        (LiteralValue::Int(x), LiteralValue::Number(y)) => (*x as f64 - y).abs() < 1e-12,
634        (LiteralValue::Boolean(x), LiteralValue::Boolean(y)) => x == y,
635        (LiteralValue::Text(x), LiteralValue::Text(y)) => x.eq_ignore_ascii_case(y),
636        (LiteralValue::Empty, LiteralValue::Empty) => true,
637        _ => false,
638    }
639}
640
641pub fn register_builtins() {
642    use std::sync::Arc;
643    crate::function_registry::register_function(Arc::new(NotFn));
644    crate::function_registry::register_function(Arc::new(XorFn));
645    crate::function_registry::register_function(Arc::new(IfErrorFn));
646    crate::function_registry::register_function(Arc::new(IfNaFn));
647    crate::function_registry::register_function(Arc::new(IfsFn));
648    crate::function_registry::register_function(Arc::new(SwitchFn));
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654    use crate::test_workbook::TestWorkbook;
655    use crate::traits::ArgumentHandle;
656    use formualizer_common::LiteralValue;
657    use formualizer_parse::parser::{ASTNode, ASTNodeType};
658
659    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
660        wb.interpreter()
661    }
662
663    #[test]
664    fn not_basic() {
665        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
666        let ctx = interp(&wb);
667        let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
668        let args = vec![ArgumentHandle::new(&t, &ctx)];
669        let f = ctx.context.get_function("", "NOT").unwrap();
670        assert_eq!(
671            f.dispatch(&args, &ctx.function_context(None))
672                .unwrap()
673                .into_literal(),
674            LiteralValue::Boolean(false)
675        );
676    }
677
678    #[test]
679    fn xor_range_and_scalars() {
680        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
681        let ctx = interp(&wb);
682        let arr = ASTNode::new(
683            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
684                LiteralValue::Int(1),
685                LiteralValue::Int(0),
686                LiteralValue::Int(2),
687            ]])),
688            None,
689        );
690        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
691        let args = vec![
692            ArgumentHandle::new(&arr, &ctx),
693            ArgumentHandle::new(&zero, &ctx),
694        ];
695        let f = ctx.context.get_function("", "XOR").unwrap();
696        // 1,true,true -> 2 trues => even => FALSE
697        assert_eq!(
698            f.dispatch(&args, &ctx.function_context(None))
699                .unwrap()
700                .into_literal(),
701            LiteralValue::Boolean(false)
702        );
703    }
704
705    #[test]
706    fn iferror_fallback() {
707        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
708        let ctx = interp(&wb);
709        let err = ASTNode::new(
710            ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
711                "#DIV/0!",
712            ))),
713            None,
714        );
715        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
716        let args = vec![
717            ArgumentHandle::new(&err, &ctx),
718            ArgumentHandle::new(&fb, &ctx),
719        ];
720        let f = ctx.context.get_function("", "IFERROR").unwrap();
721        assert_eq!(
722            f.dispatch(&args, &ctx.function_context(None))
723                .unwrap()
724                .into_literal(),
725            LiteralValue::Int(5)
726        );
727    }
728
729    #[test]
730    fn iferror_passthrough_non_error() {
731        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
732        let ctx = interp(&wb);
733        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(11)), None);
734        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
735        let args = vec![
736            ArgumentHandle::new(&val, &ctx),
737            ArgumentHandle::new(&fb, &ctx),
738        ];
739        let f = ctx.context.get_function("", "IFERROR").unwrap();
740        assert_eq!(
741            f.dispatch(&args, &ctx.function_context(None))
742                .unwrap()
743                .into_literal(),
744            LiteralValue::Int(11)
745        );
746    }
747
748    #[test]
749    fn ifna_only_handles_na() {
750        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
751        let ctx = interp(&wb);
752        let na = ASTNode::new(
753            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
754            None,
755        );
756        let other_err = ASTNode::new(
757            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
758            None,
759        );
760        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(7)), None);
761        let args_na = vec![
762            ArgumentHandle::new(&na, &ctx),
763            ArgumentHandle::new(&fb, &ctx),
764        ];
765        let args_val = vec![
766            ArgumentHandle::new(&other_err, &ctx),
767            ArgumentHandle::new(&fb, &ctx),
768        ];
769        let f = ctx.context.get_function("", "IFNA").unwrap();
770        assert_eq!(
771            f.dispatch(&args_na, &ctx.function_context(None))
772                .unwrap()
773                .into_literal(),
774            LiteralValue::Int(7)
775        );
776        match f
777            .dispatch(&args_val, &ctx.function_context(None))
778            .unwrap()
779            .into_literal()
780        {
781            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
782            _ => panic!(),
783        }
784    }
785
786    #[test]
787    fn ifna_value_passthrough() {
788        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
789        let ctx = interp(&wb);
790        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(22)), None);
791        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
792        let args = vec![
793            ArgumentHandle::new(&val, &ctx),
794            ArgumentHandle::new(&fb, &ctx),
795        ];
796        let f = ctx.context.get_function("", "IFNA").unwrap();
797        assert_eq!(
798            f.dispatch(&args, &ctx.function_context(None))
799                .unwrap()
800                .into_literal(),
801            LiteralValue::Int(22)
802        );
803    }
804
805    #[test]
806    fn ifs_short_circuits() {
807        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
808        let ctx = interp(&wb);
809        let cond_true = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
810        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
811        let cond_false = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
812        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
813        let args = vec![
814            ArgumentHandle::new(&cond_true, &ctx),
815            ArgumentHandle::new(&val1, &ctx),
816            ArgumentHandle::new(&cond_false, &ctx),
817            ArgumentHandle::new(&val2, &ctx),
818        ];
819        let f = ctx.context.get_function("", "IFS").unwrap();
820        assert_eq!(
821            f.dispatch(&args, &ctx.function_context(None))
822                .unwrap()
823                .into_literal(),
824            LiteralValue::Int(9)
825        );
826    }
827
828    #[test]
829    fn ifs_no_match_returns_na_error() {
830        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
831        let ctx = interp(&wb);
832        let cond_false1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
833        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
834        let cond_false2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
835        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
836        let args = vec![
837            ArgumentHandle::new(&cond_false1, &ctx),
838            ArgumentHandle::new(&val1, &ctx),
839            ArgumentHandle::new(&cond_false2, &ctx),
840            ArgumentHandle::new(&val2, &ctx),
841        ];
842        let f = ctx.context.get_function("", "IFS").unwrap();
843        match f
844            .dispatch(&args, &ctx.function_context(None))
845            .unwrap()
846            .into_literal()
847        {
848            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
849            other => panic!("expected #N/A got {other:?}"),
850        }
851    }
852
853    #[test]
854    fn not_number_zero_and_nonzero() {
855        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
856        let ctx = interp(&wb);
857        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
858        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
859        let f = ctx.context.get_function("", "NOT").unwrap();
860        assert_eq!(
861            f.dispatch(
862                &[ArgumentHandle::new(&zero, &ctx)],
863                &ctx.function_context(None)
864            )
865            .unwrap()
866            .into_literal(),
867            LiteralValue::Boolean(true)
868        );
869        assert_eq!(
870            f.dispatch(
871                &[ArgumentHandle::new(&one, &ctx)],
872                &ctx.function_context(None)
873            )
874            .unwrap()
875            .into_literal(),
876            LiteralValue::Boolean(false)
877        );
878    }
879
880    #[test]
881    fn xor_error_propagation() {
882        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
883        let ctx = interp(&wb);
884        let err = ASTNode::new(
885            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
886            None,
887        );
888        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
889        let f = ctx.context.get_function("", "XOR").unwrap();
890        match f
891            .dispatch(
892                &[
893                    ArgumentHandle::new(&err, &ctx),
894                    ArgumentHandle::new(&one, &ctx),
895                ],
896                &ctx.function_context(None),
897            )
898            .unwrap()
899            .into_literal()
900        {
901            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
902            _ => panic!("expected value error"),
903        }
904    }
905
906    #[derive(Debug)]
907    struct ThrowNameFn;
908
909    impl Function for ThrowNameFn {
910        func_caps!(PURE);
911
912        fn name(&self) -> &'static str {
913            "THROWNAME"
914        }
915
916        fn eval<'a, 'b, 'c>(
917            &self,
918            _args: &'c [ArgumentHandle<'a, 'b>],
919            _ctx: &dyn FunctionContext<'b>,
920        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
921            Err(ExcelError::new_name())
922        }
923    }
924
925    #[test]
926    fn switch_match_and_default() {
927        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
928        let ctx = interp(&wb);
929        let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
930        let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
931        let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
932        let c2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
933        let v2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("b".into())), None);
934        let f = ctx.context.get_function("", "SWITCH").unwrap();
935        let args = vec![
936            ArgumentHandle::new(&expr, &ctx),
937            ArgumentHandle::new(&c1, &ctx),
938            ArgumentHandle::new(&v1, &ctx),
939            ArgumentHandle::new(&c2, &ctx),
940            ArgumentHandle::new(&v2, &ctx),
941        ];
942        assert_eq!(
943            f.dispatch(&args, &ctx.function_context(None))
944                .unwrap()
945                .into_literal(),
946            LiteralValue::Text("b".into())
947        );
948    }
949
950    #[test]
951    fn switch_no_match_no_default_returns_na() {
952        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
953        let ctx = interp(&wb);
954        let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(99)), None);
955        let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
956        let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
957        let f = ctx.context.get_function("", "SWITCH").unwrap();
958        let args = vec![
959            ArgumentHandle::new(&expr, &ctx),
960            ArgumentHandle::new(&c1, &ctx),
961            ArgumentHandle::new(&v1, &ctx),
962        ];
963        match f
964            .dispatch(&args, &ctx.function_context(None))
965            .unwrap()
966            .into_literal()
967        {
968            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
969            other => panic!("expected #N/A got {other:?}"),
970        }
971    }
972
973    #[test]
974    fn switch_with_default() {
975        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
976        let ctx = interp(&wb);
977        let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(99)), None);
978        let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
979        let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
980        let def = ASTNode::new(
981            ASTNodeType::Literal(LiteralValue::Text("default".into())),
982            None,
983        );
984        let f = ctx.context.get_function("", "SWITCH").unwrap();
985        let args = vec![
986            ArgumentHandle::new(&expr, &ctx),
987            ArgumentHandle::new(&c1, &ctx),
988            ArgumentHandle::new(&v1, &ctx),
989            ArgumentHandle::new(&def, &ctx),
990        ];
991        assert_eq!(
992            f.dispatch(&args, &ctx.function_context(None))
993                .unwrap()
994                .into_literal(),
995            LiteralValue::Text("default".into())
996        );
997    }
998
999    #[test]
1000    fn iferror_catches_evaluation_errors_returned_as_err() {
1001        let wb = TestWorkbook::new()
1002            .with_function(std::sync::Arc::new(IfErrorFn))
1003            .with_function(std::sync::Arc::new(ThrowNameFn));
1004        let ctx = interp(&wb);
1005
1006        let throw = ASTNode::new(
1007            ASTNodeType::Function {
1008                name: "THROWNAME".to_string(),
1009                args: vec![],
1010            },
1011            None,
1012        );
1013        let fallback = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(42)), None);
1014
1015        let args = vec![
1016            ArgumentHandle::new(&throw, &ctx),
1017            ArgumentHandle::new(&fallback, &ctx),
1018        ];
1019        let f = ctx.context.get_function("", "IFERROR").unwrap();
1020
1021        assert_eq!(
1022            f.dispatch(&args, &ctx.function_context(None))
1023                .unwrap()
1024                .into_literal(),
1025            LiteralValue::Int(42)
1026        );
1027    }
1028}