formualizer_eval/builtins/info.rs
1use crate::args::ArgSchema;
2use crate::function::Function;
3use crate::traits::{ArgumentHandle, FunctionContext};
4use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
5use formualizer_macros::func_caps;
6
7use super::utils::ARG_ANY_ONE;
8
9/* Info and type-introspection builtins for spreadsheet formulas. */
10
11#[derive(Debug)]
12pub struct IsNumberFn;
13/// Returns TRUE when the value is numeric.
14///
15/// This includes integer, floating-point, and temporal serial-compatible values.
16///
17/// # Remarks
18/// - Returns TRUE for `Int`, `Number`, `Date`, `DateTime`, `Time`, and `Duration`.
19/// - Text that looks numeric is still text and returns FALSE.
20/// - Errors are treated as non-numeric and return FALSE.
21///
22/// # Examples
23///
24/// ```yaml,sandbox
25/// title: "Number is numeric"
26/// formula: '=ISNUMBER(42)'
27/// expected: true
28/// ```
29///
30/// ```yaml,sandbox
31/// title: "Numeric text is not numeric"
32/// formula: '=ISNUMBER("42")'
33/// expected: false
34/// ```
35///
36/// ```yaml,docs
37/// related:
38/// - VALUE
39/// - N
40/// - TYPE
41/// faq:
42/// - q: "Does numeric-looking text count as a number?"
43/// a: "No. ISNUMBER checks the stored value type, so text like \"42\" returns FALSE."
44/// ```
45/// [formualizer-docgen:schema:start]
46/// Name: ISNUMBER
47/// Type: IsNumberFn
48/// Min args: 1
49/// Max args: 1
50/// Variadic: false
51/// Signature: ISNUMBER(arg1: any@scalar)
52/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
53/// Caps: PURE
54/// [formualizer-docgen:schema:end]
55impl Function for IsNumberFn {
56 func_caps!(PURE);
57 fn name(&self) -> &'static str {
58 "ISNUMBER"
59 }
60 fn min_args(&self) -> usize {
61 1
62 }
63 fn arg_schema(&self) -> &'static [ArgSchema] {
64 &ARG_ANY_ONE[..]
65 }
66 fn eval<'a, 'b, 'c>(
67 &self,
68 args: &'c [ArgumentHandle<'a, 'b>],
69 _ctx: &dyn FunctionContext<'b>,
70 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
71 if args.len() != 1 {
72 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
73 ExcelError::new_value(),
74 )));
75 }
76 let v = args[0].value()?.into_literal();
77 let is_num = matches!(
78 v,
79 LiteralValue::Int(_)
80 | LiteralValue::Number(_)
81 | LiteralValue::Date(_)
82 | LiteralValue::DateTime(_)
83 | LiteralValue::Time(_)
84 | LiteralValue::Duration(_)
85 );
86 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
87 is_num,
88 )))
89 }
90}
91
92#[derive(Debug)]
93pub struct IsTextFn;
94/// Returns TRUE when the value is text.
95///
96/// # Remarks
97/// - Only text literals return TRUE.
98/// - Numbers, booleans, blanks, and errors return FALSE.
99/// - No coercion from other types to text is performed for this check.
100///
101/// # Examples
102///
103/// ```yaml,sandbox
104/// title: "Detect text"
105/// formula: '=ISTEXT("alpha")'
106/// expected: true
107/// ```
108///
109/// ```yaml,sandbox
110/// title: "Number is not text"
111/// formula: '=ISTEXT(100)'
112/// expected: false
113/// ```
114///
115/// ```yaml,docs
116/// related:
117/// - T
118/// - TYPE
119/// - ISNUMBER
120/// faq:
121/// - q: "Is an empty string treated as text?"
122/// a: "Yes. An empty string literal is still text, so ISTEXT(\"\") returns TRUE."
123/// ```
124/// [formualizer-docgen:schema:start]
125/// Name: ISTEXT
126/// Type: IsTextFn
127/// Min args: 1
128/// Max args: 1
129/// Variadic: false
130/// Signature: ISTEXT(arg1: any@scalar)
131/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
132/// Caps: PURE
133/// [formualizer-docgen:schema:end]
134impl Function for IsTextFn {
135 func_caps!(PURE);
136 fn name(&self) -> &'static str {
137 "ISTEXT"
138 }
139 fn min_args(&self) -> usize {
140 1
141 }
142 fn arg_schema(&self) -> &'static [ArgSchema] {
143 &ARG_ANY_ONE[..]
144 }
145 fn eval<'a, 'b, 'c>(
146 &self,
147 args: &'c [ArgumentHandle<'a, 'b>],
148 _ctx: &dyn FunctionContext<'b>,
149 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
150 if args.len() != 1 {
151 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
152 ExcelError::new_value(),
153 )));
154 }
155 let v = args[0].value()?.into_literal();
156 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
157 matches!(v, LiteralValue::Text(_)),
158 )))
159 }
160}
161
162#[derive(Debug)]
163pub struct IsLogicalFn;
164/// Returns TRUE when the value is a boolean.
165///
166/// # Remarks
167/// - Only logical TRUE/FALSE values return TRUE.
168/// - Numeric truthy/falsy values are not considered logical by this predicate.
169/// - Errors return FALSE.
170///
171/// # Examples
172///
173/// ```yaml,sandbox
174/// title: "Boolean input"
175/// formula: '=ISLOGICAL(TRUE)'
176/// expected: true
177/// ```
178///
179/// ```yaml,sandbox
180/// title: "Numeric input"
181/// formula: '=ISLOGICAL(1)'
182/// expected: false
183/// ```
184///
185/// ```yaml,docs
186/// related:
187/// - TRUE
188/// - FALSE
189/// - TYPE
190/// faq:
191/// - q: "Do truthy numbers count as logical values?"
192/// a: "No. ISLOGICAL returns TRUE only for actual boolean TRUE/FALSE values."
193/// ```
194/// [formualizer-docgen:schema:start]
195/// Name: ISLOGICAL
196/// Type: IsLogicalFn
197/// Min args: 1
198/// Max args: 1
199/// Variadic: false
200/// Signature: ISLOGICAL(arg1: any@scalar)
201/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
202/// Caps: PURE
203/// [formualizer-docgen:schema:end]
204impl Function for IsLogicalFn {
205 func_caps!(PURE);
206 fn name(&self) -> &'static str {
207 "ISLOGICAL"
208 }
209 fn min_args(&self) -> usize {
210 1
211 }
212 fn arg_schema(&self) -> &'static [ArgSchema] {
213 &ARG_ANY_ONE[..]
214 }
215 fn eval<'a, 'b, 'c>(
216 &self,
217 args: &'c [ArgumentHandle<'a, 'b>],
218 _ctx: &dyn FunctionContext<'b>,
219 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
220 if args.len() != 1 {
221 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
222 ExcelError::new_value(),
223 )));
224 }
225 let v = args[0].value()?.into_literal();
226 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
227 matches!(v, LiteralValue::Boolean(_)),
228 )))
229 }
230}
231
232#[derive(Debug)]
233pub struct IsBlankFn;
234/// Returns TRUE only for a truly empty value.
235///
236/// # Remarks
237/// - Empty string `""` is text, not blank, so it returns FALSE.
238/// - Numeric zero and FALSE are not blank.
239/// - Errors return FALSE.
240///
241/// # Examples
242///
243/// ```yaml,sandbox
244/// title: "Reference to an empty cell"
245/// formula: '=ISBLANK(A1)'
246/// expected: true
247/// ```
248///
249/// ```yaml,sandbox
250/// title: "Empty string is not blank"
251/// formula: '=ISBLANK("")'
252/// expected: false
253/// ```
254///
255/// ```yaml,docs
256/// related:
257/// - ISTEXT
258/// - LEN
259/// - T
260/// faq:
261/// - q: "Why does ISBLANK(\"\") return FALSE?"
262/// a: "Because an empty string is text, not a truly empty cell value."
263/// ```
264/// [formualizer-docgen:schema:start]
265/// Name: ISBLANK
266/// Type: IsBlankFn
267/// Min args: 1
268/// Max args: 1
269/// Variadic: false
270/// Signature: ISBLANK(arg1: any@scalar)
271/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
272/// Caps: PURE
273/// [formualizer-docgen:schema:end]
274impl Function for IsBlankFn {
275 func_caps!(PURE);
276 fn name(&self) -> &'static str {
277 "ISBLANK"
278 }
279 fn min_args(&self) -> usize {
280 1
281 }
282 fn arg_schema(&self) -> &'static [ArgSchema] {
283 &ARG_ANY_ONE[..]
284 }
285 fn eval<'a, 'b, 'c>(
286 &self,
287 args: &'c [ArgumentHandle<'a, 'b>],
288 _ctx: &dyn FunctionContext<'b>,
289 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
290 if args.len() != 1 {
291 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
292 ExcelError::new_value(),
293 )));
294 }
295 let v = args[0].value()?.into_literal();
296 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
297 matches!(v, LiteralValue::Empty),
298 )))
299 }
300}
301
302#[derive(Debug)]
303pub struct IsErrorFn; // TRUE for any error (#N/A included)
304/// Returns TRUE for any error value.
305///
306/// # Remarks
307/// - Matches all error kinds, including `#N/A`.
308/// - Non-error values always return FALSE.
309/// - Arity mismatch returns `#VALUE!`.
310///
311/// # Examples
312///
313/// ```yaml,sandbox
314/// title: "Division error"
315/// formula: '=ISERROR(1/0)'
316/// expected: true
317/// ```
318///
319/// ```yaml,sandbox
320/// title: "Normal value"
321/// formula: '=ISERROR(123)'
322/// expected: false
323/// ```
324///
325/// ```yaml,docs
326/// related:
327/// - ISERR
328/// - ISNA
329/// - IFERROR
330/// faq:
331/// - q: "Does ISERROR include #N/A?"
332/// a: "Yes. ISERROR returns TRUE for all error kinds, including #N/A."
333/// ```
334/// [formualizer-docgen:schema:start]
335/// Name: ISERROR
336/// Type: IsErrorFn
337/// Min args: 1
338/// Max args: 1
339/// Variadic: false
340/// Signature: ISERROR(arg1: any@scalar)
341/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
342/// Caps: PURE
343/// [formualizer-docgen:schema:end]
344impl Function for IsErrorFn {
345 func_caps!(PURE);
346 fn name(&self) -> &'static str {
347 "ISERROR"
348 }
349 fn min_args(&self) -> usize {
350 1
351 }
352 fn arg_schema(&self) -> &'static [ArgSchema] {
353 &ARG_ANY_ONE[..]
354 }
355 fn eval<'a, 'b, 'c>(
356 &self,
357 args: &'c [ArgumentHandle<'a, 'b>],
358 _ctx: &dyn FunctionContext<'b>,
359 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
360 if args.len() != 1 {
361 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
362 ExcelError::new_value(),
363 )));
364 }
365 let v = args[0].value()?.into_literal();
366 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
367 matches!(v, LiteralValue::Error(_)),
368 )))
369 }
370}
371
372#[derive(Debug)]
373pub struct IsErrFn; // TRUE for any error except #N/A
374/// Returns TRUE for any error except `#N/A`.
375///
376/// # Remarks
377/// - `#N/A` specifically returns FALSE.
378/// - Other errors such as `#DIV/0!` or `#VALUE!` return TRUE.
379/// - Non-error values return FALSE.
380///
381/// # Examples
382///
383/// ```yaml,sandbox
384/// title: "DIV/0 is an error excluding N/A"
385/// formula: '=ISERR(1/0)'
386/// expected: true
387/// ```
388///
389/// ```yaml,sandbox
390/// title: "N/A is excluded"
391/// formula: '=ISERR(NA())'
392/// expected: false
393/// ```
394///
395/// ```yaml,docs
396/// related:
397/// - ISERROR
398/// - ISNA
399/// - IFERROR
400/// faq:
401/// - q: "What is the difference between ISERR and ISERROR?"
402/// a: "ISERR excludes #N/A, while ISERROR treats #N/A as an error too."
403/// ```
404/// [formualizer-docgen:schema:start]
405/// Name: ISERR
406/// Type: IsErrFn
407/// Min args: 1
408/// Max args: 1
409/// Variadic: false
410/// Signature: ISERR(arg1: any@scalar)
411/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
412/// Caps: PURE
413/// [formualizer-docgen:schema:end]
414impl Function for IsErrFn {
415 func_caps!(PURE);
416 fn name(&self) -> &'static str {
417 "ISERR"
418 }
419 fn min_args(&self) -> usize {
420 1
421 }
422 fn arg_schema(&self) -> &'static [ArgSchema] {
423 &ARG_ANY_ONE[..]
424 }
425 fn eval<'a, 'b, 'c>(
426 &self,
427 args: &'c [ArgumentHandle<'a, 'b>],
428 _ctx: &dyn FunctionContext<'b>,
429 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
430 if args.len() != 1 {
431 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
432 ExcelError::new_value(),
433 )));
434 }
435 let v = args[0].value()?.into_literal();
436 let is_err = match v {
437 LiteralValue::Error(e) => e.kind != ExcelErrorKind::Na,
438 _ => false,
439 };
440 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
441 is_err,
442 )))
443 }
444}
445
446#[derive(Debug)]
447pub struct IsNaFn; // TRUE only for #N/A
448/// Returns TRUE only for the `#N/A` error.
449///
450/// # Remarks
451/// - Other error kinds return FALSE.
452/// - Non-error values return FALSE.
453/// - Useful when `#N/A` has special business meaning.
454///
455/// # Examples
456///
457/// ```yaml,sandbox
458/// title: "Check for N/A"
459/// formula: '=ISNA(NA())'
460/// expected: true
461/// ```
462///
463/// ```yaml,sandbox
464/// title: "Other errors are not N/A"
465/// formula: '=ISNA(1/0)'
466/// expected: false
467/// ```
468///
469/// ```yaml,docs
470/// related:
471/// - NA
472/// - IFNA
473/// - ISERROR
474/// faq:
475/// - q: "Does ISNA return TRUE for errors other than #N/A?"
476/// a: "No. It returns TRUE only when the value is exactly #N/A."
477/// ```
478/// [formualizer-docgen:schema:start]
479/// Name: ISNA
480/// Type: IsNaFn
481/// Min args: 1
482/// Max args: 1
483/// Variadic: false
484/// Signature: ISNA(arg1: any@scalar)
485/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
486/// Caps: PURE
487/// [formualizer-docgen:schema:end]
488impl Function for IsNaFn {
489 func_caps!(PURE);
490 fn name(&self) -> &'static str {
491 "ISNA"
492 }
493 fn min_args(&self) -> usize {
494 1
495 }
496 fn arg_schema(&self) -> &'static [ArgSchema] {
497 &ARG_ANY_ONE[..]
498 }
499 fn eval<'a, 'b, 'c>(
500 &self,
501 args: &'c [ArgumentHandle<'a, 'b>],
502 _ctx: &dyn FunctionContext<'b>,
503 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
504 if args.len() != 1 {
505 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
506 ExcelError::new_value(),
507 )));
508 }
509 let v = args[0].value()?.into_literal();
510 let is_na = matches!(v, LiteralValue::Error(e) if e.kind==ExcelErrorKind::Na);
511 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
512 is_na,
513 )))
514 }
515}
516
517#[derive(Debug)]
518pub struct IsFormulaFn; // Requires provenance tracking (not yet) => always FALSE.
519/// Returns whether a value originates from a formula.
520///
521/// Current engine metadata does not track formula provenance at this call site.
522///
523/// # Remarks
524/// - This implementation currently returns FALSE for all inputs.
525/// - Errors are not raised solely due to provenance unavailability.
526/// - Arity mismatch returns `#VALUE!`.
527///
528/// # Examples
529///
530/// ```yaml,sandbox
531/// title: "Literal value"
532/// formula: '=ISFORMULA(10)'
533/// expected: false
534/// ```
535///
536/// ```yaml,sandbox
537/// title: "Computed value in expression context"
538/// formula: '=ISFORMULA(1+1)'
539/// expected: false
540/// ```
541///
542/// ```yaml,docs
543/// related:
544/// - TYPE
545/// - ISNUMBER
546/// - ISTEXT
547/// faq:
548/// - q: "Can ISFORMULA currently detect formula provenance?"
549/// a: "Not yet. This implementation always returns FALSE because provenance metadata is not tracked here."
550/// ```
551/// [formualizer-docgen:schema:start]
552/// Name: ISFORMULA
553/// Type: IsFormulaFn
554/// Min args: 1
555/// Max args: 1
556/// Variadic: false
557/// Signature: ISFORMULA(arg1: any@scalar)
558/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
559/// Caps: PURE
560/// [formualizer-docgen:schema:end]
561impl Function for IsFormulaFn {
562 func_caps!(PURE);
563 fn name(&self) -> &'static str {
564 "ISFORMULA"
565 }
566 fn min_args(&self) -> usize {
567 1
568 }
569 fn arg_schema(&self) -> &'static [ArgSchema] {
570 &ARG_ANY_ONE[..]
571 }
572 fn eval<'a, 'b, 'c>(
573 &self,
574 args: &'c [ArgumentHandle<'a, 'b>],
575 _ctx: &dyn FunctionContext<'b>,
576 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
577 if args.len() != 1 {
578 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
579 ExcelError::new_value(),
580 )));
581 }
582 // Formula provenance metadata is not tracked yet, so ISFORMULA currently returns FALSE.
583 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
584 false,
585 )))
586 }
587}
588
589#[derive(Debug)]
590pub struct TypeFn;
591/// Returns an Excel TYPE code describing the value category.
592///
593/// # Remarks
594/// - Codes: `1` number, `2` text, `4` logical, `64` array.
595/// - Errors are propagated unchanged instead of returning `16`.
596/// - Blank values map to numeric code `1` in this implementation.
597///
598/// # Examples
599///
600/// ```yaml,sandbox
601/// title: "Text type code"
602/// formula: '=TYPE("abc")'
603/// expected: 2
604/// ```
605///
606/// ```yaml,sandbox
607/// title: "Boolean type code"
608/// formula: '=TYPE(TRUE)'
609/// expected: 4
610/// ```
611///
612/// ```yaml,docs
613/// related:
614/// - ISNUMBER
615/// - ISTEXT
616/// - ISLOGICAL
617/// faq:
618/// - q: "How are errors handled by TYPE?"
619/// a: "Errors are propagated unchanged instead of returning Excel's error type code 16."
620/// ```
621/// [formualizer-docgen:schema:start]
622/// Name: TYPE
623/// Type: TypeFn
624/// Min args: 1
625/// Max args: 1
626/// Variadic: false
627/// Signature: TYPE(arg1: any@scalar)
628/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
629/// Caps: PURE
630/// [formualizer-docgen:schema:end]
631impl Function for TypeFn {
632 func_caps!(PURE);
633 fn name(&self) -> &'static str {
634 "TYPE"
635 }
636 fn min_args(&self) -> usize {
637 1
638 }
639 fn arg_schema(&self) -> &'static [ArgSchema] {
640 &ARG_ANY_ONE[..]
641 }
642 fn eval<'a, 'b, 'c>(
643 &self,
644 args: &'c [ArgumentHandle<'a, 'b>],
645 _ctx: &dyn FunctionContext<'b>,
646 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
647 if args.len() != 1 {
648 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
649 ExcelError::new_value(),
650 )));
651 }
652 let v = args[0].value()?.into_literal(); // Propagate errors directly
653 if let LiteralValue::Error(e) = v {
654 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
655 }
656 let code = match v {
657 LiteralValue::Int(_)
658 | LiteralValue::Number(_)
659 | LiteralValue::Empty
660 | LiteralValue::Date(_)
661 | LiteralValue::DateTime(_)
662 | LiteralValue::Time(_)
663 | LiteralValue::Duration(_) => 1,
664 LiteralValue::Text(_) => 2,
665 LiteralValue::Boolean(_) => 4,
666 LiteralValue::Array(_) => 64,
667 LiteralValue::Error(_) => unreachable!(),
668 LiteralValue::Pending => 1, // treat as blank/zero numeric; may change
669 };
670 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(code)))
671 }
672}
673
674#[derive(Debug)]
675pub struct NaFn; // NA() -> #N/A error
676/// Returns the `#N/A` error value.
677///
678/// # Remarks
679/// - `NA()` is commonly used to mark missing lookup results.
680/// - The function takes no arguments.
681/// - The returned value is an error and propagates through dependent formulas.
682///
683/// # Examples
684///
685/// ```yaml,sandbox
686/// title: "Direct N/A"
687/// formula: '=NA()'
688/// expected: "#N/A"
689/// ```
690///
691/// ```yaml,sandbox
692/// title: "Detect N/A"
693/// formula: '=ISNA(NA())'
694/// expected: true
695/// ```
696///
697/// ```yaml,docs
698/// related:
699/// - ISNA
700/// - IFNA
701/// - IFERROR
702/// faq:
703/// - q: "When should I use NA() intentionally?"
704/// a: "Use it to mark missing data so lookups and downstream checks can distinguish absent values from blanks."
705/// ```
706/// [formualizer-docgen:schema:start]
707/// Name: NA
708/// Type: NaFn
709/// Min args: 0
710/// Max args: 0
711/// Variadic: false
712/// Signature: NA()
713/// Arg schema: []
714/// Caps: PURE
715/// [formualizer-docgen:schema:end]
716impl Function for NaFn {
717 func_caps!(PURE);
718 fn name(&self) -> &'static str {
719 "NA"
720 }
721 fn min_args(&self) -> usize {
722 0
723 }
724 fn eval<'a, 'b, 'c>(
725 &self,
726 _args: &'c [ArgumentHandle<'a, 'b>],
727 _ctx: &dyn FunctionContext<'b>,
728 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
729 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
730 ExcelError::new(ExcelErrorKind::Na),
731 )))
732 }
733}
734
735#[derive(Debug)]
736pub struct NFn; // N(value)
737/// Converts a value to its numeric representation.
738///
739/// # Remarks
740/// - Numbers pass through unchanged; booleans convert to `1`/`0`.
741/// - Text and blank values convert to `0`.
742/// - Errors propagate unchanged.
743/// - Temporal values are converted using serial number representation.
744///
745/// # Examples
746///
747/// ```yaml,sandbox
748/// title: "Boolean to number"
749/// formula: '=N(TRUE)'
750/// expected: 1
751/// ```
752///
753/// ```yaml,sandbox
754/// title: "Text to zero"
755/// formula: '=N("hello")'
756/// expected: 0
757/// ```
758///
759/// ```yaml,docs
760/// related:
761/// - VALUE
762/// - T
763/// - TYPE
764/// faq:
765/// - q: "What does N do with text and blanks?"
766/// a: "Text and blank values convert to 0, while existing errors are passed through."
767/// ```
768/// [formualizer-docgen:schema:start]
769/// Name: N
770/// Type: NFn
771/// Min args: 1
772/// Max args: 1
773/// Variadic: false
774/// Signature: N(arg1: any@scalar)
775/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
776/// Caps: PURE
777/// [formualizer-docgen:schema:end]
778impl Function for NFn {
779 func_caps!(PURE);
780 fn name(&self) -> &'static str {
781 "N"
782 }
783 fn min_args(&self) -> usize {
784 1
785 }
786 fn arg_schema(&self) -> &'static [ArgSchema] {
787 &ARG_ANY_ONE[..]
788 }
789 fn eval<'a, 'b, 'c>(
790 &self,
791 args: &'c [ArgumentHandle<'a, 'b>],
792 _ctx: &dyn FunctionContext<'b>,
793 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
794 if args.len() != 1 {
795 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
796 ExcelError::new_value(),
797 )));
798 }
799 let v = args[0].value()?.into_literal();
800 match v {
801 LiteralValue::Int(i) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(i))),
802 LiteralValue::Number(n) => {
803 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(n)))
804 }
805 LiteralValue::Date(_)
806 | LiteralValue::DateTime(_)
807 | LiteralValue::Time(_)
808 | LiteralValue::Duration(_) => {
809 // Convert via serial number helper
810 if let Some(serial) = v.as_serial_number() {
811 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
812 serial,
813 )))
814 } else {
815 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0)))
816 }
817 }
818 LiteralValue::Boolean(b) => {
819 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(if b {
820 1
821 } else {
822 0
823 })))
824 }
825 LiteralValue::Text(_) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
826 LiteralValue::Empty => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
827 LiteralValue::Array(_) => {
828 // Array-to-scalar implicit intersection is not implemented here; returns 0.
829 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0)))
830 }
831 LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
832 LiteralValue::Pending => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
833 }
834 }
835}
836
837#[derive(Debug)]
838pub struct TFn; // T(value)
839/// Returns text when input is text, otherwise returns empty text.
840///
841/// # Remarks
842/// - Text values pass through unchanged.
843/// - Errors propagate unchanged.
844/// - Numbers, booleans, and blanks return an empty string.
845///
846/// # Examples
847///
848/// ```yaml,sandbox
849/// title: "Text passthrough"
850/// formula: '=T("report")'
851/// expected: "report"
852/// ```
853///
854/// ```yaml,sandbox
855/// title: "Number becomes empty text"
856/// formula: '=T(99)'
857/// expected: ""
858/// ```
859///
860/// ```yaml,docs
861/// related:
862/// - N
863/// - ISTEXT
864/// - TYPE
865/// faq:
866/// - q: "Does T hide non-text values?"
867/// a: "Yes. Non-text inputs become an empty string, but errors are still propagated."
868/// ```
869/// [formualizer-docgen:schema:start]
870/// Name: T
871/// Type: TFn
872/// Min args: 1
873/// Max args: 1
874/// Variadic: false
875/// Signature: T(arg1: any@scalar)
876/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
877/// Caps: PURE
878/// [formualizer-docgen:schema:end]
879impl Function for TFn {
880 func_caps!(PURE);
881 fn name(&self) -> &'static str {
882 "T"
883 }
884 fn min_args(&self) -> usize {
885 1
886 }
887 fn arg_schema(&self) -> &'static [ArgSchema] {
888 &ARG_ANY_ONE[..]
889 }
890 fn eval<'a, 'b, 'c>(
891 &self,
892 args: &'c [ArgumentHandle<'a, 'b>],
893 _ctx: &dyn FunctionContext<'b>,
894 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
895 if args.len() != 1 {
896 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
897 ExcelError::new_value(),
898 )));
899 }
900 let v = args[0].value()?.into_literal();
901 match v {
902 LiteralValue::Text(s) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(s))),
903 LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
904 _ => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
905 String::new(),
906 ))),
907 }
908 }
909}
910
911/// ISEVEN(number) - Returns TRUE if number is even
912#[derive(Debug)]
913pub struct IsEvenFn;
914/// Returns TRUE when a number is even.
915///
916/// # Remarks
917/// - Numeric input is truncated toward zero before parity is checked.
918/// - Booleans are coerced (`TRUE` -> 1, `FALSE` -> 0).
919/// - Non-numeric text returns `#VALUE!`.
920/// - Errors propagate unchanged.
921///
922/// # Examples
923///
924/// ```yaml,sandbox
925/// title: "Even integer"
926/// formula: '=ISEVEN(6)'
927/// expected: true
928/// ```
929///
930/// ```yaml,sandbox
931/// title: "Decimal truncation before parity"
932/// formula: '=ISEVEN(3.9)'
933/// expected: false
934/// ```
935///
936/// ```yaml,docs
937/// related:
938/// - ISODD
939/// - ISNUMBER
940/// - N
941/// faq:
942/// - q: "How are decimals handled by ISEVEN?"
943/// a: "The number is truncated toward zero before checking even/odd parity."
944/// ```
945/// [formualizer-docgen:schema:start]
946/// Name: ISEVEN
947/// Type: IsEvenFn
948/// Min args: 1
949/// Max args: 1
950/// Variadic: false
951/// Signature: ISEVEN(arg1: any@scalar)
952/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
953/// Caps: PURE
954/// [formualizer-docgen:schema:end]
955impl Function for IsEvenFn {
956 func_caps!(PURE);
957 fn name(&self) -> &'static str {
958 "ISEVEN"
959 }
960 fn min_args(&self) -> usize {
961 1
962 }
963 fn arg_schema(&self) -> &'static [ArgSchema] {
964 &ARG_ANY_ONE[..]
965 }
966 fn eval<'a, 'b, 'c>(
967 &self,
968 args: &'c [ArgumentHandle<'a, 'b>],
969 _ctx: &dyn FunctionContext<'b>,
970 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
971 if args.len() != 1 {
972 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
973 ExcelError::new_value(),
974 )));
975 }
976 let v = args[0].value()?.into_literal();
977 let n = match v {
978 LiteralValue::Error(e) => {
979 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
980 }
981 LiteralValue::Int(i) => i as f64,
982 LiteralValue::Number(n) => n,
983 LiteralValue::Boolean(b) => {
984 if b {
985 1.0
986 } else {
987 0.0
988 }
989 }
990 _ => {
991 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
992 ExcelError::new_value(),
993 )));
994 }
995 };
996 // Excel truncates to integer first
997 let n = n.trunc() as i64;
998 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
999 n % 2 == 0,
1000 )))
1001 }
1002}
1003
1004/// ISODD(number) - Returns TRUE if number is odd
1005#[derive(Debug)]
1006pub struct IsOddFn;
1007/// Returns TRUE when a number is odd.
1008///
1009/// # Remarks
1010/// - Numeric input is truncated toward zero before parity is checked.
1011/// - Booleans are coerced (`TRUE` -> 1, `FALSE` -> 0).
1012/// - Non-numeric text returns `#VALUE!`.
1013/// - Errors propagate unchanged.
1014///
1015/// # Examples
1016///
1017/// ```yaml,sandbox
1018/// title: "Odd integer"
1019/// formula: '=ISODD(7)'
1020/// expected: true
1021/// ```
1022///
1023/// ```yaml,sandbox
1024/// title: "Boolean coercion"
1025/// formula: '=ISODD(TRUE)'
1026/// expected: true
1027/// ```
1028///
1029/// ```yaml,docs
1030/// related:
1031/// - ISEVEN
1032/// - ISNUMBER
1033/// - N
1034/// faq:
1035/// - q: "Are booleans valid inputs for ISODD?"
1036/// a: "Yes. TRUE is treated as 1 and FALSE as 0 before the odd check."
1037/// ```
1038/// [formualizer-docgen:schema:start]
1039/// Name: ISODD
1040/// Type: IsOddFn
1041/// Min args: 1
1042/// Max args: 1
1043/// Variadic: false
1044/// Signature: ISODD(arg1: any@scalar)
1045/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
1046/// Caps: PURE
1047/// [formualizer-docgen:schema:end]
1048impl Function for IsOddFn {
1049 func_caps!(PURE);
1050 fn name(&self) -> &'static str {
1051 "ISODD"
1052 }
1053 fn min_args(&self) -> usize {
1054 1
1055 }
1056 fn arg_schema(&self) -> &'static [ArgSchema] {
1057 &ARG_ANY_ONE[..]
1058 }
1059 fn eval<'a, 'b, 'c>(
1060 &self,
1061 args: &'c [ArgumentHandle<'a, 'b>],
1062 _ctx: &dyn FunctionContext<'b>,
1063 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1064 if args.len() != 1 {
1065 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1066 ExcelError::new_value(),
1067 )));
1068 }
1069 let v = args[0].value()?.into_literal();
1070 let n = match v {
1071 LiteralValue::Error(e) => {
1072 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1073 }
1074 LiteralValue::Int(i) => i as f64,
1075 LiteralValue::Number(n) => n,
1076 LiteralValue::Boolean(b) => {
1077 if b {
1078 1.0
1079 } else {
1080 0.0
1081 }
1082 }
1083 _ => {
1084 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1085 ExcelError::new_value(),
1086 )));
1087 }
1088 };
1089 let n = n.trunc() as i64;
1090 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
1091 n % 2 != 0,
1092 )))
1093 }
1094}
1095
1096/// ERROR.TYPE(error_val) - Returns a number corresponding to an error type
1097/// Returns:
1098/// 1 = #NULL!
1099/// 2 = #DIV/0!
1100/// 3 = #VALUE!
1101/// 4 = #REF!
1102/// 5 = #NAME?
1103/// 6 = #NUM!
1104/// 7 = #N/A
1105/// 8 = #GETTING_DATA (not commonly used)
1106/// #N/A if the value is not an error
1107///
1108/// NOTE: Error codes 9-13 are non-standard extensions for internal error types.
1109#[derive(Debug)]
1110pub struct ErrorTypeFn;
1111/// Returns the numeric code for a specific error value.
1112///
1113/// # Remarks
1114/// - Standard mappings include: `#NULL!`=1, `#DIV/0!`=2, `#VALUE!`=3, `#REF!`=4, `#NAME?`=5, `#NUM!`=6, `#N/A`=7.
1115/// - Non-error inputs return `#N/A`.
1116/// - Additional internal error kinds may map to extended non-standard codes.
1117///
1118/// # Examples
1119///
1120/// ```yaml,sandbox
1121/// title: "Map DIV/0 to code"
1122/// formula: '=ERROR.TYPE(1/0)'
1123/// expected: 2
1124/// ```
1125///
1126/// ```yaml,sandbox
1127/// title: "Non-error input returns N/A"
1128/// formula: '=ERROR.TYPE(10)'
1129/// expected: "#N/A"
1130/// ```
1131///
1132/// ```yaml,docs
1133/// related:
1134/// - ISERROR
1135/// - ISNA
1136/// - IFERROR
1137/// faq:
1138/// - q: "What if the input is not an error value?"
1139/// a: "ERROR.TYPE returns #N/A when the input is not an error."
1140/// ```
1141/// [formualizer-docgen:schema:start]
1142/// Name: ERROR.TYPE
1143/// Type: ErrorTypeFn
1144/// Min args: 1
1145/// Max args: 1
1146/// Variadic: false
1147/// Signature: ERROR.TYPE(arg1: any@scalar)
1148/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
1149/// Caps: PURE
1150/// [formualizer-docgen:schema:end]
1151impl Function for ErrorTypeFn {
1152 func_caps!(PURE);
1153 fn name(&self) -> &'static str {
1154 "ERROR.TYPE"
1155 }
1156 fn min_args(&self) -> usize {
1157 1
1158 }
1159 fn arg_schema(&self) -> &'static [ArgSchema] {
1160 &ARG_ANY_ONE[..]
1161 }
1162 fn eval<'a, 'b, 'c>(
1163 &self,
1164 args: &'c [ArgumentHandle<'a, 'b>],
1165 _ctx: &dyn FunctionContext<'b>,
1166 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1167 if args.len() != 1 {
1168 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1169 ExcelError::new_value(),
1170 )));
1171 }
1172 let v = args[0].value()?.into_literal();
1173 match v {
1174 LiteralValue::Error(e) => {
1175 let code = match e.kind {
1176 ExcelErrorKind::Null => 1,
1177 ExcelErrorKind::Div => 2,
1178 ExcelErrorKind::Value => 3,
1179 ExcelErrorKind::Ref => 4,
1180 ExcelErrorKind::Name => 5,
1181 ExcelErrorKind::Num => 6,
1182 ExcelErrorKind::Na => 7,
1183 ExcelErrorKind::Error => 8,
1184 // Non-standard extensions (codes 9-13)
1185 ExcelErrorKind::NImpl => 9,
1186 ExcelErrorKind::Spill => 10,
1187 ExcelErrorKind::Calc => 11,
1188 ExcelErrorKind::Circ => 12,
1189 ExcelErrorKind::Cancelled => 13,
1190 };
1191 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(code)))
1192 }
1193 _ => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1194 ExcelError::new_na(),
1195 ))),
1196 }
1197 }
1198}
1199
1200pub fn register_builtins() {
1201 use std::sync::Arc;
1202 crate::function_registry::register_function(Arc::new(IsNumberFn));
1203 crate::function_registry::register_function(Arc::new(IsTextFn));
1204 crate::function_registry::register_function(Arc::new(IsLogicalFn));
1205 crate::function_registry::register_function(Arc::new(IsBlankFn));
1206 crate::function_registry::register_function(Arc::new(IsErrorFn));
1207 crate::function_registry::register_function(Arc::new(IsErrFn));
1208 crate::function_registry::register_function(Arc::new(IsNaFn));
1209 crate::function_registry::register_function(Arc::new(IsFormulaFn));
1210 crate::function_registry::register_function(Arc::new(IsEvenFn));
1211 crate::function_registry::register_function(Arc::new(IsOddFn));
1212 crate::function_registry::register_function(Arc::new(ErrorTypeFn));
1213 crate::function_registry::register_function(Arc::new(TypeFn));
1214 crate::function_registry::register_function(Arc::new(NaFn));
1215 crate::function_registry::register_function(Arc::new(NFn));
1216 crate::function_registry::register_function(Arc::new(TFn));
1217}
1218
1219#[cfg(test)]
1220mod tests {
1221 use super::*;
1222 use crate::test_workbook::TestWorkbook;
1223 use formualizer_parse::parser::{ASTNode, ASTNodeType};
1224 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1225 wb.interpreter()
1226 }
1227
1228 #[test]
1229 fn isnumber_numeric_and_date() {
1230 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsNumberFn));
1231 let ctx = interp(&wb);
1232 let f = ctx.context.get_function("", "ISNUMBER").unwrap();
1233 let num = ASTNode::new(
1234 ASTNodeType::Literal(LiteralValue::Number(std::f64::consts::PI)),
1235 None,
1236 );
1237 let date = ASTNode::new(
1238 ASTNodeType::Literal(LiteralValue::Date(
1239 chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1240 )),
1241 None,
1242 );
1243 let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("x".into())), None);
1244 let args_num = vec![crate::traits::ArgumentHandle::new(&num, &ctx)];
1245 let args_date = vec![crate::traits::ArgumentHandle::new(&date, &ctx)];
1246 let args_txt = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
1247 assert_eq!(
1248 f.dispatch(&args_num, &ctx.function_context(None))
1249 .unwrap()
1250 .into_literal(),
1251 LiteralValue::Boolean(true)
1252 );
1253 assert_eq!(
1254 f.dispatch(&args_date, &ctx.function_context(None))
1255 .unwrap()
1256 .into_literal(),
1257 LiteralValue::Boolean(true)
1258 );
1259 assert_eq!(
1260 f.dispatch(&args_txt, &ctx.function_context(None))
1261 .unwrap()
1262 .into_literal(),
1263 LiteralValue::Boolean(false)
1264 );
1265 }
1266
1267 #[test]
1268 fn istest_and_isblank() {
1269 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsTextFn));
1270 let ctx = interp(&wb);
1271 let f = ctx.context.get_function("", "ISTEXT").unwrap();
1272 let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
1273 let n = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
1274 let args_t = vec![crate::traits::ArgumentHandle::new(&t, &ctx)];
1275 let args_n = vec![crate::traits::ArgumentHandle::new(&n, &ctx)];
1276 assert_eq!(
1277 f.dispatch(&args_t, &ctx.function_context(None))
1278 .unwrap()
1279 .into_literal(),
1280 LiteralValue::Boolean(true)
1281 );
1282 assert_eq!(
1283 f.dispatch(&args_n, &ctx.function_context(None))
1284 .unwrap()
1285 .into_literal(),
1286 LiteralValue::Boolean(false)
1287 );
1288
1289 // ISBLANK
1290 let wb2 = TestWorkbook::new().with_function(std::sync::Arc::new(IsBlankFn));
1291 let ctx2 = interp(&wb2);
1292 let f2 = ctx2.context.get_function("", "ISBLANK").unwrap();
1293 let blank = ASTNode::new(ASTNodeType::Literal(LiteralValue::Empty), None);
1294 let blank_args = vec![crate::traits::ArgumentHandle::new(&blank, &ctx2)];
1295 assert_eq!(
1296 f2.dispatch(&blank_args, &ctx2.function_context(None))
1297 .unwrap()
1298 .into_literal(),
1299 LiteralValue::Boolean(true)
1300 );
1301 }
1302
1303 #[test]
1304 fn iserror_variants() {
1305 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsErrorFn));
1306 let ctx = interp(&wb);
1307 let f = ctx.context.get_function("", "ISERROR").unwrap();
1308 let err = ASTNode::new(
1309 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div))),
1310 None,
1311 );
1312 let ok = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
1313 let a_err = vec![crate::traits::ArgumentHandle::new(&err, &ctx)];
1314 let a_ok = vec![crate::traits::ArgumentHandle::new(&ok, &ctx)];
1315 assert_eq!(
1316 f.dispatch(&a_err, &ctx.function_context(None))
1317 .unwrap()
1318 .into_literal(),
1319 LiteralValue::Boolean(true)
1320 );
1321 assert_eq!(
1322 f.dispatch(&a_ok, &ctx.function_context(None))
1323 .unwrap()
1324 .into_literal(),
1325 LiteralValue::Boolean(false)
1326 );
1327 }
1328
1329 #[test]
1330 fn type_codes_basic() {
1331 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TypeFn));
1332 let ctx = interp(&wb);
1333 let f = ctx.context.get_function("", "TYPE").unwrap();
1334 let v_num = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
1335 let v_txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("hi".into())), None);
1336 let v_bool = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
1337 let v_err = ASTNode::new(
1338 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
1339 None,
1340 );
1341 let v_arr = ASTNode::new(
1342 ASTNodeType::Literal(LiteralValue::Array(vec![vec![LiteralValue::Int(1)]])),
1343 None,
1344 );
1345 let a_num = vec![crate::traits::ArgumentHandle::new(&v_num, &ctx)];
1346 let a_txt = vec![crate::traits::ArgumentHandle::new(&v_txt, &ctx)];
1347 let a_bool = vec![crate::traits::ArgumentHandle::new(&v_bool, &ctx)];
1348 let a_err = vec![crate::traits::ArgumentHandle::new(&v_err, &ctx)];
1349 let a_arr = vec![crate::traits::ArgumentHandle::new(&v_arr, &ctx)];
1350 assert_eq!(
1351 f.dispatch(&a_num, &ctx.function_context(None))
1352 .unwrap()
1353 .into_literal(),
1354 LiteralValue::Int(1)
1355 );
1356 assert_eq!(
1357 f.dispatch(&a_txt, &ctx.function_context(None))
1358 .unwrap()
1359 .into_literal(),
1360 LiteralValue::Int(2)
1361 );
1362 assert_eq!(
1363 f.dispatch(&a_bool, &ctx.function_context(None))
1364 .unwrap()
1365 .into_literal(),
1366 LiteralValue::Int(4)
1367 );
1368 match f
1369 .dispatch(&a_err, &ctx.function_context(None))
1370 .unwrap()
1371 .into_literal()
1372 {
1373 LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
1374 _ => panic!(),
1375 }
1376 assert_eq!(
1377 f.dispatch(&a_arr, &ctx.function_context(None))
1378 .unwrap()
1379 .into_literal(),
1380 LiteralValue::Int(64)
1381 );
1382 }
1383
1384 #[test]
1385 fn na_and_n_and_t() {
1386 let wb = TestWorkbook::new()
1387 .with_function(std::sync::Arc::new(NaFn))
1388 .with_function(std::sync::Arc::new(NFn))
1389 .with_function(std::sync::Arc::new(TFn));
1390 let ctx = wb.interpreter();
1391 // NA()
1392 let na_fn = ctx.context.get_function("", "NA").unwrap();
1393 match na_fn
1394 .eval(&[], &ctx.function_context(None))
1395 .unwrap()
1396 .into_literal()
1397 {
1398 LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
1399 _ => panic!(),
1400 }
1401 // N()
1402 let n_fn = ctx.context.get_function("", "N").unwrap();
1403 let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
1404 let args = vec![crate::traits::ArgumentHandle::new(&val, &ctx)];
1405 assert_eq!(
1406 n_fn.dispatch(&args, &ctx.function_context(None))
1407 .unwrap()
1408 .into_literal(),
1409 LiteralValue::Int(1)
1410 );
1411 // T()
1412 let t_fn = ctx.context.get_function("", "T").unwrap();
1413 let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
1414 let args_t = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
1415 assert_eq!(
1416 t_fn.dispatch(&args_t, &ctx.function_context(None))
1417 .unwrap()
1418 .into_literal(),
1419 LiteralValue::Text("abc".into())
1420 );
1421 }
1422}