Skip to main content

formualizer_eval/builtins/
logical_ext.rs

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/* Additional logical & error-handling functions: NOT, XOR, IFERROR, IFNA, IFS */
9
10#[derive(Debug)]
11pub struct NotFn;
12impl Function for NotFn {
13    func_caps!(PURE);
14    fn name(&self) -> &'static str {
15        "NOT"
16    }
17    fn min_args(&self) -> usize {
18        1
19    }
20    fn arg_schema(&self) -> &'static [ArgSchema] {
21        &ARG_ANY_ONE[..]
22    }
23    fn eval<'a, 'b, 'c>(
24        &self,
25        args: &'c [ArgumentHandle<'a, 'b>],
26        _ctx: &dyn FunctionContext<'b>,
27    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
28        if args.len() != 1 {
29            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
30                ExcelError::new_value(),
31            )));
32        }
33        let v = args[0].value()?.into_literal();
34        let b = match v {
35            LiteralValue::Boolean(b) => !b,
36            LiteralValue::Number(n) => n == 0.0,
37            LiteralValue::Int(i) => i == 0,
38            LiteralValue::Empty => true,
39            LiteralValue::Error(e) => {
40                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
41            }
42            _ => {
43                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
44                    ExcelError::new_value(),
45                )));
46            }
47        };
48        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(b)))
49    }
50}
51
52#[derive(Debug)]
53pub struct XorFn;
54impl Function for XorFn {
55    func_caps!(PURE, REDUCTION, BOOL_ONLY);
56    fn name(&self) -> &'static str {
57        "XOR"
58    }
59    fn min_args(&self) -> usize {
60        1
61    }
62    fn variadic(&self) -> bool {
63        true
64    }
65    fn arg_schema(&self) -> &'static [ArgSchema] {
66        &ARG_ANY_ONE[..]
67    }
68    fn eval<'a, 'b, 'c>(
69        &self,
70        args: &'c [ArgumentHandle<'a, 'b>],
71        _ctx: &dyn FunctionContext<'b>,
72    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
73        let mut true_count = 0usize;
74        let mut first_error: Option<LiteralValue> = None;
75        for a in args {
76            if let Ok(view) = a.range_view() {
77                let mut err: Option<LiteralValue> = None;
78                view.for_each_cell(&mut |val| {
79                    match val {
80                        LiteralValue::Boolean(b) => {
81                            if *b {
82                                true_count += 1;
83                            }
84                        }
85                        LiteralValue::Number(n) => {
86                            if *n != 0.0 {
87                                true_count += 1;
88                            }
89                        }
90                        LiteralValue::Int(i) => {
91                            if *i != 0 {
92                                true_count += 1;
93                            }
94                        }
95                        LiteralValue::Empty => {}
96                        LiteralValue::Error(_) => {
97                            if first_error.is_none() {
98                                err = Some(val.clone());
99                            }
100                        }
101                        _ => {
102                            if first_error.is_none() {
103                                err = Some(LiteralValue::Error(ExcelError::from_error_string(
104                                    "#VALUE!",
105                                )));
106                            }
107                        }
108                    }
109                    Ok(())
110                })?;
111                if first_error.is_none() {
112                    first_error = err;
113                }
114            } else {
115                let v = a.value()?.into_literal();
116                match v {
117                    LiteralValue::Boolean(b) => {
118                        if b {
119                            true_count += 1;
120                        }
121                    }
122                    LiteralValue::Number(n) => {
123                        if n != 0.0 {
124                            true_count += 1;
125                        }
126                    }
127                    LiteralValue::Int(i) => {
128                        if i != 0 {
129                            true_count += 1;
130                        }
131                    }
132                    LiteralValue::Empty => {}
133                    LiteralValue::Error(e) => {
134                        if first_error.is_none() {
135                            first_error = Some(LiteralValue::Error(e));
136                        }
137                    }
138                    _ => {
139                        if first_error.is_none() {
140                            first_error = Some(LiteralValue::Error(ExcelError::from_error_string(
141                                "#VALUE!",
142                            )));
143                        }
144                    }
145                }
146            }
147        }
148        if let Some(err) = first_error {
149            return Ok(crate::traits::CalcValue::Scalar(err));
150        }
151        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
152            true_count % 2 == 1,
153        )))
154    }
155}
156
157#[derive(Debug)]
158pub struct IfErrorFn; // IFERROR(value, fallback)
159impl Function for IfErrorFn {
160    func_caps!(PURE);
161    fn name(&self) -> &'static str {
162        "IFERROR"
163    }
164    fn min_args(&self) -> usize {
165        2
166    }
167    fn variadic(&self) -> bool {
168        false
169    }
170    fn arg_schema(&self) -> &'static [ArgSchema] {
171        use std::sync::LazyLock;
172        // value, fallback (any scalar)
173        static TWO: LazyLock<Vec<ArgSchema>> =
174            LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
175        &TWO[..]
176    }
177    fn eval<'a, 'b, 'c>(
178        &self,
179        args: &'c [ArgumentHandle<'a, 'b>],
180        _ctx: &dyn FunctionContext<'b>,
181    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
182        if args.len() != 2 {
183            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
184                ExcelError::new_value(),
185            )));
186        }
187        let v = args[0].value()?.into_literal();
188        match v {
189            LiteralValue::Error(_) => args[1].value(),
190            other => Ok(crate::traits::CalcValue::Scalar(other)),
191        }
192    }
193}
194
195#[derive(Debug)]
196pub struct IfNaFn; // IFNA(value, fallback)
197impl Function for IfNaFn {
198    func_caps!(PURE);
199    fn name(&self) -> &'static str {
200        "IFNA"
201    }
202    fn min_args(&self) -> usize {
203        2
204    }
205    fn variadic(&self) -> bool {
206        false
207    }
208    fn arg_schema(&self) -> &'static [ArgSchema] {
209        use std::sync::LazyLock;
210        static TWO: LazyLock<Vec<ArgSchema>> =
211            LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
212        &TWO[..]
213    }
214    fn eval<'a, 'b, 'c>(
215        &self,
216        args: &'c [ArgumentHandle<'a, 'b>],
217        _ctx: &dyn FunctionContext<'b>,
218    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
219        if args.len() != 2 {
220            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
221                ExcelError::new_value(),
222            )));
223        }
224        let v = args[0].value()?.into_literal();
225        match v {
226            LiteralValue::Error(ref e) if e.kind == formualizer_common::ExcelErrorKind::Na => {
227                args[1].value()
228            }
229            other => Ok(crate::traits::CalcValue::Scalar(other)),
230        }
231    }
232}
233
234#[derive(Debug)]
235pub struct IfsFn; // IFS(cond1, val1, cond2, val2, ...)
236impl Function for IfsFn {
237    func_caps!(PURE, SHORT_CIRCUIT);
238    fn name(&self) -> &'static str {
239        "IFS"
240    }
241    fn min_args(&self) -> usize {
242        2
243    }
244    fn variadic(&self) -> bool {
245        true
246    }
247    fn arg_schema(&self) -> &'static [ArgSchema] {
248        &ARG_ANY_ONE[..]
249    }
250    fn eval<'a, 'b, 'c>(
251        &self,
252        args: &'c [ArgumentHandle<'a, 'b>],
253        _ctx: &dyn FunctionContext<'b>,
254    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
255        if args.len() < 2 || !args.len().is_multiple_of(2) {
256            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
257                ExcelError::new_value(),
258            )));
259        }
260        for pair in args.chunks(2) {
261            let cond = pair[0].value()?.into_literal();
262            let is_true = match cond {
263                LiteralValue::Boolean(b) => b,
264                LiteralValue::Number(n) => n != 0.0,
265                LiteralValue::Int(i) => i != 0,
266                LiteralValue::Empty => false,
267                LiteralValue::Error(e) => {
268                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
269                }
270                _ => {
271                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
272                        ExcelError::from_error_string("#VALUE!"),
273                    )));
274                }
275            };
276            if is_true {
277                return pair[1].value();
278            }
279        }
280        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
281            ExcelError::new_na(),
282        )))
283    }
284}
285
286pub fn register_builtins() {
287    use std::sync::Arc;
288    crate::function_registry::register_function(Arc::new(NotFn));
289    crate::function_registry::register_function(Arc::new(XorFn));
290    crate::function_registry::register_function(Arc::new(IfErrorFn));
291    crate::function_registry::register_function(Arc::new(IfNaFn));
292    crate::function_registry::register_function(Arc::new(IfsFn));
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use crate::test_workbook::TestWorkbook;
299    use crate::traits::ArgumentHandle;
300    use formualizer_common::LiteralValue;
301    use formualizer_parse::parser::{ASTNode, ASTNodeType};
302
303    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
304        wb.interpreter()
305    }
306
307    #[test]
308    fn not_basic() {
309        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
310        let ctx = interp(&wb);
311        let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
312        let args = vec![ArgumentHandle::new(&t, &ctx)];
313        let f = ctx.context.get_function("", "NOT").unwrap();
314        assert_eq!(
315            f.dispatch(&args, &ctx.function_context(None))
316                .unwrap()
317                .into_literal(),
318            LiteralValue::Boolean(false)
319        );
320    }
321
322    #[test]
323    fn xor_range_and_scalars() {
324        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
325        let ctx = interp(&wb);
326        let arr = ASTNode::new(
327            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
328                LiteralValue::Int(1),
329                LiteralValue::Int(0),
330                LiteralValue::Int(2),
331            ]])),
332            None,
333        );
334        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
335        let args = vec![
336            ArgumentHandle::new(&arr, &ctx),
337            ArgumentHandle::new(&zero, &ctx),
338        ];
339        let f = ctx.context.get_function("", "XOR").unwrap();
340        // 1,true,true -> 2 trues => even => FALSE
341        assert_eq!(
342            f.dispatch(&args, &ctx.function_context(None))
343                .unwrap()
344                .into_literal(),
345            LiteralValue::Boolean(false)
346        );
347    }
348
349    #[test]
350    fn iferror_fallback() {
351        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
352        let ctx = interp(&wb);
353        let err = ASTNode::new(
354            ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
355                "#DIV/0!",
356            ))),
357            None,
358        );
359        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
360        let args = vec![
361            ArgumentHandle::new(&err, &ctx),
362            ArgumentHandle::new(&fb, &ctx),
363        ];
364        let f = ctx.context.get_function("", "IFERROR").unwrap();
365        assert_eq!(
366            f.dispatch(&args, &ctx.function_context(None))
367                .unwrap()
368                .into_literal(),
369            LiteralValue::Int(5)
370        );
371    }
372
373    #[test]
374    fn iferror_passthrough_non_error() {
375        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
376        let ctx = interp(&wb);
377        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(11)), None);
378        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
379        let args = vec![
380            ArgumentHandle::new(&val, &ctx),
381            ArgumentHandle::new(&fb, &ctx),
382        ];
383        let f = ctx.context.get_function("", "IFERROR").unwrap();
384        assert_eq!(
385            f.dispatch(&args, &ctx.function_context(None))
386                .unwrap()
387                .into_literal(),
388            LiteralValue::Int(11)
389        );
390    }
391
392    #[test]
393    fn ifna_only_handles_na() {
394        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
395        let ctx = interp(&wb);
396        let na = ASTNode::new(
397            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
398            None,
399        );
400        let other_err = ASTNode::new(
401            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
402            None,
403        );
404        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(7)), None);
405        let args_na = vec![
406            ArgumentHandle::new(&na, &ctx),
407            ArgumentHandle::new(&fb, &ctx),
408        ];
409        let args_val = vec![
410            ArgumentHandle::new(&other_err, &ctx),
411            ArgumentHandle::new(&fb, &ctx),
412        ];
413        let f = ctx.context.get_function("", "IFNA").unwrap();
414        assert_eq!(
415            f.dispatch(&args_na, &ctx.function_context(None))
416                .unwrap()
417                .into_literal(),
418            LiteralValue::Int(7)
419        );
420        match f
421            .dispatch(&args_val, &ctx.function_context(None))
422            .unwrap()
423            .into_literal()
424        {
425            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
426            _ => panic!(),
427        }
428    }
429
430    #[test]
431    fn ifna_value_passthrough() {
432        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
433        let ctx = interp(&wb);
434        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(22)), None);
435        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
436        let args = vec![
437            ArgumentHandle::new(&val, &ctx),
438            ArgumentHandle::new(&fb, &ctx),
439        ];
440        let f = ctx.context.get_function("", "IFNA").unwrap();
441        assert_eq!(
442            f.dispatch(&args, &ctx.function_context(None))
443                .unwrap()
444                .into_literal(),
445            LiteralValue::Int(22)
446        );
447    }
448
449    #[test]
450    fn ifs_short_circuits() {
451        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
452        let ctx = interp(&wb);
453        let cond_true = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
454        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
455        let cond_false = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
456        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
457        let args = vec![
458            ArgumentHandle::new(&cond_true, &ctx),
459            ArgumentHandle::new(&val1, &ctx),
460            ArgumentHandle::new(&cond_false, &ctx),
461            ArgumentHandle::new(&val2, &ctx),
462        ];
463        let f = ctx.context.get_function("", "IFS").unwrap();
464        assert_eq!(
465            f.dispatch(&args, &ctx.function_context(None))
466                .unwrap()
467                .into_literal(),
468            LiteralValue::Int(9)
469        );
470    }
471
472    #[test]
473    fn ifs_no_match_returns_na_error() {
474        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
475        let ctx = interp(&wb);
476        let cond_false1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
477        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
478        let cond_false2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
479        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
480        let args = vec![
481            ArgumentHandle::new(&cond_false1, &ctx),
482            ArgumentHandle::new(&val1, &ctx),
483            ArgumentHandle::new(&cond_false2, &ctx),
484            ArgumentHandle::new(&val2, &ctx),
485        ];
486        let f = ctx.context.get_function("", "IFS").unwrap();
487        match f
488            .dispatch(&args, &ctx.function_context(None))
489            .unwrap()
490            .into_literal()
491        {
492            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
493            other => panic!("expected #N/A got {other:?}"),
494        }
495    }
496
497    #[test]
498    fn not_number_zero_and_nonzero() {
499        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
500        let ctx = interp(&wb);
501        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
502        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
503        let f = ctx.context.get_function("", "NOT").unwrap();
504        assert_eq!(
505            f.dispatch(
506                &[ArgumentHandle::new(&zero, &ctx)],
507                &ctx.function_context(None)
508            )
509            .unwrap()
510            .into_literal(),
511            LiteralValue::Boolean(true)
512        );
513        assert_eq!(
514            f.dispatch(
515                &[ArgumentHandle::new(&one, &ctx)],
516                &ctx.function_context(None)
517            )
518            .unwrap()
519            .into_literal(),
520            LiteralValue::Boolean(false)
521        );
522    }
523
524    #[test]
525    fn xor_error_propagation() {
526        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
527        let ctx = interp(&wb);
528        let err = ASTNode::new(
529            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
530            None,
531        );
532        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
533        let f = ctx.context.get_function("", "XOR").unwrap();
534        match f
535            .dispatch(
536                &[
537                    ArgumentHandle::new(&err, &ctx),
538                    ArgumentHandle::new(&one, &ctx),
539                ],
540                &ctx.function_context(None),
541            )
542            .unwrap()
543            .into_literal()
544        {
545            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
546            _ => panic!("expected value error"),
547        }
548    }
549}