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/*
10Sprint 9 – Info / Error Introspection Functions
11
12Implemented:
13  ISNUMBER, ISTEXT, ISLOGICAL, ISBLANK, ISERROR, ISERR, ISNA, ISFORMULA, TYPE,
14  NA, N, T
15
16Excel semantic notes (baseline):
17  - ISNUMBER returns TRUE for numeric types (Int, Number) and also Date/DateTime/Time/Duration
18    because Excel stores these as serial numbers. (If this diverges from desired behavior,
19    adjust by removing temporal variants.)
20  - ISBLANK is TRUE only for truly empty cells (LiteralValue::Empty), NOT for empty string "".
21  - ISERROR matches all error kinds; ISERR excludes #N/A.
22  - TYPE codes (Excel): 1 Number, 2 Text, 4 Logical, 16 Error, 64 Array. Blank coerces to 1.
23    Date/DateTime/Time/Duration mapped to 1 (numeric) for now.
24  - NA() returns the canonical #N/A error.
25  - N(value) coercion (Excel): number -> itself; date/time -> serial; TRUE->1, FALSE->0; text->0;
26    error -> propagates error; empty -> 0; other (array) -> first element via implicit (TODO) currently returns 0 with TODO flag.
27  - T(value): if text -> text; if error -> error; else -> empty text "".
28  - ISFORMULA requires formula provenance metadata (not yet tracked). Returns FALSE always (unless
29    we detect a formula node later). Marked TODO.
30
31TODO(excel-nuance): Implement implicit intersection for N() over arrays if/when model finalised.
32TODO(excel-nuance): Track formula provenance to support ISFORMULA.
33*/
34
35#[derive(Debug)]
36pub struct IsNumberFn;
37impl Function for IsNumberFn {
38    func_caps!(PURE);
39    fn name(&self) -> &'static str {
40        "ISNUMBER"
41    }
42    fn min_args(&self) -> usize {
43        1
44    }
45    fn arg_schema(&self) -> &'static [ArgSchema] {
46        &ARG_ANY_ONE[..]
47    }
48    fn eval<'a, 'b, 'c>(
49        &self,
50        args: &'c [ArgumentHandle<'a, 'b>],
51        _ctx: &dyn FunctionContext<'b>,
52    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
53        if args.len() != 1 {
54            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
55                ExcelError::new_value(),
56            )));
57        }
58        let v = args[0].value()?.into_literal();
59        let is_num = matches!(
60            v,
61            LiteralValue::Int(_)
62                | LiteralValue::Number(_)
63                | LiteralValue::Date(_)
64                | LiteralValue::DateTime(_)
65                | LiteralValue::Time(_)
66                | LiteralValue::Duration(_)
67        );
68        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
69            is_num,
70        )))
71    }
72}
73
74#[derive(Debug)]
75pub struct IsTextFn;
76impl Function for IsTextFn {
77    func_caps!(PURE);
78    fn name(&self) -> &'static str {
79        "ISTEXT"
80    }
81    fn min_args(&self) -> usize {
82        1
83    }
84    fn arg_schema(&self) -> &'static [ArgSchema] {
85        &ARG_ANY_ONE[..]
86    }
87    fn eval<'a, 'b, 'c>(
88        &self,
89        args: &'c [ArgumentHandle<'a, 'b>],
90        _ctx: &dyn FunctionContext<'b>,
91    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
92        if args.len() != 1 {
93            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
94                ExcelError::new_value(),
95            )));
96        }
97        let v = args[0].value()?.into_literal();
98        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
99            matches!(v, LiteralValue::Text(_)),
100        )))
101    }
102}
103
104#[derive(Debug)]
105pub struct IsLogicalFn;
106impl Function for IsLogicalFn {
107    func_caps!(PURE);
108    fn name(&self) -> &'static str {
109        "ISLOGICAL"
110    }
111    fn min_args(&self) -> usize {
112        1
113    }
114    fn arg_schema(&self) -> &'static [ArgSchema] {
115        &ARG_ANY_ONE[..]
116    }
117    fn eval<'a, 'b, 'c>(
118        &self,
119        args: &'c [ArgumentHandle<'a, 'b>],
120        _ctx: &dyn FunctionContext<'b>,
121    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
122        if args.len() != 1 {
123            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
124                ExcelError::new_value(),
125            )));
126        }
127        let v = args[0].value()?.into_literal();
128        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
129            matches!(v, LiteralValue::Boolean(_)),
130        )))
131    }
132}
133
134#[derive(Debug)]
135pub struct IsBlankFn;
136impl Function for IsBlankFn {
137    func_caps!(PURE);
138    fn name(&self) -> &'static str {
139        "ISBLANK"
140    }
141    fn min_args(&self) -> usize {
142        1
143    }
144    fn arg_schema(&self) -> &'static [ArgSchema] {
145        &ARG_ANY_ONE[..]
146    }
147    fn eval<'a, 'b, 'c>(
148        &self,
149        args: &'c [ArgumentHandle<'a, 'b>],
150        _ctx: &dyn FunctionContext<'b>,
151    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
152        if args.len() != 1 {
153            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
154                ExcelError::new_value(),
155            )));
156        }
157        let v = args[0].value()?.into_literal();
158        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
159            matches!(v, LiteralValue::Empty),
160        )))
161    }
162}
163
164#[derive(Debug)]
165pub struct IsErrorFn; // TRUE for any error (#N/A included)
166impl Function for IsErrorFn {
167    func_caps!(PURE);
168    fn name(&self) -> &'static str {
169        "ISERROR"
170    }
171    fn min_args(&self) -> usize {
172        1
173    }
174    fn arg_schema(&self) -> &'static [ArgSchema] {
175        &ARG_ANY_ONE[..]
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() != 1 {
183            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
184                ExcelError::new_value(),
185            )));
186        }
187        let v = args[0].value()?.into_literal();
188        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
189            matches!(v, LiteralValue::Error(_)),
190        )))
191    }
192}
193
194#[derive(Debug)]
195pub struct IsErrFn; // TRUE for any error except #N/A
196impl Function for IsErrFn {
197    func_caps!(PURE);
198    fn name(&self) -> &'static str {
199        "ISERR"
200    }
201    fn min_args(&self) -> usize {
202        1
203    }
204    fn arg_schema(&self) -> &'static [ArgSchema] {
205        &ARG_ANY_ONE[..]
206    }
207    fn eval<'a, 'b, 'c>(
208        &self,
209        args: &'c [ArgumentHandle<'a, 'b>],
210        _ctx: &dyn FunctionContext<'b>,
211    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
212        if args.len() != 1 {
213            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
214                ExcelError::new_value(),
215            )));
216        }
217        let v = args[0].value()?.into_literal();
218        let is_err = match v {
219            LiteralValue::Error(e) => e.kind != ExcelErrorKind::Na,
220            _ => false,
221        };
222        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
223            is_err,
224        )))
225    }
226}
227
228#[derive(Debug)]
229pub struct IsNaFn; // TRUE only for #N/A
230impl Function for IsNaFn {
231    func_caps!(PURE);
232    fn name(&self) -> &'static str {
233        "ISNA"
234    }
235    fn min_args(&self) -> usize {
236        1
237    }
238    fn arg_schema(&self) -> &'static [ArgSchema] {
239        &ARG_ANY_ONE[..]
240    }
241    fn eval<'a, 'b, 'c>(
242        &self,
243        args: &'c [ArgumentHandle<'a, 'b>],
244        _ctx: &dyn FunctionContext<'b>,
245    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
246        if args.len() != 1 {
247            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
248                ExcelError::new_value(),
249            )));
250        }
251        let v = args[0].value()?.into_literal();
252        let is_na = matches!(v, LiteralValue::Error(e) if e.kind==ExcelErrorKind::Na);
253        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
254            is_na,
255        )))
256    }
257}
258
259#[derive(Debug)]
260pub struct IsFormulaFn; // Requires provenance tracking (not yet) => always FALSE.
261impl Function for IsFormulaFn {
262    func_caps!(PURE);
263    fn name(&self) -> &'static str {
264        "ISFORMULA"
265    }
266    fn min_args(&self) -> usize {
267        1
268    }
269    fn arg_schema(&self) -> &'static [ArgSchema] {
270        &ARG_ANY_ONE[..]
271    }
272    fn eval<'a, 'b, 'c>(
273        &self,
274        args: &'c [ArgumentHandle<'a, 'b>],
275        _ctx: &dyn FunctionContext<'b>,
276    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
277        if args.len() != 1 {
278            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
279                ExcelError::new_value(),
280            )));
281        }
282        // TODO(excel-nuance): formula provenance once AST metadata is plumbed.
283        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
284            false,
285        )))
286    }
287}
288
289#[derive(Debug)]
290pub struct TypeFn;
291impl Function for TypeFn {
292    func_caps!(PURE);
293    fn name(&self) -> &'static str {
294        "TYPE"
295    }
296    fn min_args(&self) -> usize {
297        1
298    }
299    fn arg_schema(&self) -> &'static [ArgSchema] {
300        &ARG_ANY_ONE[..]
301    }
302    fn eval<'a, 'b, 'c>(
303        &self,
304        args: &'c [ArgumentHandle<'a, 'b>],
305        _ctx: &dyn FunctionContext<'b>,
306    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
307        if args.len() != 1 {
308            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
309                ExcelError::new_value(),
310            )));
311        }
312        let v = args[0].value()?.into_literal(); // Propagate errors directly
313        if let LiteralValue::Error(e) = v {
314            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
315        }
316        let code = match v {
317            LiteralValue::Int(_)
318            | LiteralValue::Number(_)
319            | LiteralValue::Empty
320            | LiteralValue::Date(_)
321            | LiteralValue::DateTime(_)
322            | LiteralValue::Time(_)
323            | LiteralValue::Duration(_) => 1,
324            LiteralValue::Text(_) => 2,
325            LiteralValue::Boolean(_) => 4,
326            LiteralValue::Array(_) => 64,
327            LiteralValue::Error(_) => unreachable!(),
328            LiteralValue::Pending => 1, // treat as blank/zero numeric; may change
329        };
330        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(code)))
331    }
332}
333
334#[derive(Debug)]
335pub struct NaFn; // NA() -> #N/A error
336impl Function for NaFn {
337    func_caps!(PURE);
338    fn name(&self) -> &'static str {
339        "NA"
340    }
341    fn min_args(&self) -> usize {
342        0
343    }
344    fn eval<'a, 'b, 'c>(
345        &self,
346        _args: &'c [ArgumentHandle<'a, 'b>],
347        _ctx: &dyn FunctionContext<'b>,
348    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
349        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
350            ExcelError::new(ExcelErrorKind::Na),
351        )))
352    }
353}
354
355#[derive(Debug)]
356pub struct NFn; // N(value)
357impl Function for NFn {
358    func_caps!(PURE);
359    fn name(&self) -> &'static str {
360        "N"
361    }
362    fn min_args(&self) -> usize {
363        1
364    }
365    fn arg_schema(&self) -> &'static [ArgSchema] {
366        &ARG_ANY_ONE[..]
367    }
368    fn eval<'a, 'b, 'c>(
369        &self,
370        args: &'c [ArgumentHandle<'a, 'b>],
371        _ctx: &dyn FunctionContext<'b>,
372    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
373        if args.len() != 1 {
374            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
375                ExcelError::new_value(),
376            )));
377        }
378        let v = args[0].value()?.into_literal();
379        match v {
380            LiteralValue::Int(i) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(i))),
381            LiteralValue::Number(n) => {
382                Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(n)))
383            }
384            LiteralValue::Date(_)
385            | LiteralValue::DateTime(_)
386            | LiteralValue::Time(_)
387            | LiteralValue::Duration(_) => {
388                // Convert via serial number helper
389                if let Some(serial) = v.as_serial_number() {
390                    Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
391                        serial,
392                    )))
393                } else {
394                    Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0)))
395                }
396            }
397            LiteralValue::Boolean(b) => {
398                Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(if b {
399                    1
400                } else {
401                    0
402                })))
403            }
404            LiteralValue::Text(_) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
405            LiteralValue::Empty => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
406            LiteralValue::Array(_) => {
407                // TODO(excel-nuance): implicit intersection; for now return 0
408                Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0)))
409            }
410            LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
411            LiteralValue::Pending => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0))),
412        }
413    }
414}
415
416#[derive(Debug)]
417pub struct TFn; // T(value)
418impl Function for TFn {
419    func_caps!(PURE);
420    fn name(&self) -> &'static str {
421        "T"
422    }
423    fn min_args(&self) -> usize {
424        1
425    }
426    fn arg_schema(&self) -> &'static [ArgSchema] {
427        &ARG_ANY_ONE[..]
428    }
429    fn eval<'a, 'b, 'c>(
430        &self,
431        args: &'c [ArgumentHandle<'a, 'b>],
432        _ctx: &dyn FunctionContext<'b>,
433    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
434        if args.len() != 1 {
435            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
436                ExcelError::new_value(),
437            )));
438        }
439        let v = args[0].value()?.into_literal();
440        match v {
441            LiteralValue::Text(s) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(s))),
442            LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
443            _ => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
444                String::new(),
445            ))),
446        }
447    }
448}
449
450/// ISEVEN(number) - Returns TRUE if number is even
451#[derive(Debug)]
452pub struct IsEvenFn;
453impl Function for IsEvenFn {
454    func_caps!(PURE);
455    fn name(&self) -> &'static str {
456        "ISEVEN"
457    }
458    fn min_args(&self) -> usize {
459        1
460    }
461    fn arg_schema(&self) -> &'static [ArgSchema] {
462        &ARG_ANY_ONE[..]
463    }
464    fn eval<'a, 'b, 'c>(
465        &self,
466        args: &'c [ArgumentHandle<'a, 'b>],
467        _ctx: &dyn FunctionContext<'b>,
468    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
469        if args.len() != 1 {
470            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
471                ExcelError::new_value(),
472            )));
473        }
474        let v = args[0].value()?.into_literal();
475        let n = match v {
476            LiteralValue::Error(e) => {
477                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
478            }
479            LiteralValue::Int(i) => i as f64,
480            LiteralValue::Number(n) => n,
481            LiteralValue::Boolean(b) => {
482                if b {
483                    1.0
484                } else {
485                    0.0
486                }
487            }
488            _ => {
489                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
490                    ExcelError::new_value(),
491                )));
492            }
493        };
494        // Excel truncates to integer first
495        let n = n.trunc() as i64;
496        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
497            n % 2 == 0,
498        )))
499    }
500}
501
502/// ISODD(number) - Returns TRUE if number is odd
503#[derive(Debug)]
504pub struct IsOddFn;
505impl Function for IsOddFn {
506    func_caps!(PURE);
507    fn name(&self) -> &'static str {
508        "ISODD"
509    }
510    fn min_args(&self) -> usize {
511        1
512    }
513    fn arg_schema(&self) -> &'static [ArgSchema] {
514        &ARG_ANY_ONE[..]
515    }
516    fn eval<'a, 'b, 'c>(
517        &self,
518        args: &'c [ArgumentHandle<'a, 'b>],
519        _ctx: &dyn FunctionContext<'b>,
520    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
521        if args.len() != 1 {
522            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
523                ExcelError::new_value(),
524            )));
525        }
526        let v = args[0].value()?.into_literal();
527        let n = match v {
528            LiteralValue::Error(e) => {
529                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
530            }
531            LiteralValue::Int(i) => i as f64,
532            LiteralValue::Number(n) => n,
533            LiteralValue::Boolean(b) => {
534                if b {
535                    1.0
536                } else {
537                    0.0
538                }
539            }
540            _ => {
541                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
542                    ExcelError::new_value(),
543                )));
544            }
545        };
546        let n = n.trunc() as i64;
547        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Boolean(
548            n % 2 != 0,
549        )))
550    }
551}
552
553/// ERROR.TYPE(error_val) - Returns a number corresponding to an error type
554/// Returns:
555///   1 = #NULL!
556///   2 = #DIV/0!
557///   3 = #VALUE!
558///   4 = #REF!
559///   5 = #NAME?
560///   6 = #NUM!
561///   7 = #N/A
562///   8 = #GETTING_DATA (not commonly used)
563///   #N/A if the value is not an error
564///
565/// NOTE: Error codes 9-13 are non-standard extensions for internal error types.
566#[derive(Debug)]
567pub struct ErrorTypeFn;
568impl Function for ErrorTypeFn {
569    func_caps!(PURE);
570    fn name(&self) -> &'static str {
571        "ERROR.TYPE"
572    }
573    fn min_args(&self) -> usize {
574        1
575    }
576    fn arg_schema(&self) -> &'static [ArgSchema] {
577        &ARG_ANY_ONE[..]
578    }
579    fn eval<'a, 'b, 'c>(
580        &self,
581        args: &'c [ArgumentHandle<'a, 'b>],
582        _ctx: &dyn FunctionContext<'b>,
583    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
584        if args.len() != 1 {
585            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
586                ExcelError::new_value(),
587            )));
588        }
589        let v = args[0].value()?.into_literal();
590        match v {
591            LiteralValue::Error(e) => {
592                let code = match e.kind {
593                    ExcelErrorKind::Null => 1,
594                    ExcelErrorKind::Div => 2,
595                    ExcelErrorKind::Value => 3,
596                    ExcelErrorKind::Ref => 4,
597                    ExcelErrorKind::Name => 5,
598                    ExcelErrorKind::Num => 6,
599                    ExcelErrorKind::Na => 7,
600                    ExcelErrorKind::Error => 8,
601                    // Non-standard extensions (codes 9-13)
602                    ExcelErrorKind::NImpl => 9,
603                    ExcelErrorKind::Spill => 10,
604                    ExcelErrorKind::Calc => 11,
605                    ExcelErrorKind::Circ => 12,
606                    ExcelErrorKind::Cancelled => 13,
607                };
608                Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(code)))
609            }
610            _ => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
611                ExcelError::new_na(),
612            ))),
613        }
614    }
615}
616
617pub fn register_builtins() {
618    use std::sync::Arc;
619    crate::function_registry::register_function(Arc::new(IsNumberFn));
620    crate::function_registry::register_function(Arc::new(IsTextFn));
621    crate::function_registry::register_function(Arc::new(IsLogicalFn));
622    crate::function_registry::register_function(Arc::new(IsBlankFn));
623    crate::function_registry::register_function(Arc::new(IsErrorFn));
624    crate::function_registry::register_function(Arc::new(IsErrFn));
625    crate::function_registry::register_function(Arc::new(IsNaFn));
626    crate::function_registry::register_function(Arc::new(IsFormulaFn));
627    crate::function_registry::register_function(Arc::new(IsEvenFn));
628    crate::function_registry::register_function(Arc::new(IsOddFn));
629    crate::function_registry::register_function(Arc::new(ErrorTypeFn));
630    crate::function_registry::register_function(Arc::new(TypeFn));
631    crate::function_registry::register_function(Arc::new(NaFn));
632    crate::function_registry::register_function(Arc::new(NFn));
633    crate::function_registry::register_function(Arc::new(TFn));
634}
635
636#[cfg(test)]
637mod tests {
638    use super::*;
639    use crate::test_workbook::TestWorkbook;
640    use formualizer_parse::parser::{ASTNode, ASTNodeType};
641    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
642        wb.interpreter()
643    }
644
645    #[test]
646    fn isnumber_numeric_and_date() {
647        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsNumberFn));
648        let ctx = interp(&wb);
649        let f = ctx.context.get_function("", "ISNUMBER").unwrap();
650        let num = ASTNode::new(
651            ASTNodeType::Literal(LiteralValue::Number(std::f64::consts::PI)),
652            None,
653        );
654        let date = ASTNode::new(
655            ASTNodeType::Literal(LiteralValue::Date(
656                chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
657            )),
658            None,
659        );
660        let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("x".into())), None);
661        let args_num = vec![crate::traits::ArgumentHandle::new(&num, &ctx)];
662        let args_date = vec![crate::traits::ArgumentHandle::new(&date, &ctx)];
663        let args_txt = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
664        assert_eq!(
665            f.dispatch(&args_num, &ctx.function_context(None))
666                .unwrap()
667                .into_literal(),
668            LiteralValue::Boolean(true)
669        );
670        assert_eq!(
671            f.dispatch(&args_date, &ctx.function_context(None))
672                .unwrap()
673                .into_literal(),
674            LiteralValue::Boolean(true)
675        );
676        assert_eq!(
677            f.dispatch(&args_txt, &ctx.function_context(None))
678                .unwrap()
679                .into_literal(),
680            LiteralValue::Boolean(false)
681        );
682    }
683
684    #[test]
685    fn istest_and_isblank() {
686        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsTextFn));
687        let ctx = interp(&wb);
688        let f = ctx.context.get_function("", "ISTEXT").unwrap();
689        let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
690        let n = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
691        let args_t = vec![crate::traits::ArgumentHandle::new(&t, &ctx)];
692        let args_n = vec![crate::traits::ArgumentHandle::new(&n, &ctx)];
693        assert_eq!(
694            f.dispatch(&args_t, &ctx.function_context(None))
695                .unwrap()
696                .into_literal(),
697            LiteralValue::Boolean(true)
698        );
699        assert_eq!(
700            f.dispatch(&args_n, &ctx.function_context(None))
701                .unwrap()
702                .into_literal(),
703            LiteralValue::Boolean(false)
704        );
705
706        // ISBLANK
707        let wb2 = TestWorkbook::new().with_function(std::sync::Arc::new(IsBlankFn));
708        let ctx2 = interp(&wb2);
709        let f2 = ctx2.context.get_function("", "ISBLANK").unwrap();
710        let blank = ASTNode::new(ASTNodeType::Literal(LiteralValue::Empty), None);
711        let blank_args = vec![crate::traits::ArgumentHandle::new(&blank, &ctx2)];
712        assert_eq!(
713            f2.dispatch(&blank_args, &ctx2.function_context(None))
714                .unwrap()
715                .into_literal(),
716            LiteralValue::Boolean(true)
717        );
718    }
719
720    #[test]
721    fn iserror_variants() {
722        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsErrorFn));
723        let ctx = interp(&wb);
724        let f = ctx.context.get_function("", "ISERROR").unwrap();
725        let err = ASTNode::new(
726            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div))),
727            None,
728        );
729        let ok = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
730        let a_err = vec![crate::traits::ArgumentHandle::new(&err, &ctx)];
731        let a_ok = vec![crate::traits::ArgumentHandle::new(&ok, &ctx)];
732        assert_eq!(
733            f.dispatch(&a_err, &ctx.function_context(None))
734                .unwrap()
735                .into_literal(),
736            LiteralValue::Boolean(true)
737        );
738        assert_eq!(
739            f.dispatch(&a_ok, &ctx.function_context(None))
740                .unwrap()
741                .into_literal(),
742            LiteralValue::Boolean(false)
743        );
744    }
745
746    #[test]
747    fn type_codes_basic() {
748        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TypeFn));
749        let ctx = interp(&wb);
750        let f = ctx.context.get_function("", "TYPE").unwrap();
751        let v_num = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
752        let v_txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("hi".into())), None);
753        let v_bool = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
754        let v_err = ASTNode::new(
755            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
756            None,
757        );
758        let v_arr = ASTNode::new(
759            ASTNodeType::Literal(LiteralValue::Array(vec![vec![LiteralValue::Int(1)]])),
760            None,
761        );
762        let a_num = vec![crate::traits::ArgumentHandle::new(&v_num, &ctx)];
763        let a_txt = vec![crate::traits::ArgumentHandle::new(&v_txt, &ctx)];
764        let a_bool = vec![crate::traits::ArgumentHandle::new(&v_bool, &ctx)];
765        let a_err = vec![crate::traits::ArgumentHandle::new(&v_err, &ctx)];
766        let a_arr = vec![crate::traits::ArgumentHandle::new(&v_arr, &ctx)];
767        assert_eq!(
768            f.dispatch(&a_num, &ctx.function_context(None))
769                .unwrap()
770                .into_literal(),
771            LiteralValue::Int(1)
772        );
773        assert_eq!(
774            f.dispatch(&a_txt, &ctx.function_context(None))
775                .unwrap()
776                .into_literal(),
777            LiteralValue::Int(2)
778        );
779        assert_eq!(
780            f.dispatch(&a_bool, &ctx.function_context(None))
781                .unwrap()
782                .into_literal(),
783            LiteralValue::Int(4)
784        );
785        match f
786            .dispatch(&a_err, &ctx.function_context(None))
787            .unwrap()
788            .into_literal()
789        {
790            LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
791            _ => panic!(),
792        }
793        assert_eq!(
794            f.dispatch(&a_arr, &ctx.function_context(None))
795                .unwrap()
796                .into_literal(),
797            LiteralValue::Int(64)
798        );
799    }
800
801    #[test]
802    fn na_and_n_and_t() {
803        let wb = TestWorkbook::new()
804            .with_function(std::sync::Arc::new(NaFn))
805            .with_function(std::sync::Arc::new(NFn))
806            .with_function(std::sync::Arc::new(TFn));
807        let ctx = wb.interpreter();
808        // NA()
809        let na_fn = ctx.context.get_function("", "NA").unwrap();
810        match na_fn
811            .eval(&[], &ctx.function_context(None))
812            .unwrap()
813            .into_literal()
814        {
815            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
816            _ => panic!(),
817        }
818        // N()
819        let n_fn = ctx.context.get_function("", "N").unwrap();
820        let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
821        let args = vec![crate::traits::ArgumentHandle::new(&val, &ctx)];
822        assert_eq!(
823            n_fn.dispatch(&args, &ctx.function_context(None))
824                .unwrap()
825                .into_literal(),
826            LiteralValue::Int(1)
827        );
828        // T()
829        let t_fn = ctx.context.get_function("", "T").unwrap();
830        let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
831        let args_t = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
832        assert_eq!(
833            t_fn.dispatch(&args_t, &ctx.function_context(None))
834                .unwrap()
835                .into_literal(),
836            LiteralValue::Text("abc".into())
837        );
838    }
839}