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
15impl Function for TrueFn {
16    func_caps!(PURE);
17
18    fn name(&self) -> &'static str {
19        "TRUE"
20    }
21    fn min_args(&self) -> usize {
22        0
23    }
24
25    fn eval<'a, 'b, 'c>(
26        &self,
27        _args: &'c [ArgumentHandle<'a, 'b>],
28        _ctx: &dyn FunctionContext<'b>,
29    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
30        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
31            true,
32        )))
33    }
34}
35
36/* ─────────────────────────── FALSE() ────────────────────────────── */
37
38#[derive(Debug)]
39pub struct FalseFn;
40
41impl Function for FalseFn {
42    func_caps!(PURE);
43
44    fn name(&self) -> &'static str {
45        "FALSE"
46    }
47    fn min_args(&self) -> usize {
48        0
49    }
50
51    fn eval<'a, 'b, 'c>(
52        &self,
53        _args: &'c [ArgumentHandle<'a, 'b>],
54        _ctx: &dyn FunctionContext<'b>,
55    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
56        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
57            false,
58        )))
59    }
60}
61
62/* ─────────────────────────── AND() ──────────────────────────────── */
63
64#[derive(Debug)]
65pub struct AndFn;
66
67impl Function for AndFn {
68    func_caps!(PURE, REDUCTION, BOOL_ONLY, SHORT_CIRCUIT);
69
70    fn name(&self) -> &'static str {
71        "AND"
72    }
73    fn min_args(&self) -> usize {
74        1
75    }
76    fn variadic(&self) -> bool {
77        true
78    }
79    fn arg_schema(&self) -> &'static [ArgSchema] {
80        &ARG_ANY_ONE[..]
81    }
82
83    fn eval<'a, 'b, 'c>(
84        &self,
85        args: &'c [ArgumentHandle<'a, 'b>],
86        _ctx: &dyn FunctionContext<'b>,
87    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
88        let mut first_error: Option<LiteralValue> = None;
89        for h in args {
90            let it = h.lazy_values_owned()?;
91            for v in it {
92                match v {
93                    LiteralValue::Error(_) => {
94                        if first_error.is_none() {
95                            first_error = Some(v);
96                        }
97                    }
98                    LiteralValue::Empty => {
99                        return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
100                            false,
101                        )));
102                    }
103                    LiteralValue::Boolean(b) => {
104                        if !b {
105                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
106                                false,
107                            )));
108                        }
109                    }
110                    LiteralValue::Number(n) => {
111                        if n == 0.0 {
112                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
113                                false,
114                            )));
115                        }
116                    }
117                    LiteralValue::Int(i) => {
118                        if i == 0 {
119                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
120                                false,
121                            )));
122                        }
123                    }
124                    _ => {
125                        // Non-coercible (e.g., Text) → #VALUE! candidate with message
126                        if first_error.is_none() {
127                            first_error =
128                                Some(LiteralValue::Error(ExcelError::new_value().with_message(
129                                    "AND expects logical/numeric inputs; text is not coercible",
130                                )));
131                        }
132                    }
133                }
134            }
135        }
136        if let Some(err) = first_error {
137            return Ok(crate::traits::CalcValue::Scalar(err));
138        }
139        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
140            true,
141        )))
142    }
143}
144
145/* ─────────────────────────── OR() ───────────────────────────────── */
146
147#[derive(Debug)]
148pub struct OrFn;
149
150impl Function for OrFn {
151    func_caps!(PURE, REDUCTION, BOOL_ONLY, SHORT_CIRCUIT);
152
153    fn name(&self) -> &'static str {
154        "OR"
155    }
156    fn min_args(&self) -> usize {
157        1
158    }
159    fn variadic(&self) -> bool {
160        true
161    }
162    fn arg_schema(&self) -> &'static [ArgSchema] {
163        &ARG_ANY_ONE[..]
164    }
165
166    fn eval<'a, 'b, 'c>(
167        &self,
168        args: &'c [ArgumentHandle<'a, 'b>],
169        _ctx: &dyn FunctionContext<'b>,
170    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
171        let mut first_error: Option<LiteralValue> = None;
172        for h in args {
173            let it = h.lazy_values_owned()?;
174            for v in it {
175                match v {
176                    LiteralValue::Error(_) => {
177                        if first_error.is_none() {
178                            first_error = Some(v);
179                        }
180                    }
181                    LiteralValue::Empty => {
182                        // ignored
183                    }
184                    LiteralValue::Boolean(b) => {
185                        if b {
186                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
187                                true,
188                            )));
189                        }
190                    }
191                    LiteralValue::Number(n) => {
192                        if n != 0.0 {
193                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
194                                true,
195                            )));
196                        }
197                    }
198                    LiteralValue::Int(i) => {
199                        if i != 0 {
200                            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
201                                true,
202                            )));
203                        }
204                    }
205                    _ => {
206                        // Non-coercible → #VALUE! candidate with message
207                        if first_error.is_none() {
208                            first_error =
209                                Some(LiteralValue::Error(ExcelError::new_value().with_message(
210                                    "OR expects logical/numeric inputs; text is not coercible",
211                                )));
212                        }
213                    }
214                }
215            }
216        }
217        if let Some(err) = first_error {
218            return Ok(crate::traits::CalcValue::Scalar(err));
219        }
220        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
221            false,
222        )))
223    }
224}
225
226/* ─────────────────────────── IF() ───────────────────────────────── */
227
228#[derive(Debug)]
229pub struct IfFn;
230
231impl Function for IfFn {
232    func_caps!(PURE, SHORT_CIRCUIT);
233
234    fn name(&self) -> &'static str {
235        "IF"
236    }
237    fn min_args(&self) -> usize {
238        2
239    }
240    fn variadic(&self) -> bool {
241        true
242    }
243
244    fn arg_schema(&self) -> &'static [ArgSchema] {
245        use std::sync::LazyLock;
246        // Single variadic any schema so we can enforce precise 2 or 3 arity inside eval()
247        static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
248        &ONE[..]
249    }
250
251    fn eval<'a, 'b, 'c>(
252        &self,
253        args: &'c [ArgumentHandle<'a, 'b>],
254        _ctx: &dyn FunctionContext<'b>,
255    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
256        if args.len() < 2 || args.len() > 3 {
257            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
258                ExcelError::new_value()
259                    .with_message(format!("IF expects 2 or 3 arguments, got {}", args.len())),
260            )));
261        }
262
263        let condition = args[0].value()?.into_literal();
264        let b = match condition {
265            LiteralValue::Boolean(b) => b,
266            LiteralValue::Number(n) => n != 0.0,
267            LiteralValue::Int(i) => i != 0,
268            LiteralValue::Empty => false,
269            _ => {
270                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
271                    ExcelError::new_value().with_message("IF condition must be boolean or number"),
272                )));
273            }
274        };
275
276        if b {
277            args[1].value()
278        } else if let Some(arg) = args.get(2) {
279            arg.value()
280        } else {
281            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
282                false,
283            )))
284        }
285    }
286}
287
288pub fn register_builtins() {
289    crate::function_registry::register_function(std::sync::Arc::new(TrueFn));
290    crate::function_registry::register_function(std::sync::Arc::new(FalseFn));
291    crate::function_registry::register_function(std::sync::Arc::new(AndFn));
292    crate::function_registry::register_function(std::sync::Arc::new(OrFn));
293    crate::function_registry::register_function(std::sync::Arc::new(IfFn));
294}
295
296/* ─────────────────────────── tests ─────────────────────────────── */
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use crate::traits::ArgumentHandle;
302    use crate::{interpreter::Interpreter, test_workbook::TestWorkbook};
303    use formualizer_parse::LiteralValue;
304    use std::sync::{
305        Arc,
306        atomic::{AtomicUsize, Ordering},
307    };
308
309    #[derive(Debug)]
310    struct CountFn(Arc<AtomicUsize>);
311    impl Function for CountFn {
312        func_caps!(PURE);
313        fn name(&self) -> &'static str {
314            "COUNTING"
315        }
316        fn min_args(&self) -> usize {
317            0
318        }
319        fn eval<'a, 'b, 'c>(
320            &self,
321            _args: &'c [ArgumentHandle<'a, 'b>],
322            _ctx: &dyn FunctionContext<'b>,
323        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
324            self.0.fetch_add(1, Ordering::SeqCst);
325            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
326                true,
327            )))
328        }
329    }
330
331    #[derive(Debug)]
332    struct ErrorFn(Arc<AtomicUsize>);
333    impl Function for ErrorFn {
334        func_caps!(PURE);
335        fn name(&self) -> &'static str {
336            "ERRORFN"
337        }
338        fn min_args(&self) -> usize {
339            0
340        }
341        fn eval<'a, 'b, 'c>(
342            &self,
343            _args: &'c [ArgumentHandle<'a, 'b>],
344            _ctx: &dyn FunctionContext<'b>,
345        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
346            self.0.fetch_add(1, Ordering::SeqCst);
347            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
348                ExcelError::new_value(),
349            )))
350        }
351    }
352
353    fn interp(wb: &TestWorkbook) -> Interpreter<'_> {
354        wb.interpreter()
355    }
356
357    #[test]
358    fn test_true_false() {
359        let wb = TestWorkbook::new()
360            .with_function(std::sync::Arc::new(TrueFn))
361            .with_function(std::sync::Arc::new(FalseFn));
362
363        let ctx = interp(&wb);
364        let t = ctx.context.get_function("", "TRUE").unwrap();
365        let fctx = ctx.function_context(None);
366        assert_eq!(
367            t.eval(&[], &fctx).unwrap().into_literal(),
368            LiteralValue::Boolean(true)
369        );
370
371        let f = ctx.context.get_function("", "FALSE").unwrap();
372        assert_eq!(
373            f.eval(&[], &fctx).unwrap().into_literal(),
374            LiteralValue::Boolean(false)
375        );
376    }
377
378    #[test]
379    fn test_and_or() {
380        let wb = TestWorkbook::new()
381            .with_function(std::sync::Arc::new(AndFn))
382            .with_function(std::sync::Arc::new(OrFn));
383        let ctx = interp(&wb);
384        let fctx = ctx.function_context(None);
385
386        let and = ctx.context.get_function("", "AND").unwrap();
387        let or = ctx.context.get_function("", "OR").unwrap();
388        // Build ArgumentHandles manually: TRUE, 1, FALSE
389        let dummy_ast = formualizer_parse::parser::ASTNode::new(
390            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(true)),
391            None,
392        );
393        let dummy_ast_false = formualizer_parse::parser::ASTNode::new(
394            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(false)),
395            None,
396        );
397        let dummy_ast_one = formualizer_parse::parser::ASTNode::new(
398            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(1)),
399            None,
400        );
401        let hs = vec![
402            ArgumentHandle::new(&dummy_ast, &ctx),
403            ArgumentHandle::new(&dummy_ast_one, &ctx),
404        ];
405        assert_eq!(
406            and.eval(&hs, &fctx).unwrap().into_literal(),
407            LiteralValue::Boolean(true)
408        );
409
410        let hs2 = vec![
411            ArgumentHandle::new(&dummy_ast_false, &ctx),
412            ArgumentHandle::new(&dummy_ast_one, &ctx),
413        ];
414        assert_eq!(
415            and.eval(&hs2, &fctx).unwrap().into_literal(),
416            LiteralValue::Boolean(false)
417        );
418        assert_eq!(
419            or.eval(&hs2, &fctx).unwrap().into_literal(),
420            LiteralValue::Boolean(true)
421        );
422    }
423
424    #[test]
425    fn and_short_circuits_on_false_without_evaluating_rest() {
426        let counter = Arc::new(AtomicUsize::new(0));
427        let wb = TestWorkbook::new()
428            .with_function(Arc::new(AndFn))
429            .with_function(Arc::new(CountFn(counter.clone())));
430        let ctx = interp(&wb);
431        let fctx = ctx.function_context(None);
432        let and = ctx.context.get_function("", "AND").unwrap();
433
434        // Build args: FALSE, COUNTING()
435        let a_false = formualizer_parse::parser::ASTNode::new(
436            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(false)),
437            None,
438        );
439        let counting_call = formualizer_parse::parser::ASTNode::new(
440            formualizer_parse::parser::ASTNodeType::Function {
441                name: "COUNTING".into(),
442                args: vec![],
443            },
444            None,
445        );
446        let hs = vec![
447            ArgumentHandle::new(&a_false, &ctx),
448            ArgumentHandle::new(&counting_call, &ctx),
449        ];
450        let out = and.eval(&hs, &fctx).unwrap().into_literal();
451        assert_eq!(out, LiteralValue::Boolean(false));
452        assert_eq!(
453            counter.load(Ordering::SeqCst),
454            0,
455            "COUNTING should not be evaluated"
456        );
457    }
458
459    #[test]
460    fn or_short_circuits_on_true_without_evaluating_rest() {
461        let counter = Arc::new(AtomicUsize::new(0));
462        let wb = TestWorkbook::new()
463            .with_function(Arc::new(OrFn))
464            .with_function(Arc::new(CountFn(counter.clone())));
465        let ctx = interp(&wb);
466        let fctx = ctx.function_context(None);
467        let or = ctx.context.get_function("", "OR").unwrap();
468
469        // Build args: TRUE, COUNTING()
470        let a_true = formualizer_parse::parser::ASTNode::new(
471            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(true)),
472            None,
473        );
474        let counting_call = formualizer_parse::parser::ASTNode::new(
475            formualizer_parse::parser::ASTNodeType::Function {
476                name: "COUNTING".into(),
477                args: vec![],
478            },
479            None,
480        );
481        let hs = vec![
482            ArgumentHandle::new(&a_true, &ctx),
483            ArgumentHandle::new(&counting_call, &ctx),
484        ];
485        let out = or.eval(&hs, &fctx).unwrap().into_literal();
486        assert_eq!(out, LiteralValue::Boolean(true));
487        assert_eq!(
488            counter.load(Ordering::SeqCst),
489            0,
490            "COUNTING should not be evaluated"
491        );
492    }
493
494    #[test]
495    fn or_range_arg_short_circuits_on_first_true_before_evaluating_next_arg() {
496        let counter = Arc::new(AtomicUsize::new(0));
497        let wb = TestWorkbook::new()
498            .with_function(Arc::new(OrFn))
499            .with_function(Arc::new(CountFn(counter.clone())));
500        let ctx = interp(&wb);
501        let fctx = ctx.function_context(None);
502        let or = ctx.context.get_function("", "OR").unwrap();
503
504        // First arg is an array literal with first element 1 (truey), then zeros.
505        let arr = formualizer_parse::parser::ASTNode::new(
506            formualizer_parse::parser::ASTNodeType::Array(vec![
507                vec![formualizer_parse::parser::ASTNode::new(
508                    formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(1)),
509                    None,
510                )],
511                vec![formualizer_parse::parser::ASTNode::new(
512                    formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(0)),
513                    None,
514                )],
515            ]),
516            None,
517        );
518        let counting_call = formualizer_parse::parser::ASTNode::new(
519            formualizer_parse::parser::ASTNodeType::Function {
520                name: "COUNTING".into(),
521                args: vec![],
522            },
523            None,
524        );
525        let hs = vec![
526            ArgumentHandle::new(&arr, &ctx),
527            ArgumentHandle::new(&counting_call, &ctx),
528        ];
529        let out = or.eval(&hs, &fctx).unwrap().into_literal();
530        assert_eq!(out, LiteralValue::Boolean(true));
531        assert_eq!(
532            counter.load(Ordering::SeqCst),
533            0,
534            "COUNTING should not be evaluated"
535        );
536    }
537
538    #[test]
539    fn and_returns_first_error_when_no_decisive_false() {
540        let err_counter = Arc::new(AtomicUsize::new(0));
541        let wb = TestWorkbook::new()
542            .with_function(Arc::new(AndFn))
543            .with_function(Arc::new(ErrorFn(err_counter.clone())));
544        let ctx = interp(&wb);
545        let fctx = ctx.function_context(None);
546        let and = ctx.context.get_function("", "AND").unwrap();
547
548        // AND(1, ERRORFN(), 1) => #VALUE!
549        let one = formualizer_parse::parser::ASTNode::new(
550            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(1)),
551            None,
552        );
553        let errcall = formualizer_parse::parser::ASTNode::new(
554            formualizer_parse::parser::ASTNodeType::Function {
555                name: "ERRORFN".into(),
556                args: vec![],
557            },
558            None,
559        );
560        let hs = vec![
561            ArgumentHandle::new(&one, &ctx),
562            ArgumentHandle::new(&errcall, &ctx),
563            ArgumentHandle::new(&one, &ctx),
564        ];
565        let out = and.eval(&hs, &fctx).unwrap().into_literal();
566        match out {
567            LiteralValue::Error(e) => assert_eq!(e.to_string(), "#VALUE!"),
568            _ => panic!("Expected error"),
569        }
570        assert_eq!(
571            err_counter.load(Ordering::SeqCst),
572            1,
573            "ERRORFN should be evaluated once"
574        );
575    }
576
577    #[test]
578    fn or_does_not_evaluate_error_after_true() {
579        let err_counter = Arc::new(AtomicUsize::new(0));
580        let wb = TestWorkbook::new()
581            .with_function(Arc::new(OrFn))
582            .with_function(Arc::new(ErrorFn(err_counter.clone())));
583        let ctx = interp(&wb);
584        let fctx = ctx.function_context(None);
585        let or = ctx.context.get_function("", "OR").unwrap();
586
587        // OR(TRUE, ERRORFN()) => TRUE and ERRORFN not evaluated
588        let a_true = formualizer_parse::parser::ASTNode::new(
589            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Boolean(true)),
590            None,
591        );
592        let errcall = formualizer_parse::parser::ASTNode::new(
593            formualizer_parse::parser::ASTNodeType::Function {
594                name: "ERRORFN".into(),
595                args: vec![],
596            },
597            None,
598        );
599        let hs = vec![
600            ArgumentHandle::new(&a_true, &ctx),
601            ArgumentHandle::new(&errcall, &ctx),
602        ];
603        let out = or.eval(&hs, &fctx).unwrap().into_literal();
604        assert_eq!(out, LiteralValue::Boolean(true));
605        assert_eq!(
606            err_counter.load(Ordering::SeqCst),
607            0,
608            "ERRORFN should not be evaluated"
609        );
610    }
611
612    #[test]
613    fn if_treats_empty_condition_as_false() {
614        let wb = TestWorkbook::new().with_function(Arc::new(IfFn));
615        let ctx = interp(&wb);
616        let fctx = ctx.function_context(None);
617        let iff = ctx.context.get_function("", "IF").unwrap();
618
619        let cond_empty = formualizer_parse::parser::ASTNode::new(
620            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Empty),
621            None,
622        );
623        let when_true = formualizer_parse::parser::ASTNode::new(
624            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(10)),
625            None,
626        );
627        let when_false = formualizer_parse::parser::ASTNode::new(
628            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Int(20)),
629            None,
630        );
631
632        let args = vec![
633            ArgumentHandle::new(&cond_empty, &ctx),
634            ArgumentHandle::new(&when_true, &ctx),
635            ArgumentHandle::new(&when_false, &ctx),
636        ];
637
638        assert_eq!(
639            iff.eval(&args, &fctx).unwrap().into_literal(),
640            LiteralValue::Int(20)
641        );
642    }
643}