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