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
503pub fn register_builtins() {
504    use std::sync::Arc;
505    crate::function_registry::register_function(Arc::new(NotFn));
506    crate::function_registry::register_function(Arc::new(XorFn));
507    crate::function_registry::register_function(Arc::new(IfErrorFn));
508    crate::function_registry::register_function(Arc::new(IfNaFn));
509    crate::function_registry::register_function(Arc::new(IfsFn));
510}
511
512#[cfg(test)]
513mod tests {
514    use super::*;
515    use crate::test_workbook::TestWorkbook;
516    use crate::traits::ArgumentHandle;
517    use formualizer_common::LiteralValue;
518    use formualizer_parse::parser::{ASTNode, ASTNodeType};
519
520    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
521        wb.interpreter()
522    }
523
524    #[test]
525    fn not_basic() {
526        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
527        let ctx = interp(&wb);
528        let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
529        let args = vec![ArgumentHandle::new(&t, &ctx)];
530        let f = ctx.context.get_function("", "NOT").unwrap();
531        assert_eq!(
532            f.dispatch(&args, &ctx.function_context(None))
533                .unwrap()
534                .into_literal(),
535            LiteralValue::Boolean(false)
536        );
537    }
538
539    #[test]
540    fn xor_range_and_scalars() {
541        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
542        let ctx = interp(&wb);
543        let arr = ASTNode::new(
544            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
545                LiteralValue::Int(1),
546                LiteralValue::Int(0),
547                LiteralValue::Int(2),
548            ]])),
549            None,
550        );
551        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
552        let args = vec![
553            ArgumentHandle::new(&arr, &ctx),
554            ArgumentHandle::new(&zero, &ctx),
555        ];
556        let f = ctx.context.get_function("", "XOR").unwrap();
557        // 1,true,true -> 2 trues => even => FALSE
558        assert_eq!(
559            f.dispatch(&args, &ctx.function_context(None))
560                .unwrap()
561                .into_literal(),
562            LiteralValue::Boolean(false)
563        );
564    }
565
566    #[test]
567    fn iferror_fallback() {
568        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
569        let ctx = interp(&wb);
570        let err = ASTNode::new(
571            ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
572                "#DIV/0!",
573            ))),
574            None,
575        );
576        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
577        let args = vec![
578            ArgumentHandle::new(&err, &ctx),
579            ArgumentHandle::new(&fb, &ctx),
580        ];
581        let f = ctx.context.get_function("", "IFERROR").unwrap();
582        assert_eq!(
583            f.dispatch(&args, &ctx.function_context(None))
584                .unwrap()
585                .into_literal(),
586            LiteralValue::Int(5)
587        );
588    }
589
590    #[test]
591    fn iferror_passthrough_non_error() {
592        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
593        let ctx = interp(&wb);
594        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(11)), None);
595        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
596        let args = vec![
597            ArgumentHandle::new(&val, &ctx),
598            ArgumentHandle::new(&fb, &ctx),
599        ];
600        let f = ctx.context.get_function("", "IFERROR").unwrap();
601        assert_eq!(
602            f.dispatch(&args, &ctx.function_context(None))
603                .unwrap()
604                .into_literal(),
605            LiteralValue::Int(11)
606        );
607    }
608
609    #[test]
610    fn ifna_only_handles_na() {
611        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
612        let ctx = interp(&wb);
613        let na = ASTNode::new(
614            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
615            None,
616        );
617        let other_err = ASTNode::new(
618            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
619            None,
620        );
621        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(7)), None);
622        let args_na = vec![
623            ArgumentHandle::new(&na, &ctx),
624            ArgumentHandle::new(&fb, &ctx),
625        ];
626        let args_val = vec![
627            ArgumentHandle::new(&other_err, &ctx),
628            ArgumentHandle::new(&fb, &ctx),
629        ];
630        let f = ctx.context.get_function("", "IFNA").unwrap();
631        assert_eq!(
632            f.dispatch(&args_na, &ctx.function_context(None))
633                .unwrap()
634                .into_literal(),
635            LiteralValue::Int(7)
636        );
637        match f
638            .dispatch(&args_val, &ctx.function_context(None))
639            .unwrap()
640            .into_literal()
641        {
642            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
643            _ => panic!(),
644        }
645    }
646
647    #[test]
648    fn ifna_value_passthrough() {
649        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
650        let ctx = interp(&wb);
651        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(22)), None);
652        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
653        let args = vec![
654            ArgumentHandle::new(&val, &ctx),
655            ArgumentHandle::new(&fb, &ctx),
656        ];
657        let f = ctx.context.get_function("", "IFNA").unwrap();
658        assert_eq!(
659            f.dispatch(&args, &ctx.function_context(None))
660                .unwrap()
661                .into_literal(),
662            LiteralValue::Int(22)
663        );
664    }
665
666    #[test]
667    fn ifs_short_circuits() {
668        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
669        let ctx = interp(&wb);
670        let cond_true = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
671        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
672        let cond_false = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
673        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
674        let args = vec![
675            ArgumentHandle::new(&cond_true, &ctx),
676            ArgumentHandle::new(&val1, &ctx),
677            ArgumentHandle::new(&cond_false, &ctx),
678            ArgumentHandle::new(&val2, &ctx),
679        ];
680        let f = ctx.context.get_function("", "IFS").unwrap();
681        assert_eq!(
682            f.dispatch(&args, &ctx.function_context(None))
683                .unwrap()
684                .into_literal(),
685            LiteralValue::Int(9)
686        );
687    }
688
689    #[test]
690    fn ifs_no_match_returns_na_error() {
691        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
692        let ctx = interp(&wb);
693        let cond_false1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
694        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
695        let cond_false2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
696        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
697        let args = vec![
698            ArgumentHandle::new(&cond_false1, &ctx),
699            ArgumentHandle::new(&val1, &ctx),
700            ArgumentHandle::new(&cond_false2, &ctx),
701            ArgumentHandle::new(&val2, &ctx),
702        ];
703        let f = ctx.context.get_function("", "IFS").unwrap();
704        match f
705            .dispatch(&args, &ctx.function_context(None))
706            .unwrap()
707            .into_literal()
708        {
709            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
710            other => panic!("expected #N/A got {other:?}"),
711        }
712    }
713
714    #[test]
715    fn not_number_zero_and_nonzero() {
716        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
717        let ctx = interp(&wb);
718        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
719        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
720        let f = ctx.context.get_function("", "NOT").unwrap();
721        assert_eq!(
722            f.dispatch(
723                &[ArgumentHandle::new(&zero, &ctx)],
724                &ctx.function_context(None)
725            )
726            .unwrap()
727            .into_literal(),
728            LiteralValue::Boolean(true)
729        );
730        assert_eq!(
731            f.dispatch(
732                &[ArgumentHandle::new(&one, &ctx)],
733                &ctx.function_context(None)
734            )
735            .unwrap()
736            .into_literal(),
737            LiteralValue::Boolean(false)
738        );
739    }
740
741    #[test]
742    fn xor_error_propagation() {
743        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
744        let ctx = interp(&wb);
745        let err = ASTNode::new(
746            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
747            None,
748        );
749        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
750        let f = ctx.context.get_function("", "XOR").unwrap();
751        match f
752            .dispatch(
753                &[
754                    ArgumentHandle::new(&err, &ctx),
755                    ArgumentHandle::new(&one, &ctx),
756                ],
757                &ctx.function_context(None),
758            )
759            .unwrap()
760            .into_literal()
761        {
762            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
763            _ => panic!("expected value error"),
764        }
765    }
766
767    #[derive(Debug)]
768    struct ThrowNameFn;
769
770    impl Function for ThrowNameFn {
771        func_caps!(PURE);
772
773        fn name(&self) -> &'static str {
774            "THROWNAME"
775        }
776
777        fn eval<'a, 'b, 'c>(
778            &self,
779            _args: &'c [ArgumentHandle<'a, 'b>],
780            _ctx: &dyn FunctionContext<'b>,
781        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
782            Err(ExcelError::new_name())
783        }
784    }
785
786    #[test]
787    fn iferror_catches_evaluation_errors_returned_as_err() {
788        let wb = TestWorkbook::new()
789            .with_function(std::sync::Arc::new(IfErrorFn))
790            .with_function(std::sync::Arc::new(ThrowNameFn));
791        let ctx = interp(&wb);
792
793        let throw = ASTNode::new(
794            ASTNodeType::Function {
795                name: "THROWNAME".to_string(),
796                args: vec![],
797            },
798            None,
799        );
800        let fallback = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(42)), None);
801
802        let args = vec![
803            ArgumentHandle::new(&throw, &ctx),
804            ArgumentHandle::new(&fallback, &ctx),
805        ];
806        let f = ctx.context.get_function("", "IFERROR").unwrap();
807
808        assert_eq!(
809            f.dispatch(&args, &ctx.function_context(None))
810                .unwrap()
811                .into_literal(),
812            LiteralValue::Int(42)
813        );
814    }
815}