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#[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; impl 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 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; impl 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; impl 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 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}