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#[derive(Debug)]
12pub struct NotFn;
13impl 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;
101impl 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; impl Function for IfErrorFn {
293 func_caps!(PURE);
294 fn name(&self) -> &'static str {
295 "IFERROR"
296 }
297 fn min_args(&self) -> usize {
298 2
299 }
300 fn variadic(&self) -> bool {
301 false
302 }
303 fn arg_schema(&self) -> &'static [ArgSchema] {
304 use std::sync::LazyLock;
305 static TWO: LazyLock<Vec<ArgSchema>> =
307 LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
308 &TWO[..]
309 }
310 fn eval<'a, 'b, 'c>(
311 &self,
312 args: &'c [ArgumentHandle<'a, 'b>],
313 _ctx: &dyn FunctionContext<'b>,
314 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
315 if args.len() != 2 {
316 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
317 ExcelError::new_value(),
318 )));
319 }
320 match args[0].value() {
321 Ok(cv) => match cv.into_literal() {
322 LiteralValue::Error(_) => args[1].value(),
323 other => Ok(crate::traits::CalcValue::Scalar(other)),
324 },
325 Err(_) => args[1].value(),
326 }
327 }
328}
329
330#[derive(Debug)]
331pub struct IfNaFn; impl Function for IfNaFn {
376 func_caps!(PURE);
377 fn name(&self) -> &'static str {
378 "IFNA"
379 }
380 fn min_args(&self) -> usize {
381 2
382 }
383 fn variadic(&self) -> bool {
384 false
385 }
386 fn arg_schema(&self) -> &'static [ArgSchema] {
387 use std::sync::LazyLock;
388 static TWO: LazyLock<Vec<ArgSchema>> =
389 LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
390 &TWO[..]
391 }
392 fn eval<'a, 'b, 'c>(
393 &self,
394 args: &'c [ArgumentHandle<'a, 'b>],
395 _ctx: &dyn FunctionContext<'b>,
396 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
397 if args.len() != 2 {
398 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
399 ExcelError::new_value(),
400 )));
401 }
402 let v = args[0].value()?.into_literal();
403 match v {
404 LiteralValue::Error(ref e) if e.kind == formualizer_common::ExcelErrorKind::Na => {
405 args[1].value()
406 }
407 other => Ok(crate::traits::CalcValue::Scalar(other)),
408 }
409 }
410}
411
412#[derive(Debug)]
413pub struct IfsFn; impl Function for IfsFn {
458 func_caps!(PURE, SHORT_CIRCUIT);
459 fn name(&self) -> &'static str {
460 "IFS"
461 }
462 fn min_args(&self) -> usize {
463 2
464 }
465 fn variadic(&self) -> bool {
466 true
467 }
468 fn arg_schema(&self) -> &'static [ArgSchema] {
469 &ARG_ANY_ONE[..]
470 }
471 fn eval<'a, 'b, 'c>(
472 &self,
473 args: &'c [ArgumentHandle<'a, 'b>],
474 _ctx: &dyn FunctionContext<'b>,
475 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
476 if args.len() < 2 || !args.len().is_multiple_of(2) {
477 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
478 ExcelError::new_value(),
479 )));
480 }
481 for pair in args.chunks(2) {
482 let cond = pair[0].value()?.into_literal();
483 let is_true = match cond {
484 LiteralValue::Boolean(b) => b,
485 LiteralValue::Number(n) => n != 0.0,
486 LiteralValue::Int(i) => i != 0,
487 LiteralValue::Empty => false,
488 LiteralValue::Error(e) => {
489 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
490 }
491 _ => {
492 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
493 ExcelError::from_error_string("#VALUE!"),
494 )));
495 }
496 };
497 if is_true {
498 return pair[1].value();
499 }
500 }
501 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
502 ExcelError::new_na(),
503 )))
504 }
505}
506
507#[derive(Debug)]
559pub struct SwitchFn;
560impl Function for SwitchFn {
571 func_caps!(PURE, SHORT_CIRCUIT);
572 fn name(&self) -> &'static str {
573 "SWITCH"
574 }
575 fn min_args(&self) -> usize {
576 3
577 }
578 fn variadic(&self) -> bool {
579 true
580 }
581 fn arg_schema(&self) -> &'static [ArgSchema] {
582 &ARG_ANY_ONE[..]
583 }
584 fn eval<'a, 'b, 'c>(
585 &self,
586 args: &'c [ArgumentHandle<'a, 'b>],
587 _ctx: &dyn FunctionContext<'b>,
588 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
589 if args.len() < 3 {
590 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
591 ExcelError::new_value(),
592 )));
593 }
594 let expr = args[0].value()?.into_literal();
595 if let LiteralValue::Error(e) = &expr {
596 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
597 e.clone(),
598 )));
599 }
600 let rest = &args[1..];
602 let has_default = rest.len() % 2 == 1;
603 let pairs = if has_default {
604 rest.len() - 1
605 } else {
606 rest.len()
607 };
608 for chunk in rest[..pairs].chunks(2) {
609 let candidate = chunk[0].value()?.into_literal();
610 if switch_values_equal(&expr, &candidate) {
611 return chunk[1].value();
612 }
613 }
614 if has_default {
615 return rest.last().unwrap().value();
616 }
617 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
618 ExcelError::new_na(),
619 )))
620 }
621}
622
623fn switch_values_equal(a: &LiteralValue, b: &LiteralValue) -> bool {
624 match (a, b) {
625 (LiteralValue::Number(x), LiteralValue::Number(y)) => (x - y).abs() < 1e-12,
626 (LiteralValue::Int(x), LiteralValue::Int(y)) => x == y,
627 (LiteralValue::Number(x), LiteralValue::Int(y)) => (x - *y as f64).abs() < 1e-12,
628 (LiteralValue::Int(x), LiteralValue::Number(y)) => (*x as f64 - y).abs() < 1e-12,
629 (LiteralValue::Boolean(x), LiteralValue::Boolean(y)) => x == y,
630 (LiteralValue::Text(x), LiteralValue::Text(y)) => x.eq_ignore_ascii_case(y),
631 (LiteralValue::Empty, LiteralValue::Empty) => true,
632 _ => false,
633 }
634}
635
636pub fn register_builtins() {
637 use std::sync::Arc;
638 crate::function_registry::register_function(Arc::new(NotFn));
639 crate::function_registry::register_function(Arc::new(XorFn));
640 crate::function_registry::register_function(Arc::new(IfErrorFn));
641 crate::function_registry::register_function(Arc::new(IfNaFn));
642 crate::function_registry::register_function(Arc::new(IfsFn));
643 crate::function_registry::register_function(Arc::new(SwitchFn));
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649 use crate::test_workbook::TestWorkbook;
650 use crate::traits::ArgumentHandle;
651 use formualizer_common::LiteralValue;
652 use formualizer_parse::parser::{ASTNode, ASTNodeType};
653
654 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
655 wb.interpreter()
656 }
657
658 #[test]
659 fn not_basic() {
660 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
661 let ctx = interp(&wb);
662 let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
663 let args = vec![ArgumentHandle::new(&t, &ctx)];
664 let f = ctx.context.get_function("", "NOT").unwrap();
665 assert_eq!(
666 f.dispatch(&args, &ctx.function_context(None))
667 .unwrap()
668 .into_literal(),
669 LiteralValue::Boolean(false)
670 );
671 }
672
673 #[test]
674 fn xor_range_and_scalars() {
675 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
676 let ctx = interp(&wb);
677 let arr = ASTNode::new(
678 ASTNodeType::Literal(LiteralValue::Array(vec![vec![
679 LiteralValue::Int(1),
680 LiteralValue::Int(0),
681 LiteralValue::Int(2),
682 ]])),
683 None,
684 );
685 let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
686 let args = vec![
687 ArgumentHandle::new(&arr, &ctx),
688 ArgumentHandle::new(&zero, &ctx),
689 ];
690 let f = ctx.context.get_function("", "XOR").unwrap();
691 assert_eq!(
693 f.dispatch(&args, &ctx.function_context(None))
694 .unwrap()
695 .into_literal(),
696 LiteralValue::Boolean(false)
697 );
698 }
699
700 #[test]
701 fn iferror_fallback() {
702 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
703 let ctx = interp(&wb);
704 let err = ASTNode::new(
705 ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
706 "#DIV/0!",
707 ))),
708 None,
709 );
710 let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
711 let args = vec![
712 ArgumentHandle::new(&err, &ctx),
713 ArgumentHandle::new(&fb, &ctx),
714 ];
715 let f = ctx.context.get_function("", "IFERROR").unwrap();
716 assert_eq!(
717 f.dispatch(&args, &ctx.function_context(None))
718 .unwrap()
719 .into_literal(),
720 LiteralValue::Int(5)
721 );
722 }
723
724 #[test]
725 fn iferror_passthrough_non_error() {
726 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
727 let ctx = interp(&wb);
728 let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(11)), None);
729 let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
730 let args = vec![
731 ArgumentHandle::new(&val, &ctx),
732 ArgumentHandle::new(&fb, &ctx),
733 ];
734 let f = ctx.context.get_function("", "IFERROR").unwrap();
735 assert_eq!(
736 f.dispatch(&args, &ctx.function_context(None))
737 .unwrap()
738 .into_literal(),
739 LiteralValue::Int(11)
740 );
741 }
742
743 #[test]
744 fn ifna_only_handles_na() {
745 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
746 let ctx = interp(&wb);
747 let na = ASTNode::new(
748 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
749 None,
750 );
751 let other_err = ASTNode::new(
752 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
753 None,
754 );
755 let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(7)), None);
756 let args_na = vec![
757 ArgumentHandle::new(&na, &ctx),
758 ArgumentHandle::new(&fb, &ctx),
759 ];
760 let args_val = vec![
761 ArgumentHandle::new(&other_err, &ctx),
762 ArgumentHandle::new(&fb, &ctx),
763 ];
764 let f = ctx.context.get_function("", "IFNA").unwrap();
765 assert_eq!(
766 f.dispatch(&args_na, &ctx.function_context(None))
767 .unwrap()
768 .into_literal(),
769 LiteralValue::Int(7)
770 );
771 match f
772 .dispatch(&args_val, &ctx.function_context(None))
773 .unwrap()
774 .into_literal()
775 {
776 LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
777 _ => panic!(),
778 }
779 }
780
781 #[test]
782 fn ifna_value_passthrough() {
783 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
784 let ctx = interp(&wb);
785 let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(22)), None);
786 let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
787 let args = vec![
788 ArgumentHandle::new(&val, &ctx),
789 ArgumentHandle::new(&fb, &ctx),
790 ];
791 let f = ctx.context.get_function("", "IFNA").unwrap();
792 assert_eq!(
793 f.dispatch(&args, &ctx.function_context(None))
794 .unwrap()
795 .into_literal(),
796 LiteralValue::Int(22)
797 );
798 }
799
800 #[test]
801 fn ifs_short_circuits() {
802 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
803 let ctx = interp(&wb);
804 let cond_true = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
805 let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
806 let cond_false = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
807 let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
808 let args = vec![
809 ArgumentHandle::new(&cond_true, &ctx),
810 ArgumentHandle::new(&val1, &ctx),
811 ArgumentHandle::new(&cond_false, &ctx),
812 ArgumentHandle::new(&val2, &ctx),
813 ];
814 let f = ctx.context.get_function("", "IFS").unwrap();
815 assert_eq!(
816 f.dispatch(&args, &ctx.function_context(None))
817 .unwrap()
818 .into_literal(),
819 LiteralValue::Int(9)
820 );
821 }
822
823 #[test]
824 fn ifs_no_match_returns_na_error() {
825 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
826 let ctx = interp(&wb);
827 let cond_false1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
828 let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
829 let cond_false2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
830 let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
831 let args = vec![
832 ArgumentHandle::new(&cond_false1, &ctx),
833 ArgumentHandle::new(&val1, &ctx),
834 ArgumentHandle::new(&cond_false2, &ctx),
835 ArgumentHandle::new(&val2, &ctx),
836 ];
837 let f = ctx.context.get_function("", "IFS").unwrap();
838 match f
839 .dispatch(&args, &ctx.function_context(None))
840 .unwrap()
841 .into_literal()
842 {
843 LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
844 other => panic!("expected #N/A got {other:?}"),
845 }
846 }
847
848 #[test]
849 fn not_number_zero_and_nonzero() {
850 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
851 let ctx = interp(&wb);
852 let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
853 let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
854 let f = ctx.context.get_function("", "NOT").unwrap();
855 assert_eq!(
856 f.dispatch(
857 &[ArgumentHandle::new(&zero, &ctx)],
858 &ctx.function_context(None)
859 )
860 .unwrap()
861 .into_literal(),
862 LiteralValue::Boolean(true)
863 );
864 assert_eq!(
865 f.dispatch(
866 &[ArgumentHandle::new(&one, &ctx)],
867 &ctx.function_context(None)
868 )
869 .unwrap()
870 .into_literal(),
871 LiteralValue::Boolean(false)
872 );
873 }
874
875 #[test]
876 fn xor_error_propagation() {
877 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
878 let ctx = interp(&wb);
879 let err = ASTNode::new(
880 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
881 None,
882 );
883 let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
884 let f = ctx.context.get_function("", "XOR").unwrap();
885 match f
886 .dispatch(
887 &[
888 ArgumentHandle::new(&err, &ctx),
889 ArgumentHandle::new(&one, &ctx),
890 ],
891 &ctx.function_context(None),
892 )
893 .unwrap()
894 .into_literal()
895 {
896 LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
897 _ => panic!("expected value error"),
898 }
899 }
900
901 #[derive(Debug)]
902 struct ThrowNameFn;
903
904 impl Function for ThrowNameFn {
905 func_caps!(PURE);
906
907 fn name(&self) -> &'static str {
908 "THROWNAME"
909 }
910
911 fn eval<'a, 'b, 'c>(
912 &self,
913 _args: &'c [ArgumentHandle<'a, 'b>],
914 _ctx: &dyn FunctionContext<'b>,
915 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
916 Err(ExcelError::new_name())
917 }
918 }
919
920 #[test]
921 fn switch_match_and_default() {
922 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
923 let ctx = interp(&wb);
924 let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
925 let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
926 let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
927 let c2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
928 let v2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("b".into())), None);
929 let f = ctx.context.get_function("", "SWITCH").unwrap();
930 let args = vec![
931 ArgumentHandle::new(&expr, &ctx),
932 ArgumentHandle::new(&c1, &ctx),
933 ArgumentHandle::new(&v1, &ctx),
934 ArgumentHandle::new(&c2, &ctx),
935 ArgumentHandle::new(&v2, &ctx),
936 ];
937 assert_eq!(
938 f.dispatch(&args, &ctx.function_context(None))
939 .unwrap()
940 .into_literal(),
941 LiteralValue::Text("b".into())
942 );
943 }
944
945 #[test]
946 fn switch_no_match_no_default_returns_na() {
947 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
948 let ctx = interp(&wb);
949 let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(99)), None);
950 let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
951 let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
952 let f = ctx.context.get_function("", "SWITCH").unwrap();
953 let args = vec![
954 ArgumentHandle::new(&expr, &ctx),
955 ArgumentHandle::new(&c1, &ctx),
956 ArgumentHandle::new(&v1, &ctx),
957 ];
958 match f
959 .dispatch(&args, &ctx.function_context(None))
960 .unwrap()
961 .into_literal()
962 {
963 LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
964 other => panic!("expected #N/A got {other:?}"),
965 }
966 }
967
968 #[test]
969 fn switch_with_default() {
970 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SwitchFn));
971 let ctx = interp(&wb);
972 let expr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(99)), None);
973 let c1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
974 let v1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("a".into())), None);
975 let def = ASTNode::new(
976 ASTNodeType::Literal(LiteralValue::Text("default".into())),
977 None,
978 );
979 let f = ctx.context.get_function("", "SWITCH").unwrap();
980 let args = vec![
981 ArgumentHandle::new(&expr, &ctx),
982 ArgumentHandle::new(&c1, &ctx),
983 ArgumentHandle::new(&v1, &ctx),
984 ArgumentHandle::new(&def, &ctx),
985 ];
986 assert_eq!(
987 f.dispatch(&args, &ctx.function_context(None))
988 .unwrap()
989 .into_literal(),
990 LiteralValue::Text("default".into())
991 );
992 }
993
994 #[test]
995 fn iferror_catches_evaluation_errors_returned_as_err() {
996 let wb = TestWorkbook::new()
997 .with_function(std::sync::Arc::new(IfErrorFn))
998 .with_function(std::sync::Arc::new(ThrowNameFn));
999 let ctx = interp(&wb);
1000
1001 let throw = ASTNode::new(
1002 ASTNodeType::Function {
1003 name: "THROWNAME".to_string(),
1004 args: vec![],
1005 },
1006 None,
1007 );
1008 let fallback = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(42)), None);
1009
1010 let args = vec![
1011 ArgumentHandle::new(&throw, &ctx),
1012 ArgumentHandle::new(&fallback, &ctx),
1013 ];
1014 let f = ctx.context.get_function("", "IFERROR").unwrap();
1015
1016 assert_eq!(
1017 f.dispatch(&args, &ctx.function_context(None))
1018 .unwrap()
1019 .into_literal(),
1020 LiteralValue::Int(42)
1021 );
1022 }
1023}