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