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