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(
441 ASTNodeType::Literal(LiteralValue::Number(std::f64::consts::PI)),
442 None,
443 );
444 let date = ASTNode::new(
445 ASTNodeType::Literal(LiteralValue::Date(
446 chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
447 )),
448 None,
449 );
450 let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("x".into())), None);
451 let args_num = vec![crate::traits::ArgumentHandle::new(&num, &ctx)];
452 let args_date = vec![crate::traits::ArgumentHandle::new(&date, &ctx)];
453 let args_txt = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
454 assert_eq!(
455 f.dispatch(&args_num, &ctx.function_context(None)).unwrap(),
456 LiteralValue::Boolean(true)
457 );
458 assert_eq!(
459 f.dispatch(&args_date, &ctx.function_context(None)).unwrap(),
460 LiteralValue::Boolean(true)
461 );
462 assert_eq!(
463 f.dispatch(&args_txt, &ctx.function_context(None)).unwrap(),
464 LiteralValue::Boolean(false)
465 );
466 }
467
468 #[test]
469 fn istest_and_isblank() {
470 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsTextFn));
471 let ctx = interp(&wb);
472 let f = ctx.context.get_function("", "ISTEXT").unwrap();
473 let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
474 let n = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(5)), None);
475 let args_t = vec![crate::traits::ArgumentHandle::new(&t, &ctx)];
476 let args_n = vec![crate::traits::ArgumentHandle::new(&n, &ctx)];
477 assert_eq!(
478 f.dispatch(&args_t, &ctx.function_context(None)).unwrap(),
479 LiteralValue::Boolean(true)
480 );
481 assert_eq!(
482 f.dispatch(&args_n, &ctx.function_context(None)).unwrap(),
483 LiteralValue::Boolean(false)
484 );
485
486 let wb2 = TestWorkbook::new().with_function(std::sync::Arc::new(IsBlankFn));
488 let ctx2 = interp(&wb2);
489 let f2 = ctx2.context.get_function("", "ISBLANK").unwrap();
490 let blank = ASTNode::new(ASTNodeType::Literal(LiteralValue::Empty), None);
491 let blank_args = vec![crate::traits::ArgumentHandle::new(&blank, &ctx2)];
492 assert_eq!(
493 f2.dispatch(&blank_args, &ctx2.function_context(None))
494 .unwrap(),
495 LiteralValue::Boolean(true)
496 );
497 }
498
499 #[test]
500 fn iserror_variants() {
501 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IsErrorFn));
502 let ctx = interp(&wb);
503 let f = ctx.context.get_function("", "ISERROR").unwrap();
504 let err = ASTNode::new(
505 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div))),
506 None,
507 );
508 let ok = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
509 let a_err = vec![crate::traits::ArgumentHandle::new(&err, &ctx)];
510 let a_ok = vec![crate::traits::ArgumentHandle::new(&ok, &ctx)];
511 assert_eq!(
512 f.dispatch(&a_err, &ctx.function_context(None)).unwrap(),
513 LiteralValue::Boolean(true)
514 );
515 assert_eq!(
516 f.dispatch(&a_ok, &ctx.function_context(None)).unwrap(),
517 LiteralValue::Boolean(false)
518 );
519 }
520
521 #[test]
522 fn type_codes_basic() {
523 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TypeFn));
524 let ctx = interp(&wb);
525 let f = ctx.context.get_function("", "TYPE").unwrap();
526 let v_num = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
527 let v_txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("hi".into())), None);
528 let v_bool = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
529 let v_err = ASTNode::new(
530 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
531 None,
532 );
533 let v_arr = ASTNode::new(
534 ASTNodeType::Literal(LiteralValue::Array(vec![vec![LiteralValue::Int(1)]])),
535 None,
536 );
537 let a_num = vec![crate::traits::ArgumentHandle::new(&v_num, &ctx)];
538 let a_txt = vec![crate::traits::ArgumentHandle::new(&v_txt, &ctx)];
539 let a_bool = vec![crate::traits::ArgumentHandle::new(&v_bool, &ctx)];
540 let a_err = vec![crate::traits::ArgumentHandle::new(&v_err, &ctx)];
541 let a_arr = vec![crate::traits::ArgumentHandle::new(&v_arr, &ctx)];
542 assert_eq!(
543 f.dispatch(&a_num, &ctx.function_context(None)).unwrap(),
544 LiteralValue::Int(1)
545 );
546 assert_eq!(
547 f.dispatch(&a_txt, &ctx.function_context(None)).unwrap(),
548 LiteralValue::Int(2)
549 );
550 assert_eq!(
551 f.dispatch(&a_bool, &ctx.function_context(None)).unwrap(),
552 LiteralValue::Int(4)
553 );
554 match f.dispatch(&a_err, &ctx.function_context(None)).unwrap() {
555 LiteralValue::Error(e) => assert_eq!(e, "#VALUE!"),
556 _ => panic!(),
557 }
558 assert_eq!(
559 f.dispatch(&a_arr, &ctx.function_context(None)).unwrap(),
560 LiteralValue::Int(64)
561 );
562 }
563
564 #[test]
565 fn na_and_n_and_t() {
566 let wb = TestWorkbook::new()
567 .with_function(std::sync::Arc::new(NaFn))
568 .with_function(std::sync::Arc::new(NFn))
569 .with_function(std::sync::Arc::new(TFn));
570 let ctx = wb.interpreter();
571 let na_fn = ctx.context.get_function("", "NA").unwrap();
573 match na_fn.eval_scalar(&[], &ctx.function_context(None)).unwrap() {
574 LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
575 _ => panic!(),
576 }
577 let n_fn = ctx.context.get_function("", "N").unwrap();
579 let val = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
580 let args = vec![crate::traits::ArgumentHandle::new(&val, &ctx)];
581 assert_eq!(
582 n_fn.dispatch(&args, &ctx.function_context(None)).unwrap(),
583 LiteralValue::Int(1)
584 );
585 let t_fn = ctx.context.get_function("", "T").unwrap();
587 let txt = ASTNode::new(ASTNodeType::Literal(LiteralValue::Text("abc".into())), None);
588 let args_t = vec![crate::traits::ArgumentHandle::new(&txt, &ctx)];
589 assert_eq!(
590 t_fn.dispatch(&args_t, &ctx.function_context(None)).unwrap(),
591 LiteralValue::Text("abc".into())
592 );
593 }
594}