Skip to main content

formualizer_eval/builtins/
logical.rs

1// crates/formualizer-eval/src/builtins/logical.rs
2
3use super::utils::ARG_ANY_ONE;
4use crate::args::ArgSchema;
5use crate::function::Function;
6use crate::traits::{ArgumentHandle, FunctionContext};
7use formualizer_common::{ExcelError, LiteralValue};
8use formualizer_macros::func_caps;
9
10/* ─────────────────────────── TRUE() ─────────────────────────────── */
11
12#[derive(Debug)]
13pub struct TrueFn;
14/// Returns the logical constant TRUE.
15///
16/// Use `TRUE()` when you want an explicit boolean value in formulas.
17///
18/// # Remarks
19/// - `TRUE` takes no arguments and always returns the boolean value `TRUE`.
20/// - No coercion or evaluation side effects are involved.
21///
22/// # Examples
23///
24/// ```yaml,sandbox
25/// title: "Return TRUE directly"
26/// formula: '=TRUE()'
27/// expected: true
28/// ```
29///
30/// ```yaml,sandbox
31/// title: "Use TRUE in branching"
32/// formula: '=IF(TRUE(), "yes", "no")'
33/// expected: "yes"
34/// ```
35///
36/// ```yaml,docs
37/// related:
38///   - FALSE
39///   - IF
40///   - AND
41/// faq:
42///   - q: "Can TRUE accept arguments?"
43///     a: "No. TRUE takes zero arguments and always returns the boolean constant TRUE."
44/// ```
45/// [formualizer-docgen:schema:start]
46/// Name: TRUE
47/// Type: TrueFn
48/// Min args: 0
49/// Max args: 0
50/// Variadic: false
51/// Signature: TRUE()
52/// Arg schema: []
53/// Caps: PURE
54/// [formualizer-docgen:schema:end]
55impl Function for TrueFn {
56    func_caps!(PURE);
57
58    fn name(&self) -> &'static str {
59        "TRUE"
60    }
61    fn min_args(&self) -> usize {
62        0
63    }
64
65    fn eval<'a, 'b, 'c>(
66        &self,
67        _args: &'c [ArgumentHandle<'a, 'b>],
68        _ctx: &dyn FunctionContext<'b>,
69    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
70        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
71            true,
72        )))
73    }
74}
75
76/* ─────────────────────────── FALSE() ────────────────────────────── */
77
78#[derive(Debug)]
79pub struct FalseFn;
80/// Returns the logical constant FALSE.
81///
82/// Use `FALSE()` when you want an explicit boolean false value in formulas.
83///
84/// # Remarks
85/// - `FALSE` takes no arguments and always returns the boolean value `FALSE`.
86/// - No coercion or evaluation side effects are involved.
87///
88/// # Examples
89///
90/// ```yaml,sandbox
91/// title: "Return FALSE directly"
92/// formula: '=FALSE()'
93/// expected: false
94/// ```
95///
96/// ```yaml,sandbox
97/// title: "Use FALSE in branching"
98/// formula: '=IF(FALSE(), "yes", "no")'
99/// expected: "no"
100/// ```
101///
102/// ```yaml,docs
103/// related:
104///   - TRUE
105///   - IF
106///   - OR
107/// faq:
108///   - q: "Can FALSE accept arguments?"
109///     a: "No. FALSE takes zero arguments and always returns the boolean constant FALSE."
110/// ```
111/// [formualizer-docgen:schema:start]
112/// Name: FALSE
113/// Type: FalseFn
114/// Min args: 0
115/// Max args: 0
116/// Variadic: false
117/// Signature: FALSE()
118/// Arg schema: []
119/// Caps: PURE
120/// [formualizer-docgen:schema:end]
121impl Function for FalseFn {
122    func_caps!(PURE);
123
124    fn name(&self) -> &'static str {
125        "FALSE"
126    }
127    fn min_args(&self) -> usize {
128        0
129    }
130
131    fn eval<'a, 'b, 'c>(
132        &self,
133        _args: &'c [ArgumentHandle<'a, 'b>],
134        _ctx: &dyn FunctionContext<'b>,
135    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
136        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
137            false,
138        )))
139    }
140}
141
142/* ─────────────────────────── AND() ──────────────────────────────── */
143
144#[derive(Debug)]
145pub struct AndFn;
146/// Returns TRUE only when all supplied values evaluate to TRUE.
147///
148/// `AND` evaluates arguments left to right and short-circuits on a decisive `FALSE`.
149///
150/// # Remarks
151/// - Booleans and numbers are accepted (`0` is FALSE, non-zero is TRUE).
152/// - Blank values are treated as FALSE.
153/// - Text and other non-coercible values yield `#VALUE!` unless a prior FALSE short-circuits.
154/// - If no decisive FALSE is found, the first encountered error is returned.
155///
156/// # Examples
157///
158/// ```yaml,sandbox
159/// title: "All truthy inputs"
160/// formula: '=AND(TRUE, 1, 5)'
161/// expected: true
162/// ```
163///
164/// ```yaml,sandbox
165/// title: "Text input causes VALUE error"
166/// formula: '=AND(TRUE, "x")'
167/// expected: "#VALUE!"
168/// ```
169///
170/// ```yaml,docs
171/// related:
172///   - OR
173///   - NOT
174///   - XOR
175/// faq:
176///   - q: "What happens with blanks and text in AND?"
177///     a: "Blank values evaluate as FALSE; non-coercible text yields #VALUE! unless a prior FALSE short-circuits."
178/// ```
179/// [formualizer-docgen:schema:start]
180/// Name: AND
181/// Type: AndFn
182/// Min args: 1
183/// Max args: variadic
184/// Variadic: true
185/// Signature: AND(arg1...: any@scalar)
186/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
187/// Caps: PURE, REDUCTION, BOOL_ONLY, SHORT_CIRCUIT
188/// [formualizer-docgen:schema:end]
189impl Function for AndFn {
190    func_caps!(PURE, REDUCTION, BOOL_ONLY, SHORT_CIRCUIT);
191
192    fn name(&self) -> &'static str {
193        "AND"
194    }
195    fn min_args(&self) -> usize {
196        1
197    }
198    fn variadic(&self) -> bool {
199        true
200    }
201    fn arg_schema(&self) -> &'static [ArgSchema] {
202        &ARG_ANY_ONE[..]
203    }
204
205    fn eval<'a, 'b, 'c>(
206        &self,
207        args: &'c [ArgumentHandle<'a, 'b>],
208        _ctx: &dyn FunctionContext<'b>,
209    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
210        let mut first_error: Option<LiteralValue> = None;
211        for h in args {
212            let it = h.lazy_values_owned()?;
213            for v in it {
214                match v {
215                    LiteralValue::Error(_) => {
216                        if first_error.is_none() {
217                            first_error = Some(v);
218                        }
219                    }
220                    LiteralValue::Empty => {
221                        return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
222                            false,
223                        )));
224                    }
225                    LiteralValue::Boolean(b) => {
226                        if !b {
227                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
228                                false,
229                            )));
230                        }
231                    }
232                    LiteralValue::Number(n) => {
233                        if n == 0.0 {
234                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
235                                false,
236                            )));
237                        }
238                    }
239                    LiteralValue::Int(i) => {
240                        if i == 0 {
241                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
242                                false,
243                            )));
244                        }
245                    }
246                    _ => {
247                        // Non-coercible (e.g., Text) → #VALUE! candidate with message
248                        if first_error.is_none() {
249                            first_error =
250                                Some(LiteralValue::Error(ExcelError::new_value().with_message(
251                                    "AND expects logical/numeric inputs; text is not coercible",
252                                )));
253                        }
254                    }
255                }
256            }
257        }
258        if let Some(err) = first_error {
259            return Ok(crate::traits::CalcValue::Scalar(err));
260        }
261        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
262            true,
263        )))
264    }
265}
266
267/* ─────────────────────────── OR() ───────────────────────────────── */
268
269#[derive(Debug)]
270pub struct OrFn;
271/// Returns TRUE when any supplied value evaluates to TRUE.
272///
273/// `OR` evaluates arguments left to right and short-circuits on a decisive `TRUE`.
274///
275/// # Remarks
276/// - Booleans and numbers are accepted (`0` is FALSE, non-zero is TRUE).
277/// - Blank values are ignored.
278/// - Text and other non-coercible values yield `#VALUE!` if no prior TRUE short-circuits.
279/// - If no TRUE is found, the first encountered error is returned.
280///
281/// # Examples
282///
283/// ```yaml,sandbox
284/// title: "One truthy value makes OR true"
285/// formula: '=OR(FALSE, 0, 2)'
286/// expected: true
287/// ```
288///
289/// ```yaml,sandbox
290/// title: "No true values and text input"
291/// formula: '=OR(FALSE, "x")'
292/// expected: "#VALUE!"
293/// ```
294///
295/// ```yaml,docs
296/// related:
297///   - AND
298///   - NOT
299///   - XOR
300/// faq:
301///   - q: "How does OR treat blanks and text?"
302///     a: "Blanks are ignored; non-coercible text returns #VALUE! unless a prior TRUE already short-circuits."
303/// ```
304/// [formualizer-docgen:schema:start]
305/// Name: OR
306/// Type: OrFn
307/// Min args: 1
308/// Max args: variadic
309/// Variadic: true
310/// Signature: OR(arg1...: any@scalar)
311/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
312/// Caps: PURE, REDUCTION, BOOL_ONLY, SHORT_CIRCUIT
313/// [formualizer-docgen:schema:end]
314impl Function for OrFn {
315    func_caps!(PURE, REDUCTION, BOOL_ONLY, SHORT_CIRCUIT);
316
317    fn name(&self) -> &'static str {
318        "OR"
319    }
320    fn min_args(&self) -> usize {
321        1
322    }
323    fn variadic(&self) -> bool {
324        true
325    }
326    fn arg_schema(&self) -> &'static [ArgSchema] {
327        &ARG_ANY_ONE[..]
328    }
329
330    fn eval<'a, 'b, 'c>(
331        &self,
332        args: &'c [ArgumentHandle<'a, 'b>],
333        _ctx: &dyn FunctionContext<'b>,
334    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
335        let mut first_error: Option<LiteralValue> = None;
336        for h in args {
337            let it = h.lazy_values_owned()?;
338            for v in it {
339                match v {
340                    LiteralValue::Error(_) => {
341                        if first_error.is_none() {
342                            first_error = Some(v);
343                        }
344                    }
345                    LiteralValue::Empty => {
346                        // ignored
347                    }
348                    LiteralValue::Boolean(b) => {
349                        if b {
350                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
351                                true,
352                            )));
353                        }
354                    }
355                    LiteralValue::Number(n) => {
356                        if n != 0.0 {
357                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
358                                true,
359                            )));
360                        }
361                    }
362                    LiteralValue::Int(i) => {
363                        if i != 0 {
364                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
365                                true,
366                            )));
367                        }
368                    }
369                    _ => {
370                        // Non-coercible → #VALUE! candidate with message
371                        if first_error.is_none() {
372                            first_error =
373                                Some(LiteralValue::Error(ExcelError::new_value().with_message(
374                                    "OR expects logical/numeric inputs; text is not coercible",
375                                )));
376                        }
377                    }
378                }
379            }
380        }
381        if let Some(err) = first_error {
382            return Ok(crate::traits::CalcValue::Scalar(err));
383        }
384        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
385            false,
386        )))
387    }
388}
389
390/* ─────────────────────────── IF() ───────────────────────────────── */
391
392#[derive(Debug)]
393pub struct IfFn;
394/// Returns one value when a condition is TRUE and another when FALSE.
395///
396/// `IF(condition, value_if_true, [value_if_false])` supports two or three arguments.
397///
398/// # Remarks
399/// - Condition coercion: booleans are used directly, numbers use `0` as FALSE and non-zero as TRUE.
400/// - A blank condition is treated as FALSE.
401/// - Text or other non-numeric/non-boolean conditions return `#VALUE!`.
402/// - With only two arguments, the FALSE branch defaults to logical `FALSE`.
403///
404/// # Examples
405///
406/// ```yaml,sandbox
407/// title: "Numeric condition"
408/// formula: '=IF(2, "yes", "no")'
409/// expected: "yes"
410/// ```
411///
412/// ```yaml,sandbox
413/// title: "Two-argument IF defaults false branch"
414/// formula: '=IF(0, 10)'
415/// expected: false
416/// ```
417///
418/// ```yaml,docs
419/// related:
420///   - IFS
421///   - IFERROR
422///   - IFNA
423/// faq:
424///   - q: "What is returned when IF has only two arguments and condition is FALSE?"
425///     a: "The false branch defaults to logical FALSE when value_if_false is omitted."
426/// ```
427/// [formualizer-docgen:schema:start]
428/// Name: IF
429/// Type: IfFn
430/// Min args: 2
431/// Max args: variadic
432/// Variadic: true
433/// Signature: IF(arg1...: any@scalar)
434/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
435/// Caps: PURE, SHORT_CIRCUIT
436/// [formualizer-docgen:schema:end]
437impl Function for IfFn {
438    func_caps!(PURE, SHORT_CIRCUIT);
439
440    fn name(&self) -> &'static str {
441        "IF"
442    }
443    fn min_args(&self) -> usize {
444        2
445    }
446    fn variadic(&self) -> bool {
447        true
448    }
449
450    fn arg_schema(&self) -> &'static [ArgSchema] {
451        use std::sync::LazyLock;
452        // Single variadic any schema so we can enforce precise 2 or 3 arity inside eval()
453        static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
454        &ONE[..]
455    }
456
457    fn eval<'a, 'b, 'c>(
458        &self,
459        args: &'c [ArgumentHandle<'a, 'b>],
460        _ctx: &dyn FunctionContext<'b>,
461    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
462        if args.len() < 2 || args.len() > 3 {
463            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
464                ExcelError::new_value()
465                    .with_message(format!("IF expects 2 or 3 arguments, got {}", args.len())),
466            )));
467        }
468
469        let condition = args[0].value()?.into_literal();
470        let b = match condition {
471            LiteralValue::Boolean(b) => b,
472            LiteralValue::Number(n) => n != 0.0,
473            LiteralValue::Int(i) => i != 0,
474            LiteralValue::Empty => false,
475            _ => {
476                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
477                    ExcelError::new_value().with_message("IF condition must be boolean or number"),
478                )));
479            }
480        };
481
482        if b {
483            args[1].value()
484        } else if let Some(arg) = args.get(2) {
485            arg.value()
486        } else {
487            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
488                false,
489            )))
490        }
491    }
492}
493
494pub fn register_builtins() {
495    crate::function_registry::register_function(std::sync::Arc::new(TrueFn));
496    crate::function_registry::register_function(std::sync::Arc::new(FalseFn));
497    crate::function_registry::register_function(std::sync::Arc::new(AndFn));
498    crate::function_registry::register_function(std::sync::Arc::new(OrFn));
499    crate::function_registry::register_function(std::sync::Arc::new(IfFn));
500}
501
502/* ─────────────────────────── tests ─────────────────────────────── */
503
504#[cfg(test)]
505mod tests {
506    use super::*;
507    use crate::traits::ArgumentHandle;
508    use crate::{interpreter::Interpreter, test_workbook::TestWorkbook};
509    use formualizer_parse::LiteralValue;
510    use std::sync::{
511        Arc,
512        atomic::{AtomicUsize, Ordering},
513    };
514
515    #[derive(Debug)]
516    struct CountFn(Arc<AtomicUsize>);
517    impl Function for CountFn {
518        func_caps!(PURE);
519        fn name(&self) -> &'static str {
520            "COUNTING"
521        }
522        fn min_args(&self) -> usize {
523            0
524        }
525        fn eval<'a, 'b, 'c>(
526            &self,
527            _args: &'c [ArgumentHandle<'a, 'b>],
528            _ctx: &dyn FunctionContext<'b>,
529        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
530            self.0.fetch_add(1, Ordering::SeqCst);
531            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
532                true,
533            )))
534        }
535    }
536
537    #[derive(Debug)]
538    struct ErrorFn(Arc<AtomicUsize>);
539    impl Function for ErrorFn {
540        func_caps!(PURE);
541        fn name(&self) -> &'static str {
542            "ERRORFN"
543        }
544        fn min_args(&self) -> usize {
545            0
546        }
547        fn eval<'a, 'b, 'c>(
548            &self,
549            _args: &'c [ArgumentHandle<'a, 'b>],
550            _ctx: &dyn FunctionContext<'b>,
551        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
552            self.0.fetch_add(1, Ordering::SeqCst);
553            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
554                ExcelError::new_value(),
555            )))
556        }
557    }
558
559    fn interp(wb: &TestWorkbook) -> Interpreter<'_> {
560        wb.interpreter()
561    }
562
563    #[test]
564    fn test_true_false() {
565        let wb = TestWorkbook::new()
566            .with_function(std::sync::Arc::new(TrueFn))
567            .with_function(std::sync::Arc::new(FalseFn));
568
569        let ctx = interp(&wb);
570        let t = ctx.context.get_function("", "TRUE").unwrap();
571        let fctx = ctx.function_context(None);
572        assert_eq!(
573            t.eval(&[], &fctx).unwrap().into_literal(),
574            LiteralValue::Boolean(true)
575        );
576
577        let f = ctx.context.get_function("", "FALSE").unwrap();
578        assert_eq!(
579            f.eval(&[], &fctx).unwrap().into_literal(),
580            LiteralValue::Boolean(false)
581        );
582    }
583
584    #[test]
585    fn test_and_or() {
586        let wb = TestWorkbook::new()
587            .with_function(std::sync::Arc::new(AndFn))
588            .with_function(std::sync::Arc::new(OrFn));
589        let ctx = interp(&wb);
590        let fctx = ctx.function_context(None);
591
592        let and = ctx.context.get_function("", "AND").unwrap();
593        let or = ctx.context.get_function("", "OR").unwrap();
594        // Build ArgumentHandles manually: TRUE, 1, FALSE
595        let dummy_ast = formualizer_parse::parser::ASTNode::new(
596            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(true)),
597            None,
598        );
599        let dummy_ast_false = formualizer_parse::parser::ASTNode::new(
600            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(false)),
601            None,
602        );
603        let dummy_ast_one = formualizer_parse::parser::ASTNode::new(
604            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(1)),
605            None,
606        );
607        let hs = vec![
608            ArgumentHandle::new(&dummy_ast, &ctx),
609            ArgumentHandle::new(&dummy_ast_one, &ctx),
610        ];
611        assert_eq!(
612            and.eval(&hs, &fctx).unwrap().into_literal(),
613            LiteralValue::Boolean(true)
614        );
615
616        let hs2 = vec![
617            ArgumentHandle::new(&dummy_ast_false, &ctx),
618            ArgumentHandle::new(&dummy_ast_one, &ctx),
619        ];
620        assert_eq!(
621            and.eval(&hs2, &fctx).unwrap().into_literal(),
622            LiteralValue::Boolean(false)
623        );
624        assert_eq!(
625            or.eval(&hs2, &fctx).unwrap().into_literal(),
626            LiteralValue::Boolean(true)
627        );
628    }
629
630    #[test]
631    fn and_short_circuits_on_false_without_evaluating_rest() {
632        let counter = Arc::new(AtomicUsize::new(0));
633        let wb = TestWorkbook::new()
634            .with_function(Arc::new(AndFn))
635            .with_function(Arc::new(CountFn(counter.clone())));
636        let ctx = interp(&wb);
637        let fctx = ctx.function_context(None);
638        let and = ctx.context.get_function("", "AND").unwrap();
639
640        // Build args: FALSE, COUNTING()
641        let a_false = formualizer_parse::parser::ASTNode::new(
642            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(false)),
643            None,
644        );
645        let counting_call = formualizer_parse::parser::ASTNode::new(
646            formualizer_parse::parser::ASTNodeType::Function {
647                name: "COUNTING".into(),
648                args: vec![],
649            },
650            None,
651        );
652        let hs = vec![
653            ArgumentHandle::new(&a_false, &ctx),
654            ArgumentHandle::new(&counting_call, &ctx),
655        ];
656        let out = and.eval(&hs, &fctx).unwrap().into_literal();
657        assert_eq!(out, LiteralValue::Boolean(false));
658        assert_eq!(
659            counter.load(Ordering::SeqCst),
660            0,
661            "COUNTING should not be evaluated"
662        );
663    }
664
665    #[test]
666    fn or_short_circuits_on_true_without_evaluating_rest() {
667        let counter = Arc::new(AtomicUsize::new(0));
668        let wb = TestWorkbook::new()
669            .with_function(Arc::new(OrFn))
670            .with_function(Arc::new(CountFn(counter.clone())));
671        let ctx = interp(&wb);
672        let fctx = ctx.function_context(None);
673        let or = ctx.context.get_function("", "OR").unwrap();
674
675        // Build args: TRUE, COUNTING()
676        let a_true = formualizer_parse::parser::ASTNode::new(
677            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(true)),
678            None,
679        );
680        let counting_call = formualizer_parse::parser::ASTNode::new(
681            formualizer_parse::parser::ASTNodeType::Function {
682                name: "COUNTING".into(),
683                args: vec![],
684            },
685            None,
686        );
687        let hs = vec![
688            ArgumentHandle::new(&a_true, &ctx),
689            ArgumentHandle::new(&counting_call, &ctx),
690        ];
691        let out = or.eval(&hs, &fctx).unwrap().into_literal();
692        assert_eq!(out, LiteralValue::Boolean(true));
693        assert_eq!(
694            counter.load(Ordering::SeqCst),
695            0,
696            "COUNTING should not be evaluated"
697        );
698    }
699
700    #[test]
701    fn or_range_arg_short_circuits_on_first_true_before_evaluating_next_arg() {
702        let counter = Arc::new(AtomicUsize::new(0));
703        let wb = TestWorkbook::new()
704            .with_function(Arc::new(OrFn))
705            .with_function(Arc::new(CountFn(counter.clone())));
706        let ctx = interp(&wb);
707        let fctx = ctx.function_context(None);
708        let or = ctx.context.get_function("", "OR").unwrap();
709
710        // First arg is an array literal with first element 1 (truey), then zeros.
711        let arr = formualizer_parse::parser::ASTNode::new(
712            formualizer_parse::parser::ASTNodeType::Array(vec![
713                vec![formualizer_parse::parser::ASTNode::new(
714                    formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(1)),
715                    None,
716                )],
717                vec![formualizer_parse::parser::ASTNode::new(
718                    formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(0)),
719                    None,
720                )],
721            ]),
722            None,
723        );
724        let counting_call = formualizer_parse::parser::ASTNode::new(
725            formualizer_parse::parser::ASTNodeType::Function {
726                name: "COUNTING".into(),
727                args: vec![],
728            },
729            None,
730        );
731        let hs = vec![
732            ArgumentHandle::new(&arr, &ctx),
733            ArgumentHandle::new(&counting_call, &ctx),
734        ];
735        let out = or.eval(&hs, &fctx).unwrap().into_literal();
736        assert_eq!(out, LiteralValue::Boolean(true));
737        assert_eq!(
738            counter.load(Ordering::SeqCst),
739            0,
740            "COUNTING should not be evaluated"
741        );
742    }
743
744    #[test]
745    fn and_returns_first_error_when_no_decisive_false() {
746        let err_counter = Arc::new(AtomicUsize::new(0));
747        let wb = TestWorkbook::new()
748            .with_function(Arc::new(AndFn))
749            .with_function(Arc::new(ErrorFn(err_counter.clone())));
750        let ctx = interp(&wb);
751        let fctx = ctx.function_context(None);
752        let and = ctx.context.get_function("", "AND").unwrap();
753
754        // AND(1, ERRORFN(), 1) => #VALUE!
755        let one = formualizer_parse::parser::ASTNode::new(
756            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(1)),
757            None,
758        );
759        let errcall = formualizer_parse::parser::ASTNode::new(
760            formualizer_parse::parser::ASTNodeType::Function {
761                name: "ERRORFN".into(),
762                args: vec![],
763            },
764            None,
765        );
766        let hs = vec![
767            ArgumentHandle::new(&one, &ctx),
768            ArgumentHandle::new(&errcall, &ctx),
769            ArgumentHandle::new(&one, &ctx),
770        ];
771        let out = and.eval(&hs, &fctx).unwrap().into_literal();
772        match out {
773            LiteralValue::Error(e) => assert_eq!(e.to_string(), "#VALUE!"),
774            _ => panic!("Expected error"),
775        }
776        assert_eq!(
777            err_counter.load(Ordering::SeqCst),
778            1,
779            "ERRORFN should be evaluated once"
780        );
781    }
782
783    #[test]
784    fn or_does_not_evaluate_error_after_true() {
785        let err_counter = Arc::new(AtomicUsize::new(0));
786        let wb = TestWorkbook::new()
787            .with_function(Arc::new(OrFn))
788            .with_function(Arc::new(ErrorFn(err_counter.clone())));
789        let ctx = interp(&wb);
790        let fctx = ctx.function_context(None);
791        let or = ctx.context.get_function("", "OR").unwrap();
792
793        // OR(TRUE, ERRORFN()) => TRUE and ERRORFN not evaluated
794        let a_true = formualizer_parse::parser::ASTNode::new(
795            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(true)),
796            None,
797        );
798        let errcall = formualizer_parse::parser::ASTNode::new(
799            formualizer_parse::parser::ASTNodeType::Function {
800                name: "ERRORFN".into(),
801                args: vec![],
802            },
803            None,
804        );
805        let hs = vec![
806            ArgumentHandle::new(&a_true, &ctx),
807            ArgumentHandle::new(&errcall, &ctx),
808        ];
809        let out = or.eval(&hs, &fctx).unwrap().into_literal();
810        assert_eq!(out, LiteralValue::Boolean(true));
811        assert_eq!(
812            err_counter.load(Ordering::SeqCst),
813            0,
814            "ERRORFN should not be evaluated"
815        );
816    }
817
818    #[test]
819    fn if_treats_empty_condition_as_false() {
820        let wb = TestWorkbook::new().with_function(Arc::new(IfFn));
821        let ctx = interp(&wb);
822        let fctx = ctx.function_context(None);
823        let iff = ctx.context.get_function("", "IF").unwrap();
824
825        let cond_empty = formualizer_parse::parser::ASTNode::new(
826            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Empty),
827            None,
828        );
829        let when_true = formualizer_parse::parser::ASTNode::new(
830            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(10)),
831            None,
832        );
833        let when_false = formualizer_parse::parser::ASTNode::new(
834            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(20)),
835            None,
836        );
837
838        let args = vec![
839            ArgumentHandle::new(&cond_empty, &ctx),
840            ArgumentHandle::new(&when_true, &ctx),
841            ArgumentHandle::new(&when_false, &ctx),
842        ];
843
844        assert_eq!(
845            iff.eval(&args, &fctx).unwrap().into_literal(),
846            LiteralValue::Int(20)
847        );
848    }
849}