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, 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 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; impl Function for IfNaFn {
379 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; impl 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#[derive(Debug)]
564pub struct SwitchFn;
565impl 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 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 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}