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#[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_scalar<'a, 'b>(
49 &self,
50 args: &'a [ArgumentHandle<'a, 'b>],
51 _ctx: &dyn FunctionContext,
52 ) -> Result<LiteralValue, ExcelError> {
53 if args.len() != 1 {
54 return Ok(LiteralValue::Error(ExcelError::new_value()));
55 }
56 let v = args[0].value()?;
57 let is_num = matches!(
58 v.as_ref(),
59 LiteralValue::Int(_)
60 | LiteralValue::Number(_)
61 | LiteralValue::Date(_)
62 | LiteralValue::DateTime(_)
63 | LiteralValue::Time(_)
64 | LiteralValue::Duration(_)
65 );
66 Ok(LiteralValue::Boolean(is_num))
67 }
68}
69
70#[derive(Debug)]
71pub struct IsTextFn;
72impl Function for IsTextFn {
73 func_caps!(PURE);
74 fn name(&self) -> &'static str {
75 "ISTEXT"
76 }
77 fn min_args(&self) -> usize {
78 1
79 }
80 fn arg_schema(&self) -> &'static [ArgSchema] {
81 &ARG_ANY_ONE[..]
82 }
83 fn eval_scalar<'a, 'b>(
84 &self,
85 args: &'a [ArgumentHandle<'a, 'b>],
86 _ctx: &dyn FunctionContext,
87 ) -> Result<LiteralValue, ExcelError> {
88 if args.len() != 1 {
89 return Ok(LiteralValue::Error(ExcelError::new_value()));
90 }
91 let v = args[0].value()?;
92 Ok(LiteralValue::Boolean(matches!(
93 v.as_ref(),
94 LiteralValue::Text(_)
95 )))
96 }
97}
98
99#[derive(Debug)]
100pub struct IsLogicalFn;
101impl Function for IsLogicalFn {
102 func_caps!(PURE);
103 fn name(&self) -> &'static str {
104 "ISLOGICAL"
105 }
106 fn min_args(&self) -> usize {
107 1
108 }
109 fn arg_schema(&self) -> &'static [ArgSchema] {
110 &ARG_ANY_ONE[..]
111 }
112 fn eval_scalar<'a, 'b>(
113 &self,
114 args: &'a [ArgumentHandle<'a, 'b>],
115 _ctx: &dyn FunctionContext,
116 ) -> Result<LiteralValue, ExcelError> {
117 if args.len() != 1 {
118 return Ok(LiteralValue::Error(ExcelError::new_value()));
119 }
120 let v = args[0].value()?;
121 Ok(LiteralValue::Boolean(matches!(
122 v.as_ref(),
123 LiteralValue::Boolean(_)
124 )))
125 }
126}
127
128#[derive(Debug)]
129pub struct IsBlankFn;
130impl Function for IsBlankFn {
131 func_caps!(PURE);
132 fn name(&self) -> &'static str {
133 "ISBLANK"
134 }
135 fn min_args(&self) -> usize {
136 1
137 }
138 fn arg_schema(&self) -> &'static [ArgSchema] {
139 &ARG_ANY_ONE[..]
140 }
141 fn eval_scalar<'a, 'b>(
142 &self,
143 args: &'a [ArgumentHandle<'a, 'b>],
144 _ctx: &dyn FunctionContext,
145 ) -> Result<LiteralValue, ExcelError> {
146 if args.len() != 1 {
147 return Ok(LiteralValue::Error(ExcelError::new_value()));
148 }
149 let v = args[0].value()?;
150 Ok(LiteralValue::Boolean(matches!(
151 v.as_ref(),
152 LiteralValue::Empty
153 )))
154 }
155}
156
157#[derive(Debug)]
158pub struct IsErrorFn; impl Function for IsErrorFn {
160 func_caps!(PURE);
161 fn name(&self) -> &'static str {
162 "ISERROR"
163 }
164 fn min_args(&self) -> usize {
165 1
166 }
167 fn arg_schema(&self) -> &'static [ArgSchema] {
168 &ARG_ANY_ONE[..]
169 }
170 fn eval_scalar<'a, 'b>(
171 &self,
172 args: &'a [ArgumentHandle<'a, 'b>],
173 _ctx: &dyn FunctionContext,
174 ) -> Result<LiteralValue, ExcelError> {
175 if args.len() != 1 {
176 return Ok(LiteralValue::Error(ExcelError::new_value()));
177 }
178 let v = args[0].value()?;
179 Ok(LiteralValue::Boolean(matches!(
180 v.as_ref(),
181 LiteralValue::Error(_)
182 )))
183 }
184}
185
186#[derive(Debug)]
187pub struct IsErrFn; impl Function for IsErrFn {
189 func_caps!(PURE);
190 fn name(&self) -> &'static str {
191 "ISERR"
192 }
193 fn min_args(&self) -> usize {
194 1
195 }
196 fn arg_schema(&self) -> &'static [ArgSchema] {
197 &ARG_ANY_ONE[..]
198 }
199 fn eval_scalar<'a, 'b>(
200 &self,
201 args: &'a [ArgumentHandle<'a, 'b>],
202 _ctx: &dyn FunctionContext,
203 ) -> Result<LiteralValue, ExcelError> {
204 if args.len() != 1 {
205 return Ok(LiteralValue::Error(ExcelError::new_value()));
206 }
207 let v = args[0].value()?;
208 let is_err = match v.as_ref() {
209 LiteralValue::Error(e) => e.kind != ExcelErrorKind::Na,
210 _ => false,
211 };
212 Ok(LiteralValue::Boolean(is_err))
213 }
214}
215
216#[derive(Debug)]
217pub struct IsNaFn; impl Function for IsNaFn {
219 func_caps!(PURE);
220 fn name(&self) -> &'static str {
221 "ISNA"
222 }
223 fn min_args(&self) -> usize {
224 1
225 }
226 fn arg_schema(&self) -> &'static [ArgSchema] {
227 &ARG_ANY_ONE[..]
228 }
229 fn eval_scalar<'a, 'b>(
230 &self,
231 args: &'a [ArgumentHandle<'a, 'b>],
232 _ctx: &dyn FunctionContext,
233 ) -> Result<LiteralValue, ExcelError> {
234 if args.len() != 1 {
235 return Ok(LiteralValue::Error(ExcelError::new_value()));
236 }
237 let v = args[0].value()?;
238 let is_na = matches!(v.as_ref(), LiteralValue::Error(e) if e.kind==ExcelErrorKind::Na);
239 Ok(LiteralValue::Boolean(is_na))
240 }
241}
242
243#[derive(Debug)]
244pub struct IsFormulaFn; impl Function for IsFormulaFn {
246 func_caps!(PURE);
247 fn name(&self) -> &'static str {
248 "ISFORMULA"
249 }
250 fn min_args(&self) -> usize {
251 1
252 }
253 fn arg_schema(&self) -> &'static [ArgSchema] {
254 &ARG_ANY_ONE[..]
255 }
256 fn eval_scalar<'a, 'b>(
257 &self,
258 args: &'a [ArgumentHandle<'a, 'b>],
259 _ctx: &dyn FunctionContext,
260 ) -> Result<LiteralValue, ExcelError> {
261 if args.len() != 1 {
262 return Ok(LiteralValue::Error(ExcelError::new_value()));
263 }
264 Ok(LiteralValue::Boolean(false))
266 }
267}
268
269#[derive(Debug)]
270pub struct TypeFn;
271impl Function for TypeFn {
272 func_caps!(PURE);
273 fn name(&self) -> &'static str {
274 "TYPE"
275 }
276 fn min_args(&self) -> usize {
277 1
278 }
279 fn arg_schema(&self) -> &'static [ArgSchema] {
280 &ARG_ANY_ONE[..]
281 }
282 fn eval_scalar<'a, 'b>(
283 &self,
284 args: &'a [ArgumentHandle<'a, 'b>],
285 _ctx: &dyn FunctionContext,
286 ) -> Result<LiteralValue, ExcelError> {
287 if args.len() != 1 {
288 return Ok(LiteralValue::Error(ExcelError::new_value()));
289 }
290 let v = args[0].value()?; if let LiteralValue::Error(e) = v.as_ref() {
292 return Ok(LiteralValue::Error(e.clone()));
293 }
294 let code = match v.as_ref() {
295 LiteralValue::Int(_)
296 | LiteralValue::Number(_)
297 | LiteralValue::Empty
298 | LiteralValue::Date(_)
299 | LiteralValue::DateTime(_)
300 | LiteralValue::Time(_)
301 | LiteralValue::Duration(_) => 1,
302 LiteralValue::Text(_) => 2,
303 LiteralValue::Boolean(_) => 4,
304 LiteralValue::Array(_) => 64,
305 LiteralValue::Error(_) => unreachable!(),
306 LiteralValue::Pending => 1, };
308 Ok(LiteralValue::Int(code))
309 }
310}
311
312#[derive(Debug)]
313pub struct NaFn; impl Function for NaFn {
315 func_caps!(PURE);
316 fn name(&self) -> &'static str {
317 "NA"
318 }
319 fn min_args(&self) -> usize {
320 0
321 }
322 fn eval_scalar<'a, 'b>(
323 &self,
324 _args: &'a [ArgumentHandle<'a, 'b>],
325 _ctx: &dyn FunctionContext,
326 ) -> Result<LiteralValue, ExcelError> {
327 Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)))
328 }
329}
330
331#[derive(Debug)]
332pub struct NFn; impl Function for NFn {
334 func_caps!(PURE);
335 fn name(&self) -> &'static str {
336 "N"
337 }
338 fn min_args(&self) -> usize {
339 1
340 }
341 fn arg_schema(&self) -> &'static [ArgSchema] {
342 &ARG_ANY_ONE[..]
343 }
344 fn eval_scalar<'a, 'b>(
345 &self,
346 args: &'a [ArgumentHandle<'a, 'b>],
347 _ctx: &dyn FunctionContext,
348 ) -> Result<LiteralValue, ExcelError> {
349 if args.len() != 1 {
350 return Ok(LiteralValue::Error(ExcelError::new_value()));
351 }
352 let v = args[0].value()?;
353 match v.as_ref() {
354 LiteralValue::Int(i) => Ok(LiteralValue::Int(*i)),
355 LiteralValue::Number(n) => Ok(LiteralValue::Number(*n)),
356 LiteralValue::Date(_)
357 | LiteralValue::DateTime(_)
358 | LiteralValue::Time(_)
359 | LiteralValue::Duration(_) => {
360 if let Some(serial) = v.as_ref().as_serial_number() {
362 Ok(LiteralValue::Number(serial))
363 } else {
364 Ok(LiteralValue::Int(0))
365 }
366 }
367 LiteralValue::Boolean(b) => Ok(LiteralValue::Int(if *b { 1 } else { 0 })),
368 LiteralValue::Text(_) => Ok(LiteralValue::Int(0)),
369 LiteralValue::Empty => Ok(LiteralValue::Int(0)),
370 LiteralValue::Array(_) => {
371 Ok(LiteralValue::Int(0))
373 }
374 LiteralValue::Error(e) => Ok(LiteralValue::Error(e.clone())),
375 LiteralValue::Pending => Ok(LiteralValue::Int(0)),
376 }
377 }
378}
379
380#[derive(Debug)]
381pub struct TFn; impl Function for TFn {
383 func_caps!(PURE);
384 fn name(&self) -> &'static str {
385 "T"
386 }
387 fn min_args(&self) -> usize {
388 1
389 }
390 fn arg_schema(&self) -> &'static [ArgSchema] {
391 &ARG_ANY_ONE[..]
392 }
393 fn eval_scalar<'a, 'b>(
394 &self,
395 args: &'a [ArgumentHandle<'a, 'b>],
396 _ctx: &dyn FunctionContext,
397 ) -> Result<LiteralValue, ExcelError> {
398 if args.len() != 1 {
399 return Ok(LiteralValue::Error(ExcelError::new_value()));
400 }
401 let v = args[0].value()?;
402 match v.as_ref() {
403 LiteralValue::Text(s) => Ok(LiteralValue::Text(s.clone())),
404 LiteralValue::Error(e) => Ok(LiteralValue::Error(e.clone())),
405 _ => Ok(LiteralValue::Text(String::new())),
406 }
407 }
408}
409
410pub fn register_builtins() {
411 use std::sync::Arc;
412 crate::function_registry::register_function(Arc::new(IsNumberFn));
413 crate::function_registry::register_function(Arc::new(IsTextFn));
414 crate::function_registry::register_function(Arc::new(IsLogicalFn));
415 crate::function_registry::register_function(Arc::new(IsBlankFn));
416 crate::function_registry::register_function(Arc::new(IsErrorFn));
417 crate::function_registry::register_function(Arc::new(IsErrFn));
418 crate::function_registry::register_function(Arc::new(IsNaFn));
419 crate::function_registry::register_function(Arc::new(IsFormulaFn));
420 crate::function_registry::register_function(Arc::new(TypeFn));
421 crate::function_registry::register_function(Arc::new(NaFn));
422 crate::function_registry::register_function(Arc::new(NFn));
423 crate::function_registry::register_function(Arc::new(TFn));
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::test_workbook::TestWorkbook;
430 use formualizer_parse::parser::{ASTNode, ASTNodeType};
431 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
432 wb.interpreter()
433 }
434
435 #[test]
436 fn isnumber_numeric_and_date() {
437 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsNumberFn));
438 let ctx = interp(&wb);
439 let f = ctx.context.get_function("", "ISNUMBER").unwrap();
440 let num = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(3.14)), None);
441 let date = ASTNode::new(
442 ASTNodeType::Literal(LiteralValue::Date(
443 chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
444 )),
445 None,
446 );
447 let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("x".into())), None);
448 let args_num = vec![crate::traits::ArgumentHandle::new(&num, &ctx)];
449 let args_date = vec![crate::traits::ArgumentHandle::new(&date, &ctx)];
450 let args_txt = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
451 assert_eq!(
452 f.dispatch(&args_num, &ctx.function_context(None)).unwrap(),
453 LiteralValue::Boolean(true)
454 );
455 assert_eq!(
456 f.dispatch(&args_date, &ctx.function_context(None)).unwrap(),
457 LiteralValue::Boolean(true)
458 );
459 assert_eq!(
460 f.dispatch(&args_txt, &ctx.function_context(None)).unwrap(),
461 LiteralValue::Boolean(false)
462 );
463 }
464
465 #[test]
466 fn istest_and_isblank() {
467 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsTextFn));
468 let ctx = interp(&wb);
469 let f = ctx.context.get_function("", "ISTEXT").unwrap();
470 let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
471 let n = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
472 let args_t = vec![crate::traits::ArgumentHandle::new(&t, &ctx)];
473 let args_n = vec![crate::traits::ArgumentHandle::new(&n, &ctx)];
474 assert_eq!(
475 f.dispatch(&args_t, &ctx.function_context(None)).unwrap(),
476 LiteralValue::Boolean(true)
477 );
478 assert_eq!(
479 f.dispatch(&args_n, &ctx.function_context(None)).unwrap(),
480 LiteralValue::Boolean(false)
481 );
482
483 let wb2 = TestWorkbook::new().with_function(std::sync::Arc::new(IsBlankFn));
485 let ctx2 = interp(&wb2);
486 let f2 = ctx2.context.get_function("", "ISBLANK").unwrap();
487 let blank = ASTNode::new(ASTNodeType::Literal(LiteralValue::Empty), None);
488 let blank_args = vec![crate::traits::ArgumentHandle::new(&blank, &ctx2)];
489 assert_eq!(
490 f2.dispatch(&blank_args, &ctx2.function_context(None))
491 .unwrap(),
492 LiteralValue::Boolean(true)
493 );
494 }
495
496 #[test]
497 fn iserror_variants() {
498 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsErrorFn));
499 let ctx = interp(&wb);
500 let f = ctx.context.get_function("", "ISERROR").unwrap();
501 let err = ASTNode::new(
502 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div))),
503 None,
504 );
505 let ok = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
506 let a_err = vec![crate::traits::ArgumentHandle::new(&err, &ctx)];
507 let a_ok = vec![crate::traits::ArgumentHandle::new(&ok, &ctx)];
508 assert_eq!(
509 f.dispatch(&a_err, &ctx.function_context(None)).unwrap(),
510 LiteralValue::Boolean(true)
511 );
512 assert_eq!(
513 f.dispatch(&a_ok, &ctx.function_context(None)).unwrap(),
514 LiteralValue::Boolean(false)
515 );
516 }
517
518 #[test]
519 fn type_codes_basic() {
520 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TypeFn));
521 let ctx = interp(&wb);
522 let f = ctx.context.get_function("", "TYPE").unwrap();
523 let v_num = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
524 let v_txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("hi".into())), None);
525 let v_bool = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
526 let v_err = ASTNode::new(
527 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
528 None,
529 );
530 let v_arr = ASTNode::new(
531 ASTNodeType::Literal(LiteralValue::Array(vec![vec![LiteralValue::Int(1)]])),
532 None,
533 );
534 let a_num = vec![crate::traits::ArgumentHandle::new(&v_num, &ctx)];
535 let a_txt = vec![crate::traits::ArgumentHandle::new(&v_txt, &ctx)];
536 let a_bool = vec![crate::traits::ArgumentHandle::new(&v_bool, &ctx)];
537 let a_err = vec![crate::traits::ArgumentHandle::new(&v_err, &ctx)];
538 let a_arr = vec![crate::traits::ArgumentHandle::new(&v_arr, &ctx)];
539 assert_eq!(
540 f.dispatch(&a_num, &ctx.function_context(None)).unwrap(),
541 LiteralValue::Int(1)
542 );
543 assert_eq!(
544 f.dispatch(&a_txt, &ctx.function_context(None)).unwrap(),
545 LiteralValue::Int(2)
546 );
547 assert_eq!(
548 f.dispatch(&a_bool, &ctx.function_context(None)).unwrap(),
549 LiteralValue::Int(4)
550 );
551 match f.dispatch(&a_err, &ctx.function_context(None)).unwrap() {
552 LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
553 _ => panic!(),
554 }
555 assert_eq!(
556 f.dispatch(&a_arr, &ctx.function_context(None)).unwrap(),
557 LiteralValue::Int(64)
558 );
559 }
560
561 #[test]
562 fn na_and_n_and_t() {
563 let wb = TestWorkbook::new()
564 .with_function(std::sync::Arc::new(NaFn))
565 .with_function(std::sync::Arc::new(NFn))
566 .with_function(std::sync::Arc::new(TFn));
567 let ctx = wb.interpreter();
568 let na_fn = ctx.context.get_function("", "NA").unwrap();
570 match na_fn.eval_scalar(&[], &ctx.function_context(None)).unwrap() {
571 LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
572 _ => panic!(),
573 }
574 let n_fn = ctx.context.get_function("", "N").unwrap();
576 let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
577 let args = vec![crate::traits::ArgumentHandle::new(&val, &ctx)];
578 assert_eq!(
579 n_fn.dispatch(&args, &ctx.function_context(None)).unwrap(),
580 LiteralValue::Int(1)
581 );
582 let t_fn = ctx.context.get_function("", "T").unwrap();
584 let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
585 let args_t = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
586 assert_eq!(
587 t_fn.dispatch(&args_t, &ctx.function_context(None)).unwrap(),
588 LiteralValue::Text("abc".into())
589 );
590 }
591}