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
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 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}