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        match args[0].value() {
188            Ok(cv) => match cv.into_literal() {
189                LiteralValue::Error(_) => args[1].value(),
190                other => Ok(crate::traits::CalcValue::Scalar(other)),
191            },
192            Err(_) => args[1].value(),
193        }
194    }
195}
196
197#[derive(Debug)]
198pub struct IfNaFn; // IFNA(value, fallback)
199impl Function for IfNaFn {
200    func_caps!(PURE);
201    fn name(&self) -> &'static str {
202        "IFNA"
203    }
204    fn min_args(&self) -> usize {
205        2
206    }
207    fn variadic(&self) -> bool {
208        false
209    }
210    fn arg_schema(&self) -> &'static [ArgSchema] {
211        use std::sync::LazyLock;
212        static TWO: LazyLock<Vec<ArgSchema>> =
213            LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any()]);
214        &TWO[..]
215    }
216    fn eval<'a, 'b, 'c>(
217        &self,
218        args: &'c [ArgumentHandle<'a, 'b>],
219        _ctx: &dyn FunctionContext<'b>,
220    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
221        if args.len() != 2 {
222            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
223                ExcelError::new_value(),
224            )));
225        }
226        let v = args[0].value()?.into_literal();
227        match v {
228            LiteralValue::Error(ref e) if e.kind == formualizer_common::ExcelErrorKind::Na => {
229                args[1].value()
230            }
231            other => Ok(crate::traits::CalcValue::Scalar(other)),
232        }
233    }
234}
235
236#[derive(Debug)]
237pub struct IfsFn; // IFS(cond1, val1, cond2, val2, ...)
238impl Function for IfsFn {
239    func_caps!(PURE, SHORT_CIRCUIT);
240    fn name(&self) -> &'static str {
241        "IFS"
242    }
243    fn min_args(&self) -> usize {
244        2
245    }
246    fn variadic(&self) -> bool {
247        true
248    }
249    fn arg_schema(&self) -> &'static [ArgSchema] {
250        &ARG_ANY_ONE[..]
251    }
252    fn eval<'a, 'b, 'c>(
253        &self,
254        args: &'c [ArgumentHandle<'a, 'b>],
255        _ctx: &dyn FunctionContext<'b>,
256    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
257        if args.len() < 2 || !args.len().is_multiple_of(2) {
258            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
259                ExcelError::new_value(),
260            )));
261        }
262        for pair in args.chunks(2) {
263            let cond = pair[0].value()?.into_literal();
264            let is_true = match cond {
265                LiteralValue::Boolean(b) => b,
266                LiteralValue::Number(n) => n != 0.0,
267                LiteralValue::Int(i) => i != 0,
268                LiteralValue::Empty => false,
269                LiteralValue::Error(e) => {
270                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
271                }
272                _ => {
273                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
274                        ExcelError::from_error_string("#VALUE!"),
275                    )));
276                }
277            };
278            if is_true {
279                return pair[1].value();
280            }
281        }
282        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
283            ExcelError::new_na(),
284        )))
285    }
286}
287
288pub fn register_builtins() {
289    use std::sync::Arc;
290    crate::function_registry::register_function(Arc::new(NotFn));
291    crate::function_registry::register_function(Arc::new(XorFn));
292    crate::function_registry::register_function(Arc::new(IfErrorFn));
293    crate::function_registry::register_function(Arc::new(IfNaFn));
294    crate::function_registry::register_function(Arc::new(IfsFn));
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::test_workbook::TestWorkbook;
301    use crate::traits::ArgumentHandle;
302    use formualizer_common::LiteralValue;
303    use formualizer_parse::parser::{ASTNode, ASTNodeType};
304
305    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
306        wb.interpreter()
307    }
308
309    #[test]
310    fn not_basic() {
311        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
312        let ctx = interp(&wb);
313        let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
314        let args = vec![ArgumentHandle::new(&t, &ctx)];
315        let f = ctx.context.get_function("", "NOT").unwrap();
316        assert_eq!(
317            f.dispatch(&args, &ctx.function_context(None))
318                .unwrap()
319                .into_literal(),
320            LiteralValue::Boolean(false)
321        );
322    }
323
324    #[test]
325    fn xor_range_and_scalars() {
326        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
327        let ctx = interp(&wb);
328        let arr = ASTNode::new(
329            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
330                LiteralValue::Int(1),
331                LiteralValue::Int(0),
332                LiteralValue::Int(2),
333            ]])),
334            None,
335        );
336        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
337        let args = vec![
338            ArgumentHandle::new(&arr, &ctx),
339            ArgumentHandle::new(&zero, &ctx),
340        ];
341        let f = ctx.context.get_function("", "XOR").unwrap();
342        // 1,true,true -> 2 trues => even => FALSE
343        assert_eq!(
344            f.dispatch(&args, &ctx.function_context(None))
345                .unwrap()
346                .into_literal(),
347            LiteralValue::Boolean(false)
348        );
349    }
350
351    #[test]
352    fn iferror_fallback() {
353        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
354        let ctx = interp(&wb);
355        let err = ASTNode::new(
356            ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
357                "#DIV/0!",
358            ))),
359            None,
360        );
361        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
362        let args = vec![
363            ArgumentHandle::new(&err, &ctx),
364            ArgumentHandle::new(&fb, &ctx),
365        ];
366        let f = ctx.context.get_function("", "IFERROR").unwrap();
367        assert_eq!(
368            f.dispatch(&args, &ctx.function_context(None))
369                .unwrap()
370                .into_literal(),
371            LiteralValue::Int(5)
372        );
373    }
374
375    #[test]
376    fn iferror_passthrough_non_error() {
377        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfErrorFn));
378        let ctx = interp(&wb);
379        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(11)), None);
380        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
381        let args = vec![
382            ArgumentHandle::new(&val, &ctx),
383            ArgumentHandle::new(&fb, &ctx),
384        ];
385        let f = ctx.context.get_function("", "IFERROR").unwrap();
386        assert_eq!(
387            f.dispatch(&args, &ctx.function_context(None))
388                .unwrap()
389                .into_literal(),
390            LiteralValue::Int(11)
391        );
392    }
393
394    #[test]
395    fn ifna_only_handles_na() {
396        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
397        let ctx = interp(&wb);
398        let na = ASTNode::new(
399            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
400            None,
401        );
402        let other_err = ASTNode::new(
403            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
404            None,
405        );
406        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(7)), None);
407        let args_na = vec![
408            ArgumentHandle::new(&na, &ctx),
409            ArgumentHandle::new(&fb, &ctx),
410        ];
411        let args_val = vec![
412            ArgumentHandle::new(&other_err, &ctx),
413            ArgumentHandle::new(&fb, &ctx),
414        ];
415        let f = ctx.context.get_function("", "IFNA").unwrap();
416        assert_eq!(
417            f.dispatch(&args_na, &ctx.function_context(None))
418                .unwrap()
419                .into_literal(),
420            LiteralValue::Int(7)
421        );
422        match f
423            .dispatch(&args_val, &ctx.function_context(None))
424            .unwrap()
425            .into_literal()
426        {
427            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
428            _ => panic!(),
429        }
430    }
431
432    #[test]
433    fn ifna_value_passthrough() {
434        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfNaFn));
435        let ctx = interp(&wb);
436        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(22)), None);
437        let fb = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
438        let args = vec![
439            ArgumentHandle::new(&val, &ctx),
440            ArgumentHandle::new(&fb, &ctx),
441        ];
442        let f = ctx.context.get_function("", "IFNA").unwrap();
443        assert_eq!(
444            f.dispatch(&args, &ctx.function_context(None))
445                .unwrap()
446                .into_literal(),
447            LiteralValue::Int(22)
448        );
449    }
450
451    #[test]
452    fn ifs_short_circuits() {
453        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
454        let ctx = interp(&wb);
455        let cond_true = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
456        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
457        let cond_false = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
458        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
459        let args = vec![
460            ArgumentHandle::new(&cond_true, &ctx),
461            ArgumentHandle::new(&val1, &ctx),
462            ArgumentHandle::new(&cond_false, &ctx),
463            ArgumentHandle::new(&val2, &ctx),
464        ];
465        let f = ctx.context.get_function("", "IFS").unwrap();
466        assert_eq!(
467            f.dispatch(&args, &ctx.function_context(None))
468                .unwrap()
469                .into_literal(),
470            LiteralValue::Int(9)
471        );
472    }
473
474    #[test]
475    fn ifs_no_match_returns_na_error() {
476        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IfsFn));
477        let ctx = interp(&wb);
478        let cond_false1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
479        let val1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(9)), None);
480        let cond_false2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(false)), None);
481        let val2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
482        let args = vec![
483            ArgumentHandle::new(&cond_false1, &ctx),
484            ArgumentHandle::new(&val1, &ctx),
485            ArgumentHandle::new(&cond_false2, &ctx),
486            ArgumentHandle::new(&val2, &ctx),
487        ];
488        let f = ctx.context.get_function("", "IFS").unwrap();
489        match f
490            .dispatch(&args, &ctx.function_context(None))
491            .unwrap()
492            .into_literal()
493        {
494            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
495            other => panic!("expected #N/A got {other:?}"),
496        }
497    }
498
499    #[test]
500    fn not_number_zero_and_nonzero() {
501        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(NotFn));
502        let ctx = interp(&wb);
503        let zero = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(0)), None);
504        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
505        let f = ctx.context.get_function("", "NOT").unwrap();
506        assert_eq!(
507            f.dispatch(
508                &[ArgumentHandle::new(&zero, &ctx)],
509                &ctx.function_context(None)
510            )
511            .unwrap()
512            .into_literal(),
513            LiteralValue::Boolean(true)
514        );
515        assert_eq!(
516            f.dispatch(
517                &[ArgumentHandle::new(&one, &ctx)],
518                &ctx.function_context(None)
519            )
520            .unwrap()
521            .into_literal(),
522            LiteralValue::Boolean(false)
523        );
524    }
525
526    #[test]
527    fn xor_error_propagation() {
528        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(XorFn));
529        let ctx = interp(&wb);
530        let err = ASTNode::new(
531            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_value())),
532            None,
533        );
534        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
535        let f = ctx.context.get_function("", "XOR").unwrap();
536        match f
537            .dispatch(
538                &[
539                    ArgumentHandle::new(&err, &ctx),
540                    ArgumentHandle::new(&one, &ctx),
541                ],
542                &ctx.function_context(None),
543            )
544            .unwrap()
545            .into_literal()
546        {
547            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
548            _ => panic!("expected value error"),
549        }
550    }
551
552    #[derive(Debug)]
553    struct ThrowNameFn;
554
555    impl Function for ThrowNameFn {
556        func_caps!(PURE);
557
558        fn name(&self) -> &'static str {
559            "THROWNAME"
560        }
561
562        fn eval<'a, 'b, 'c>(
563            &self,
564            _args: &'c [ArgumentHandle<'a, 'b>],
565            _ctx: &dyn FunctionContext<'b>,
566        ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
567            Err(ExcelError::new_name())
568        }
569    }
570
571    #[test]
572    fn iferror_catches_evaluation_errors_returned_as_err() {
573        let wb = TestWorkbook::new()
574            .with_function(std::sync::Arc::new(IfErrorFn))
575            .with_function(std::sync::Arc::new(ThrowNameFn));
576        let ctx = interp(&wb);
577
578        let throw = ASTNode::new(
579            ASTNodeType::Function {
580                name: "THROWNAME".to_string(),
581                args: vec![],
582            },
583            None,
584        );
585        let fallback = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(42)), None);
586
587        let args = vec![
588            ArgumentHandle::new(&throw, &ctx),
589            ArgumentHandle::new(&fallback, &ctx),
590        ];
591        let f = ctx.context.get_function("", "IFERROR").unwrap();
592
593        assert_eq!(
594            f.dispatch(&args, &ctx.function_context(None))
595                .unwrap()
596                .into_literal(),
597            LiteralValue::Int(42)
598        );
599    }
600}