Skip to main content

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}