1use super::super::utils::collapse_if_scalar;
23use super::lookup_utils::{cmp_for_lookup, equals_maybe_wildcard, value_to_f64_lenient};
24use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
25use crate::function::Function; use crate::traits::{ArgumentHandle, FunctionContext};
27use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
28use formualizer_macros::func_caps;
29use std::collections::HashMap;
30
31pub fn super_wildcard_match(pattern: &str, text: &str) -> bool {
34 #[derive(Clone, Copy, Debug)]
38 enum Token<'a> {
39 AnySeq,
40 AnyChar,
41 Lit(&'a str),
42 }
43 let mut tokens: Vec<Token> = Vec::new();
44 let mut i = 0;
45 let bytes = pattern.as_bytes();
46 let mut lit_start = 0;
47 while i < bytes.len() {
48 match bytes[i] {
49 b'~' => {
50 if i + 1 < bytes.len() {
52 if lit_start < i {
54 tokens.push(Token::Lit(&pattern[lit_start..i]));
55 }
56 tokens.push(Token::Lit(&pattern[i + 1..i + 2]));
57 i += 2;
58 lit_start = i;
59 } else {
60 i += 1;
62 }
63 }
64 b'*' => {
65 if lit_start < i {
66 tokens.push(Token::Lit(&pattern[lit_start..i]));
67 }
68 tokens.push(Token::AnySeq);
69 i += 1;
70 lit_start = i;
71 }
72 b'?' => {
73 if lit_start < i {
74 tokens.push(Token::Lit(&pattern[lit_start..i]));
75 }
76 tokens.push(Token::AnyChar);
77 i += 1;
78 lit_start = i;
79 }
80 _ => i += 1,
81 }
82 }
83 if lit_start < bytes.len() {
84 tokens.push(Token::Lit(&pattern[lit_start..]));
85 }
86 let mut compact: Vec<Token> = Vec::new();
88 for t in tokens {
89 match t {
90 Token::AnySeq => {
91 if !matches!(compact.last(), Some(Token::AnySeq)) {
92 compact.push(t);
93 }
94 }
95 _ => compact.push(t),
96 }
97 }
98 fn match_tokens<'a>(tokens: &[Token<'a>], text: &str) -> bool {
100 fn eq_icase(a: &str, b: &str) -> bool {
101 a.eq_ignore_ascii_case(b)
102 }
103 let mut ti = 0;
105 let tb = tokens;
106 let mut backtrack: Vec<(usize, usize)> = Vec::new(); let text_bytes = text.as_bytes();
109 let mut si = 0; loop {
111 if ti == tb.len() {
112 if si == text_bytes.len() {
114 return true;
115 }
116 } else {
118 match tb[ti] {
119 Token::AnySeq => {
120 ti += 1;
122 backtrack.push((ti - 1, si + 1));
123 continue;
124 }
125 Token::AnyChar => {
126 if si < text_bytes.len() {
127 ti += 1;
128 si += 1;
129 continue;
130 }
131 }
132 Token::Lit(l) => {
133 let l_len = l.len();
134 if si + l_len <= text_bytes.len() && eq_icase(&text[si..si + l_len], l) {
135 ti += 1;
136 si += l_len;
137 continue;
138 }
139 }
140 }
141 }
142 if let Some((tok_star, new_si)) = backtrack.pop() {
144 if new_si <= text_bytes.len() {
145 ti = tok_star + 1;
146 si = new_si;
147 continue;
148 } else {
149 continue;
150 }
151 }
152 return false;
153 }
154 }
155 match_tokens(&compact, text)
156}
157
158#[derive(Debug)]
161pub struct XLookupFn;
162
163impl Function for XLookupFn {
226 func_caps!(PURE, LOOKUP);
227 fn name(&self) -> &'static str {
228 "XLOOKUP"
229 }
230 fn min_args(&self) -> usize {
231 3
232 }
233 fn variadic(&self) -> bool {
234 true
235 }
236 fn arg_schema(&self) -> &'static [ArgSchema] {
237 use once_cell::sync::Lazy;
238 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
239 vec![
240 ArgSchema {
242 kinds: smallvec::smallvec![ArgKind::Any],
243 required: true,
244 by_ref: false,
245 shape: ShapeKind::Scalar,
246 coercion: CoercionPolicy::None,
247 max: None,
248 repeating: None,
249 default: None,
250 },
251 ArgSchema {
253 kinds: smallvec::smallvec![ArgKind::Range],
254 required: true,
255 by_ref: true,
256 shape: ShapeKind::Range,
257 coercion: CoercionPolicy::None,
258 max: None,
259 repeating: None,
260 default: None,
261 },
262 ArgSchema {
264 kinds: smallvec::smallvec![ArgKind::Range],
265 required: true,
266 by_ref: true,
267 shape: ShapeKind::Range,
268 coercion: CoercionPolicy::None,
269 max: None,
270 repeating: None,
271 default: None,
272 },
273 ArgSchema {
275 kinds: smallvec::smallvec![ArgKind::Any],
276 required: false,
277 by_ref: false,
278 shape: ShapeKind::Scalar,
279 coercion: CoercionPolicy::None,
280 max: None,
281 repeating: None,
282 default: None,
283 },
284 ArgSchema {
286 kinds: smallvec::smallvec![ArgKind::Number],
287 required: false,
288 by_ref: false,
289 shape: ShapeKind::Scalar,
290 coercion: CoercionPolicy::NumberLenientText,
291 max: None,
292 repeating: None,
293 default: Some(LiteralValue::Int(0)),
294 },
295 ArgSchema {
297 kinds: smallvec::smallvec![ArgKind::Number],
298 required: false,
299 by_ref: false,
300 shape: ShapeKind::Scalar,
301 coercion: CoercionPolicy::NumberLenientText,
302 max: None,
303 repeating: None,
304 default: Some(LiteralValue::Int(1)),
305 },
306 ]
307 });
308 &SCHEMA
309 }
310 fn eval<'a, 'b, 'c>(
311 &self,
312 args: &'c [ArgumentHandle<'a, 'b>],
313 _ctx: &dyn FunctionContext<'b>,
314 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
315 if args.len() < 3 {
316 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
317 ExcelError::new(ExcelErrorKind::Value),
318 )));
319 }
320 let lookup_value = args[0].value()?.into_literal();
321 if let LiteralValue::Error(ref e) = lookup_value {
322 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
323 e.clone(),
324 )));
325 }
326 let lookup_view = match args[1].range_view() {
327 Ok(v) => v,
328 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
329 };
330 let ret_view = match args[2].range_view() {
331 Ok(v) => v,
332 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
333 };
334
335 let (lookup_rows, lookup_cols) = lookup_view.dims();
336 let (ret_rows, ret_cols) = ret_view.dims();
337
338 let vertical = if lookup_cols == 1 {
343 true
344 } else if lookup_rows == 1 {
345 false
346 } else if lookup_rows == 0 && lookup_cols == 0 {
347 if ret_cols == 1 {
348 true
349 } else if ret_rows == 1 {
350 false
351 } else {
352 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
353 ExcelError::new(ExcelErrorKind::Value),
354 )));
355 }
356 } else {
357 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
358 ExcelError::new(ExcelErrorKind::Value),
359 )));
360 };
361
362 let lookup_len = {
363 let raw = if vertical { lookup_rows } else { lookup_cols };
364 if raw == 0 {
365 if vertical { ret_rows } else { ret_cols }
366 } else {
367 raw
368 }
369 };
370
371 if lookup_len == 0 {
372 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
373 ExcelError::new(ExcelErrorKind::Na),
374 )));
375 }
376
377 let match_mode = if args.len() >= 5 {
378 match args[4].value()?.into_literal() {
379 LiteralValue::Int(i) => i,
380 LiteralValue::Number(n) => n as i64,
381 _ => 0,
382 }
383 } else {
384 0
385 };
386 let search_mode = if args.len() >= 6 {
387 match args[5].value()?.into_literal() {
388 LiteralValue::Int(i) => i,
389 LiteralValue::Number(n) => n as i64,
390 _ => 1,
391 }
392 } else {
393 1
394 };
395
396 let wildcard = match_mode == 2;
397
398 let mut found: Option<usize> = None;
399 let needle = lookup_value;
400 if match_mode == 0 || wildcard {
401 if search_mode == 1 && lookup_rows > 0 && lookup_cols > 0 {
402 found =
403 super::lookup_utils::find_exact_index_in_view(&lookup_view, &needle, wildcard)?;
404 } else if search_mode == -1 {
405 for i in (0..lookup_len).rev() {
406 let cand = if vertical {
407 lookup_view.get_cell(i, 0)
408 } else {
409 lookup_view.get_cell(0, i)
410 };
411 if equals_maybe_wildcard(&needle, &cand, wildcard) {
412 found = Some(i);
413 break;
414 }
415 }
416 } else {
417 for i in 0..lookup_len {
420 let cand = if vertical {
421 lookup_view.get_cell(i, 0)
422 } else {
423 lookup_view.get_cell(0, i)
424 };
425 if equals_maybe_wildcard(&needle, &cand, wildcard) {
426 found = Some(i);
427 break;
428 }
429 }
430 }
431 } else if match_mode == -1 || match_mode == 1 {
432 let needle_num = value_to_f64_lenient(&needle);
433 let mut best_idx: Option<usize> = None;
434 let mut best_val: f64 = if match_mode == -1 {
435 f64::NEG_INFINITY
436 } else {
437 f64::INFINITY
438 };
439
440 let mut prev: Option<LiteralValue> = None;
441 for i in 0..lookup_len {
442 let cand = if vertical {
443 lookup_view.get_cell(i, 0)
444 } else {
445 lookup_view.get_cell(0, i)
446 };
447
448 if let Some(p) = prev.as_ref() {
449 let sorted_ok = cmp_for_lookup(p, &cand).is_some_and(|o| o <= 0);
450 if !sorted_ok {
451 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
452 ExcelError::new(ExcelErrorKind::Na),
453 )));
454 }
455 }
456 prev = Some(cand.clone());
457
458 if cmp_for_lookup(&cand, &needle).is_some_and(|o| o == 0) {
459 found = Some(i);
460 break;
461 }
462
463 if let (Some(nn), Some(vv)) = (needle_num, value_to_f64_lenient(&cand)) {
464 if match_mode == -1 {
465 if vv <= nn && vv > best_val {
466 best_val = vv;
467 best_idx = Some(i);
468 }
469 } else if vv >= nn && vv < best_val {
470 best_val = vv;
471 best_idx = Some(i);
472 }
473 }
474 }
475
476 if found.is_none() {
477 found = best_idx;
478 }
479 } else {
480 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
481 ExcelError::new(ExcelErrorKind::Value),
482 )));
483 }
484
485 if let Some(idx) = found {
486 let (ret_rows, ret_cols) = ret_view.dims();
487 if ret_rows == 0 || ret_cols == 0 {
488 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Empty));
489 }
490
491 if vertical {
492 if ret_cols == 1 {
493 return Ok(crate::traits::CalcValue::Scalar(ret_view.get_cell(idx, 0)));
494 }
495 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(ret_cols);
496 for c in 0..ret_cols {
497 row_out.push(ret_view.get_cell(idx, c));
498 }
499 return Ok(crate::traits::CalcValue::Range(
500 crate::engine::range_view::RangeView::from_owned_rows(
501 vec![row_out],
502 _ctx.date_system(),
503 ),
504 ));
505 }
506
507 if ret_rows == 1 {
509 return Ok(crate::traits::CalcValue::Scalar(ret_view.get_cell(0, idx)));
510 }
511
512 let mut col_out: Vec<Vec<LiteralValue>> = Vec::with_capacity(ret_rows);
513 for r in 0..ret_rows {
514 col_out.push(vec![ret_view.get_cell(r, idx)]);
515 }
516 return Ok(crate::traits::CalcValue::Range(
517 crate::engine::range_view::RangeView::from_owned_rows(col_out, _ctx.date_system()),
518 ));
519 }
520
521 if args.len() >= 4 {
522 return args[3].value();
523 }
524 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
525 ExcelError::new(ExcelErrorKind::Na),
526 )))
527 }
528}
529
530#[derive(Debug)]
533pub struct XMatchFn;
534impl Function for XMatchFn {
588 func_caps!(PURE, LOOKUP);
589 fn name(&self) -> &'static str {
590 "XMATCH"
591 }
592 fn min_args(&self) -> usize {
593 2
594 }
595 fn variadic(&self) -> bool {
596 true
597 }
598 fn arg_schema(&self) -> &'static [ArgSchema] {
599 use once_cell::sync::Lazy;
600 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
601 vec![
602 ArgSchema {
604 kinds: smallvec::smallvec![ArgKind::Any],
605 required: true,
606 by_ref: false,
607 shape: ShapeKind::Scalar,
608 coercion: CoercionPolicy::None,
609 max: None,
610 repeating: None,
611 default: None,
612 },
613 ArgSchema {
615 kinds: smallvec::smallvec![ArgKind::Range],
616 required: true,
617 by_ref: true,
618 shape: ShapeKind::Range,
619 coercion: CoercionPolicy::None,
620 max: None,
621 repeating: None,
622 default: None,
623 },
624 ArgSchema {
627 kinds: smallvec::smallvec![ArgKind::Number],
628 required: false,
629 by_ref: false,
630 shape: ShapeKind::Scalar,
631 coercion: CoercionPolicy::NumberLenientText,
632 max: None,
633 repeating: None,
634 default: Some(LiteralValue::Int(0)),
635 },
636 ArgSchema {
639 kinds: smallvec::smallvec![ArgKind::Number],
640 required: false,
641 by_ref: false,
642 shape: ShapeKind::Scalar,
643 coercion: CoercionPolicy::NumberLenientText,
644 max: None,
645 repeating: None,
646 default: Some(LiteralValue::Int(1)),
647 },
648 ]
649 });
650 &SCHEMA
651 }
652 fn eval<'a, 'b, 'c>(
653 &self,
654 args: &'c [ArgumentHandle<'a, 'b>],
655 _ctx: &dyn FunctionContext<'b>,
656 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
657 if args.len() < 2 {
658 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
659 ExcelError::new(ExcelErrorKind::Value),
660 )));
661 }
662 let lookup_value = args[0].value()?.into_literal();
663 if let LiteralValue::Error(ref e) = lookup_value {
664 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
665 e.clone(),
666 )));
667 }
668 let lookup_view = match args[1].range_view() {
669 Ok(v) => v,
670 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
671 };
672
673 let (lookup_rows, lookup_cols) = lookup_view.dims();
674
675 let vertical = if lookup_cols == 1 {
677 true
678 } else if lookup_rows == 1 {
679 false
680 } else if lookup_rows == 0 || lookup_cols == 0 {
681 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
682 ExcelError::new(ExcelErrorKind::Na),
683 )));
684 } else {
685 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
686 ExcelError::new(ExcelErrorKind::Value),
687 )));
688 };
689
690 let lookup_len = if vertical { lookup_rows } else { lookup_cols };
691
692 if lookup_len == 0 {
693 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
694 ExcelError::new(ExcelErrorKind::Na),
695 )));
696 }
697
698 let match_mode = if args.len() >= 3 {
699 match args[2].value()?.into_literal() {
700 LiteralValue::Int(i) => i,
701 LiteralValue::Number(n) => n as i64,
702 _ => 0,
703 }
704 } else {
705 0
706 };
707 let search_mode = if args.len() >= 4 {
708 match args[3].value()?.into_literal() {
709 LiteralValue::Int(i) => i,
710 LiteralValue::Number(n) => n as i64,
711 _ => 1,
712 }
713 } else {
714 1
715 };
716
717 let wildcard = match_mode == 2;
718 let needle = lookup_value;
719
720 let mut found: Option<usize> = None;
721
722 if match_mode == 0 || wildcard {
723 if search_mode == 1 || search_mode == 2 {
725 if lookup_rows > 0 && lookup_cols > 0 {
727 found = super::lookup_utils::find_exact_index_in_view(
728 &lookup_view,
729 &needle,
730 wildcard,
731 )?;
732 }
733 } else if search_mode == -1 || search_mode == -2 {
734 for i in (0..lookup_len).rev() {
736 let cand = if vertical {
737 lookup_view.get_cell(i, 0)
738 } else {
739 lookup_view.get_cell(0, i)
740 };
741 if equals_maybe_wildcard(&needle, &cand, wildcard) {
742 found = Some(i);
743 break;
744 }
745 }
746 } else {
747 for i in 0..lookup_len {
749 let cand = if vertical {
750 lookup_view.get_cell(i, 0)
751 } else {
752 lookup_view.get_cell(0, i)
753 };
754 if equals_maybe_wildcard(&needle, &cand, wildcard) {
755 found = Some(i);
756 break;
757 }
758 }
759 }
760 } else if match_mode == -1 || match_mode == 1 {
761 let needle_num = value_to_f64_lenient(&needle);
763 let mut best_idx: Option<usize> = None;
764 let mut best_val: f64 = if match_mode == -1 {
765 f64::NEG_INFINITY
766 } else {
767 f64::INFINITY
768 };
769
770 let use_reverse = search_mode == -1 || search_mode == -2;
772 let indices: Box<dyn Iterator<Item = usize>> = if use_reverse {
773 Box::new((0..lookup_len).rev())
774 } else {
775 Box::new(0..lookup_len)
776 };
777
778 if (search_mode == 2 || search_mode == -2) && match_mode != 0 {
781 let ascending = search_mode == 2;
782 let mut prev: Option<LiteralValue> = None;
783 for i in 0..lookup_len {
784 let cand = if vertical {
785 lookup_view.get_cell(i, 0)
786 } else {
787 lookup_view.get_cell(0, i)
788 };
789 if let Some(p) = prev.as_ref() {
790 let sorted_ok = if ascending {
791 cmp_for_lookup(p, &cand).is_some_and(|o| o <= 0)
792 } else {
793 cmp_for_lookup(p, &cand).is_some_and(|o| o >= 0)
794 };
795 if !sorted_ok {
796 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
797 ExcelError::new(ExcelErrorKind::Na),
798 )));
799 }
800 }
801 prev = Some(cand);
802 }
803 }
804
805 for i in indices {
806 let cand = if vertical {
807 lookup_view.get_cell(i, 0)
808 } else {
809 lookup_view.get_cell(0, i)
810 };
811
812 if cmp_for_lookup(&cand, &needle).is_some_and(|o| o == 0) {
813 found = Some(i);
814 break;
815 }
816
817 if let (Some(nn), Some(vv)) = (needle_num, value_to_f64_lenient(&cand)) {
818 if match_mode == -1 {
819 if vv <= nn && vv > best_val {
821 best_val = vv;
822 best_idx = Some(i);
823 }
824 } else {
825 if vv >= nn && vv < best_val {
827 best_val = vv;
828 best_idx = Some(i);
829 }
830 }
831 }
832 }
833
834 if found.is_none() {
835 found = best_idx;
836 }
837 } else {
838 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
839 ExcelError::new(ExcelErrorKind::Value),
840 )));
841 }
842
843 match found {
844 Some(idx) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
845 (idx + 1) as i64,
846 ))),
847 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
848 ExcelError::new(ExcelErrorKind::Na),
849 ))),
850 }
851 }
852}
853
854#[derive(Debug)]
857pub struct SortFn;
858impl Function for SortFn {
918 func_caps!(PURE);
919 fn name(&self) -> &'static str {
920 "SORT"
921 }
922 fn min_args(&self) -> usize {
923 1
924 }
925 fn variadic(&self) -> bool {
926 true
927 }
928 fn arg_schema(&self) -> &'static [ArgSchema] {
929 use once_cell::sync::Lazy;
930 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
931 vec![
932 ArgSchema {
934 kinds: smallvec::smallvec![ArgKind::Range],
935 required: true,
936 by_ref: true,
937 shape: ShapeKind::Range,
938 coercion: CoercionPolicy::None,
939 max: None,
940 repeating: None,
941 default: None,
942 },
943 ArgSchema {
945 kinds: smallvec::smallvec![ArgKind::Number],
946 required: false,
947 by_ref: false,
948 shape: ShapeKind::Scalar,
949 coercion: CoercionPolicy::NumberLenientText,
950 max: None,
951 repeating: None,
952 default: Some(LiteralValue::Int(1)),
953 },
954 ArgSchema {
956 kinds: smallvec::smallvec![ArgKind::Number],
957 required: false,
958 by_ref: false,
959 shape: ShapeKind::Scalar,
960 coercion: CoercionPolicy::NumberLenientText,
961 max: None,
962 repeating: None,
963 default: Some(LiteralValue::Int(1)),
964 },
965 ArgSchema {
967 kinds: smallvec::smallvec![ArgKind::Logical],
968 required: false,
969 by_ref: false,
970 shape: ShapeKind::Scalar,
971 coercion: CoercionPolicy::Logical,
972 max: None,
973 repeating: None,
974 default: Some(LiteralValue::Boolean(false)),
975 },
976 ]
977 });
978 &SCHEMA
979 }
980 fn eval<'a, 'b, 'c>(
981 &self,
982 args: &'c [ArgumentHandle<'a, 'b>],
983 _ctx: &dyn FunctionContext<'b>,
984 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
985 let view = match args[0].range_view() {
986 Ok(v) => v,
987 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
988 };
989 let (rows, cols) = view.dims();
990 if rows == 0 || cols == 0 {
991 return Ok(crate::traits::CalcValue::Range(
992 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
993 ));
994 }
995
996 let sort_index = if args.len() >= 2 {
997 match args[1].value()?.into_literal() {
998 LiteralValue::Int(i) => i,
999 LiteralValue::Number(n) => n as i64,
1000 _ => 1,
1001 }
1002 } else {
1003 1
1004 };
1005
1006 let sort_order = if args.len() >= 3 {
1007 match args[2].value()?.into_literal() {
1008 LiteralValue::Int(i) => i,
1009 LiteralValue::Number(n) => n as i64,
1010 _ => 1,
1011 }
1012 } else {
1013 1
1014 };
1015
1016 let by_col = if args.len() >= 4 {
1017 matches!(args[3].value()?.into_literal(), LiteralValue::Boolean(true))
1018 } else {
1019 false
1020 };
1021
1022 let ascending = sort_order >= 0;
1023
1024 if by_col {
1025 let sort_row_idx = (sort_index - 1).max(0) as usize;
1027 if sort_row_idx >= rows {
1028 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1029 ExcelError::new(ExcelErrorKind::Value),
1030 )));
1031 }
1032
1033 let mut columns: Vec<(usize, Vec<LiteralValue>)> = Vec::with_capacity(cols);
1035 for c in 0..cols {
1036 let mut col_vals: Vec<LiteralValue> = Vec::with_capacity(rows);
1037 for r in 0..rows {
1038 col_vals.push(view.get_cell(r, c));
1039 }
1040 columns.push((c, col_vals));
1041 }
1042
1043 columns.sort_by(|a, b| {
1045 let val_a = &a.1[sort_row_idx];
1046 let val_b = &b.1[sort_row_idx];
1047 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
1048 if ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) }
1049 });
1050
1051 let mut out: Vec<Vec<LiteralValue>> = vec![Vec::with_capacity(cols); rows];
1053 for (_orig_idx, col_vals) in columns {
1054 for (r, val) in col_vals.into_iter().enumerate() {
1055 out[r].push(val);
1056 }
1057 }
1058
1059 Ok(collapse_if_scalar(out, _ctx.date_system()))
1060 } else {
1061 let sort_col_idx = (sort_index - 1).max(0) as usize;
1063 if sort_col_idx >= cols {
1064 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1065 ExcelError::new(ExcelErrorKind::Value),
1066 )));
1067 }
1068
1069 let mut row_data: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
1071 for r in 0..rows {
1072 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
1073 for c in 0..cols {
1074 row_vals.push(view.get_cell(r, c));
1075 }
1076 row_data.push(row_vals);
1077 }
1078
1079 row_data.sort_by(|a, b| {
1081 let val_a = &a[sort_col_idx];
1082 let val_b = &b[sort_col_idx];
1083 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
1084 if ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) }
1085 });
1086
1087 Ok(collapse_if_scalar(row_data, _ctx.date_system()))
1088 }
1089 }
1090}
1091
1092#[derive(Debug)]
1095pub struct SortByFn;
1096impl Function for SortByFn {
1156 func_caps!(PURE);
1157 fn name(&self) -> &'static str {
1158 "SORTBY"
1159 }
1160 fn min_args(&self) -> usize {
1161 2
1162 }
1163 fn variadic(&self) -> bool {
1164 true
1165 }
1166 fn arg_schema(&self) -> &'static [ArgSchema] {
1167 use once_cell::sync::Lazy;
1168 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1169 vec![
1170 ArgSchema {
1172 kinds: smallvec::smallvec![ArgKind::Range],
1173 required: true,
1174 by_ref: true,
1175 shape: ShapeKind::Range,
1176 coercion: CoercionPolicy::None,
1177 max: None,
1178 repeating: None,
1179 default: None,
1180 },
1181 ArgSchema {
1183 kinds: smallvec::smallvec![ArgKind::Range],
1184 required: true,
1185 by_ref: true,
1186 shape: ShapeKind::Range,
1187 coercion: CoercionPolicy::None,
1188 max: None,
1189 repeating: None,
1190 default: None,
1191 },
1192 ArgSchema {
1194 kinds: smallvec::smallvec![ArgKind::Number],
1195 required: false,
1196 by_ref: false,
1197 shape: ShapeKind::Scalar,
1198 coercion: CoercionPolicy::NumberLenientText,
1199 max: None,
1200 repeating: None,
1201 default: Some(LiteralValue::Int(1)),
1202 },
1203 ]
1205 });
1206 &SCHEMA
1207 }
1208 fn eval<'a, 'b, 'c>(
1209 &self,
1210 args: &'c [ArgumentHandle<'a, 'b>],
1211 _ctx: &dyn FunctionContext<'b>,
1212 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1213 if args.len() < 2 {
1214 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1215 ExcelError::new(ExcelErrorKind::Value),
1216 )));
1217 }
1218
1219 let view = match args[0].range_view() {
1220 Ok(v) => v,
1221 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1222 };
1223 let (rows, cols) = view.dims();
1224 if rows == 0 || cols == 0 {
1225 return Ok(crate::traits::CalcValue::Range(
1226 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
1227 ));
1228 }
1229
1230 let mut sort_criteria: Vec<(Vec<LiteralValue>, bool)> = Vec::new();
1233 let mut arg_idx = 1;
1234
1235 while arg_idx < args.len() {
1236 let by_view = match args[arg_idx].range_view() {
1238 Ok(v) => v,
1239 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1240 };
1241 let (by_rows, by_cols) = by_view.dims();
1242
1243 let by_values: Vec<LiteralValue> = if by_cols == 1 {
1245 if by_rows != rows {
1246 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1247 ExcelError::new(ExcelErrorKind::Value),
1248 )));
1249 }
1250 (0..by_rows).map(|r| by_view.get_cell(r, 0)).collect()
1251 } else if by_rows == 1 {
1252 if by_cols != rows {
1253 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1254 ExcelError::new(ExcelErrorKind::Value),
1255 )));
1256 }
1257 (0..by_cols).map(|c| by_view.get_cell(0, c)).collect()
1258 } else {
1259 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1260 ExcelError::new(ExcelErrorKind::Value),
1261 )));
1262 };
1263
1264 arg_idx += 1;
1265
1266 let ascending = if arg_idx < args.len() {
1268 match args[arg_idx].value() {
1271 Ok(v) => {
1272 let lit = v.into_literal();
1273 match lit {
1274 LiteralValue::Int(i) => {
1275 arg_idx += 1;
1276 i >= 0
1277 }
1278 LiteralValue::Number(n) => {
1279 arg_idx += 1;
1280 n >= 0.0
1281 }
1282 _ => true, }
1284 }
1285 Err(_) => true,
1286 }
1287 } else {
1288 true
1289 };
1290
1291 sort_criteria.push((by_values, ascending));
1292 }
1293
1294 if sort_criteria.is_empty() {
1295 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1296 ExcelError::new(ExcelErrorKind::Value),
1297 )));
1298 }
1299
1300 let mut indexed_rows: Vec<(usize, Vec<LiteralValue>)> = Vec::with_capacity(rows);
1302 for r in 0..rows {
1303 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
1304 for c in 0..cols {
1305 row_vals.push(view.get_cell(r, c));
1306 }
1307 indexed_rows.push((r, row_vals));
1308 }
1309
1310 indexed_rows.sort_by(|a, b| {
1312 for (by_values, ascending) in &sort_criteria {
1313 let val_a = &by_values[a.0];
1314 let val_b = &by_values[b.0];
1315 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
1316 if cmp != 0 {
1317 return if *ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) };
1318 }
1319 }
1320 std::cmp::Ordering::Equal
1321 });
1322
1323 let out: Vec<Vec<LiteralValue>> = indexed_rows.into_iter().map(|(_, row)| row).collect();
1325
1326 Ok(collapse_if_scalar(out, _ctx.date_system()))
1327 }
1328}
1329
1330#[derive(Debug)]
1333pub struct RandArrayFn;
1334impl Function for RandArrayFn {
1379 fn caps(&self) -> crate::function::FnCaps {
1381 crate::function::FnCaps::empty()
1382 }
1383 fn name(&self) -> &'static str {
1384 "RANDARRAY"
1385 }
1386 fn min_args(&self) -> usize {
1387 0
1388 }
1389 fn variadic(&self) -> bool {
1390 true
1391 }
1392 fn arg_schema(&self) -> &'static [ArgSchema] {
1393 use once_cell::sync::Lazy;
1394 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1395 vec![
1396 ArgSchema {
1398 kinds: smallvec::smallvec![ArgKind::Number],
1399 required: false,
1400 by_ref: false,
1401 shape: ShapeKind::Scalar,
1402 coercion: CoercionPolicy::NumberLenientText,
1403 max: None,
1404 repeating: None,
1405 default: Some(LiteralValue::Int(1)),
1406 },
1407 ArgSchema {
1409 kinds: smallvec::smallvec![ArgKind::Number],
1410 required: false,
1411 by_ref: false,
1412 shape: ShapeKind::Scalar,
1413 coercion: CoercionPolicy::NumberLenientText,
1414 max: None,
1415 repeating: None,
1416 default: Some(LiteralValue::Int(1)),
1417 },
1418 ArgSchema {
1420 kinds: smallvec::smallvec![ArgKind::Number],
1421 required: false,
1422 by_ref: false,
1423 shape: ShapeKind::Scalar,
1424 coercion: CoercionPolicy::NumberLenientText,
1425 max: None,
1426 repeating: None,
1427 default: Some(LiteralValue::Int(0)),
1428 },
1429 ArgSchema {
1431 kinds: smallvec::smallvec![ArgKind::Number],
1432 required: false,
1433 by_ref: false,
1434 shape: ShapeKind::Scalar,
1435 coercion: CoercionPolicy::NumberLenientText,
1436 max: None,
1437 repeating: None,
1438 default: Some(LiteralValue::Int(1)),
1439 },
1440 ArgSchema {
1442 kinds: smallvec::smallvec![ArgKind::Logical],
1443 required: false,
1444 by_ref: false,
1445 shape: ShapeKind::Scalar,
1446 coercion: CoercionPolicy::Logical,
1447 max: None,
1448 repeating: None,
1449 default: Some(LiteralValue::Boolean(false)),
1450 },
1451 ]
1452 });
1453 &SCHEMA
1454 }
1455 fn eval<'a, 'b, 'c>(
1456 &self,
1457 args: &'c [ArgumentHandle<'a, 'b>],
1458 _ctx: &dyn FunctionContext<'b>,
1459 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1460 use rand::Rng;
1461
1462 let num = |a: &ArgumentHandle| -> Result<f64, ExcelError> {
1464 Ok(match a.value()?.into_literal() {
1465 LiteralValue::Int(i) => i as f64,
1466 LiteralValue::Number(n) => n,
1467 LiteralValue::Error(e) => return Err(e),
1468 _other => {
1469 return Err(ExcelError::new(ExcelErrorKind::Value));
1470 }
1471 })
1472 };
1473
1474 let rows = if !args.is_empty() {
1475 num(&args[0])? as i64
1476 } else {
1477 1
1478 };
1479 let cols = if args.len() >= 2 {
1480 num(&args[1])? as i64
1481 } else {
1482 1
1483 };
1484 let min_val = if args.len() >= 3 { num(&args[2])? } else { 0.0 };
1485 let max_val = if args.len() >= 4 { num(&args[3])? } else { 1.0 };
1486 let whole_number = if args.len() >= 5 {
1487 matches!(args[4].value()?.into_literal(), LiteralValue::Boolean(true))
1488 } else {
1489 false
1490 };
1491
1492 if rows <= 0 || cols <= 0 {
1494 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1495 ExcelError::new(ExcelErrorKind::Value),
1496 )));
1497 }
1498
1499 if whole_number && min_val > max_val {
1501 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1502 ExcelError::new(ExcelErrorKind::Value),
1503 )));
1504 }
1505
1506 let mut rng = rand::thread_rng();
1507 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows as usize);
1508
1509 for _r in 0..rows {
1510 let mut row_vec: Vec<LiteralValue> = Vec::with_capacity(cols as usize);
1511 for _c in 0..cols {
1512 let value = if whole_number {
1513 let min_int = min_val.ceil() as i64;
1515 let max_int = max_val.floor() as i64;
1516 if min_int > max_int {
1517 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1518 ExcelError::new(ExcelErrorKind::Value),
1519 )));
1520 }
1521 let rand_int = rng.gen_range(min_int..=max_int);
1522 LiteralValue::Int(rand_int)
1523 } else {
1524 let rand_float = rng.r#gen::<f64>() * (max_val - min_val) + min_val;
1526 LiteralValue::Number(rand_float)
1527 };
1528 row_vec.push(value);
1529 }
1530 out.push(row_vec);
1531 }
1532
1533 Ok(collapse_if_scalar(out, _ctx.date_system()))
1534 }
1535}
1536
1537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1541enum GroupAggregation {
1542 Sum,
1543 Average,
1544 Count,
1545 CountA,
1546 Max,
1547 Min,
1548 Product,
1549 StDev,
1550 StDevP,
1551 Var,
1552 VarP,
1553 Median,
1554}
1555
1556impl GroupAggregation {
1557 fn from_literal(val: &LiteralValue) -> Option<Self> {
1558 match val {
1559 LiteralValue::Text(s) => Self::from_str(s),
1560 LiteralValue::Int(n) => Self::from_num(*n as i32),
1561 LiteralValue::Number(n) => Self::from_num(*n as i32),
1562 _ => None,
1563 }
1564 }
1565
1566 fn from_str(s: &str) -> Option<Self> {
1567 let upper = s.to_ascii_uppercase();
1568 match upper.as_str() {
1569 "SUM" => Some(Self::Sum),
1570 "AVERAGE" | "AVG" => Some(Self::Average),
1571 "COUNT" => Some(Self::Count),
1572 "COUNTA" => Some(Self::CountA),
1573 "MAX" => Some(Self::Max),
1574 "MIN" => Some(Self::Min),
1575 "PRODUCT" => Some(Self::Product),
1576 "STDEV" | "STDEV.S" => Some(Self::StDev),
1577 "STDEVP" | "STDEV.P" => Some(Self::StDevP),
1578 "VAR" | "VAR.S" => Some(Self::Var),
1579 "VARP" | "VAR.P" => Some(Self::VarP),
1580 "MEDIAN" => Some(Self::Median),
1581 _ => None,
1582 }
1583 }
1584
1585 fn from_num(n: i32) -> Option<Self> {
1586 match n {
1588 1 => Some(Self::Average),
1589 2 => Some(Self::Count),
1590 3 => Some(Self::CountA),
1591 4 => Some(Self::Max),
1592 5 => Some(Self::Min),
1593 6 => Some(Self::Product),
1594 7 => Some(Self::StDev),
1595 8 => Some(Self::StDevP),
1596 9 => Some(Self::Sum),
1597 10 => Some(Self::Var),
1598 11 => Some(Self::VarP),
1599 12 => Some(Self::Median),
1600 _ => None,
1601 }
1602 }
1603
1604 fn apply(&self, values: &[f64]) -> f64 {
1605 if values.is_empty() {
1606 return match self {
1607 Self::Count | Self::CountA => 0.0,
1608 Self::Sum | Self::Product => 0.0,
1609 _ => f64::NAN,
1610 };
1611 }
1612
1613 match self {
1614 Self::Sum => values.iter().sum(),
1615 Self::Average => values.iter().sum::<f64>() / values.len() as f64,
1616 Self::Count | Self::CountA => values.len() as f64,
1617 Self::Max => values.iter().copied().fold(f64::NEG_INFINITY, f64::max),
1618 Self::Min => values.iter().copied().fold(f64::INFINITY, f64::min),
1619 Self::Product => values.iter().product(),
1620 Self::StDev => {
1621 if values.len() < 2 {
1622 return f64::NAN;
1623 }
1624 let mean = values.iter().sum::<f64>() / values.len() as f64;
1625 let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>()
1626 / (values.len() - 1) as f64;
1627 variance.sqrt()
1628 }
1629 Self::StDevP => {
1630 let mean = values.iter().sum::<f64>() / values.len() as f64;
1631 let variance =
1632 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
1633 variance.sqrt()
1634 }
1635 Self::Var => {
1636 if values.len() < 2 {
1637 return f64::NAN;
1638 }
1639 let mean = values.iter().sum::<f64>() / values.len() as f64;
1640 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (values.len() - 1) as f64
1641 }
1642 Self::VarP => {
1643 let mean = values.iter().sum::<f64>() / values.len() as f64;
1644 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64
1645 }
1646 Self::Median => {
1647 let mut sorted = values.to_vec();
1648 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1649 let mid = sorted.len() / 2;
1650 if sorted.len().is_multiple_of(2) {
1651 (sorted[mid - 1] + sorted[mid]) / 2.0
1652 } else {
1653 sorted[mid]
1654 }
1655 }
1656 }
1657 }
1658}
1659
1660fn literal_to_group_key(v: &LiteralValue) -> String {
1662 match v {
1663 LiteralValue::Text(s) => s.clone(),
1664 LiteralValue::Int(i) => i.to_string(),
1665 LiteralValue::Number(n) => format!("{:.10}", n),
1666 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
1667 LiteralValue::Empty => String::new(),
1668 LiteralValue::Error(e) => format!("#{:?}", e.kind),
1669 LiteralValue::Array(_) => "[Array]".to_string(),
1670 LiteralValue::Date(d) => d.to_string(),
1671 LiteralValue::DateTime(dt) => dt.to_string(),
1672 LiteralValue::Time(t) => t.to_string(),
1673 LiteralValue::Duration(d) => format!("{:?}", d),
1674 LiteralValue::Pending => "[Pending]".to_string(),
1675 }
1676}
1677
1678fn literal_to_num_opt(v: &LiteralValue) -> Option<f64> {
1680 match v {
1681 LiteralValue::Number(n) => Some(*n),
1682 LiteralValue::Int(i) => Some(*i as f64),
1683 LiteralValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
1684 _ => None,
1685 }
1686}
1687
1688#[derive(Debug)]
1689pub struct GroupByFn;
1690
1691impl Function for GroupByFn {
1755 func_caps!(PURE);
1756 fn name(&self) -> &'static str {
1757 "GROUPBY"
1758 }
1759 fn min_args(&self) -> usize {
1760 3
1761 }
1762 fn variadic(&self) -> bool {
1763 true
1764 }
1765 fn arg_schema(&self) -> &'static [ArgSchema] {
1766 use once_cell::sync::Lazy;
1767 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1768 vec![
1769 ArgSchema {
1771 kinds: smallvec::smallvec![ArgKind::Range],
1772 required: true,
1773 by_ref: true,
1774 shape: ShapeKind::Range,
1775 coercion: CoercionPolicy::None,
1776 max: None,
1777 repeating: None,
1778 default: None,
1779 },
1780 ArgSchema {
1782 kinds: smallvec::smallvec![ArgKind::Range],
1783 required: true,
1784 by_ref: true,
1785 shape: ShapeKind::Range,
1786 coercion: CoercionPolicy::None,
1787 max: None,
1788 repeating: None,
1789 default: None,
1790 },
1791 ArgSchema {
1793 kinds: smallvec::smallvec![ArgKind::Any],
1794 required: true,
1795 by_ref: false,
1796 shape: ShapeKind::Scalar,
1797 coercion: CoercionPolicy::None,
1798 max: None,
1799 repeating: None,
1800 default: None,
1801 },
1802 ArgSchema {
1804 kinds: smallvec::smallvec![ArgKind::Number],
1805 required: false,
1806 by_ref: false,
1807 shape: ShapeKind::Scalar,
1808 coercion: CoercionPolicy::NumberLenientText,
1809 max: None,
1810 repeating: None,
1811 default: Some(LiteralValue::Int(1)),
1812 },
1813 ArgSchema {
1815 kinds: smallvec::smallvec![ArgKind::Number],
1816 required: false,
1817 by_ref: false,
1818 shape: ShapeKind::Scalar,
1819 coercion: CoercionPolicy::NumberLenientText,
1820 max: None,
1821 repeating: None,
1822 default: Some(LiteralValue::Int(0)),
1823 },
1824 ArgSchema {
1826 kinds: smallvec::smallvec![ArgKind::Number],
1827 required: false,
1828 by_ref: false,
1829 shape: ShapeKind::Scalar,
1830 coercion: CoercionPolicy::NumberLenientText,
1831 max: None,
1832 repeating: None,
1833 default: Some(LiteralValue::Int(0)),
1834 },
1835 ]
1836 });
1837 &SCHEMA
1838 }
1839
1840 fn eval<'a, 'b, 'c>(
1841 &self,
1842 args: &'c [ArgumentHandle<'a, 'b>],
1843 _ctx: &dyn FunctionContext<'b>,
1844 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1845 if args.len() < 3 {
1846 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1847 ExcelError::new(ExcelErrorKind::Value),
1848 )));
1849 }
1850
1851 let row_fields_view = match args[0].range_view() {
1853 Ok(v) => v,
1854 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1855 };
1856 let values_view = match args[1].range_view() {
1857 Ok(v) => v,
1858 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1859 };
1860
1861 let agg_val = args[2].value()?.into_literal();
1863 let aggregation = match GroupAggregation::from_literal(&agg_val) {
1864 Some(a) => a,
1865 None => {
1866 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1867 ExcelError::new(ExcelErrorKind::Value)
1868 .with_message("Invalid aggregation function"),
1869 )));
1870 }
1871 };
1872
1873 let field_headers = if args.len() >= 4 {
1875 match args[3].value()?.into_literal() {
1876 LiteralValue::Int(i) => i as i32,
1877 LiteralValue::Number(n) => n as i32,
1878 _ => 1,
1879 }
1880 } else {
1881 1
1882 };
1883
1884 let total_depth = if args.len() >= 5 {
1885 match args[4].value()?.into_literal() {
1886 LiteralValue::Int(i) => i as i32,
1887 LiteralValue::Number(n) => n as i32,
1888 _ => 0,
1889 }
1890 } else {
1891 0
1892 };
1893
1894 let sort_order = if args.len() >= 6 {
1895 match args[5].value()?.into_literal() {
1896 LiteralValue::Int(i) => i as i32,
1897 LiteralValue::Number(n) => n as i32,
1898 _ => 0,
1899 }
1900 } else {
1901 0
1902 };
1903
1904 let (rf_rows, rf_cols) = row_fields_view.dims();
1905 let (val_rows, val_cols) = values_view.dims();
1906
1907 let has_headers = field_headers == 1 || field_headers == 3;
1909 let data_start_row = if has_headers { 1 } else { 0 };
1910
1911 if rf_rows != val_rows {
1913 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1914 ExcelError::new(ExcelErrorKind::Value)
1915 .with_message("Row fields and values must have same number of rows"),
1916 )));
1917 }
1918
1919 if rf_rows <= data_start_row {
1920 return Ok(crate::traits::CalcValue::Range(
1922 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
1923 ));
1924 }
1925
1926 let mut groups: std::collections::HashMap<String, Vec<Vec<f64>>> = HashMap::new();
1929 let mut group_order: Vec<String> = Vec::new();
1930
1931 for r in data_start_row..rf_rows {
1932 let mut key_parts: Vec<String> = Vec::with_capacity(rf_cols);
1934 for c in 0..rf_cols {
1935 key_parts.push(literal_to_group_key(&row_fields_view.get_cell(r, c)));
1936 }
1937 let key = key_parts.join("\x00"); let mut row_values: Vec<Option<f64>> = Vec::with_capacity(val_cols);
1941 for c in 0..val_cols {
1942 row_values.push(literal_to_num_opt(&values_view.get_cell(r, c)));
1943 }
1944
1945 if !groups.contains_key(&key) {
1947 group_order.push(key.clone());
1948 groups.insert(key.clone(), vec![Vec::new(); val_cols]);
1949 }
1950
1951 let group_vals = groups.get_mut(&key).unwrap();
1952 for (c, val) in row_values.iter().enumerate() {
1953 if let Some(v) = val {
1954 group_vals[c].push(*v);
1955 }
1956 }
1957 }
1958
1959 if sort_order != 0 {
1961 group_order.sort_by(|a, b| if sort_order > 0 { a.cmp(b) } else { b.cmp(a) });
1962 }
1963
1964 let mut output: Vec<Vec<LiteralValue>> = Vec::new();
1966
1967 let generate_headers = field_headers == 2 || field_headers == 3;
1969 if generate_headers {
1970 let mut header_row: Vec<LiteralValue> = Vec::new();
1971 for c in 0..rf_cols {
1973 if has_headers {
1974 header_row.push(row_fields_view.get_cell(0, c));
1975 } else {
1976 header_row.push(LiteralValue::Text(format!("Field{}", c + 1)));
1977 }
1978 }
1979 for c in 0..val_cols {
1981 if has_headers {
1982 header_row.push(values_view.get_cell(0, c));
1983 } else {
1984 header_row.push(LiteralValue::Text(format!("Value{}", c + 1)));
1985 }
1986 }
1987 output.push(header_row);
1988 }
1989
1990 for key in &group_order {
1992 let mut row: Vec<LiteralValue> = Vec::new();
1993
1994 let key_parts: Vec<&str> = key.split('\x00').collect();
1996 for part in &key_parts {
1997 row.push(LiteralValue::Text(part.to_string()));
1998 }
1999
2000 let group_vals = groups.get(key).unwrap();
2002 for col_vals in group_vals {
2003 let result = aggregation.apply(col_vals);
2004 if result.is_nan() {
2005 row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2006 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2007 row.push(LiteralValue::Int(result as i64));
2008 } else {
2009 row.push(LiteralValue::Number(result));
2010 }
2011 }
2012 output.push(row);
2013 }
2014
2015 if total_depth >= 1 {
2017 let mut total_row: Vec<LiteralValue> = Vec::new();
2018 total_row.push(LiteralValue::Text("Grand Total".to_string()));
2020 for _ in 1..rf_cols {
2021 total_row.push(LiteralValue::Empty);
2022 }
2023
2024 for c in 0..val_cols {
2026 let mut all_vals: Vec<f64> = Vec::new();
2027 for group_vals in groups.values() {
2028 all_vals.extend(&group_vals[c]);
2029 }
2030 let result = aggregation.apply(&all_vals);
2031 if result.is_nan() {
2032 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2033 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2034 total_row.push(LiteralValue::Int(result as i64));
2035 } else {
2036 total_row.push(LiteralValue::Number(result));
2037 }
2038 }
2039 output.push(total_row);
2040 }
2041
2042 if output.is_empty() {
2043 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2044 ExcelError::new(ExcelErrorKind::Calc),
2045 )));
2046 }
2047
2048 Ok(collapse_if_scalar(output, _ctx.date_system()))
2049 }
2050}
2051
2052#[derive(Debug)]
2055pub struct PivotByFn;
2056
2057impl Function for PivotByFn {
2129 func_caps!(PURE);
2130 fn name(&self) -> &'static str {
2131 "PIVOTBY"
2132 }
2133 fn min_args(&self) -> usize {
2134 4
2135 }
2136 fn variadic(&self) -> bool {
2137 true
2138 }
2139 fn arg_schema(&self) -> &'static [ArgSchema] {
2140 use once_cell::sync::Lazy;
2141 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2142 vec![
2143 ArgSchema {
2145 kinds: smallvec::smallvec![ArgKind::Range],
2146 required: true,
2147 by_ref: true,
2148 shape: ShapeKind::Range,
2149 coercion: CoercionPolicy::None,
2150 max: None,
2151 repeating: None,
2152 default: None,
2153 },
2154 ArgSchema {
2156 kinds: smallvec::smallvec![ArgKind::Range],
2157 required: true,
2158 by_ref: true,
2159 shape: ShapeKind::Range,
2160 coercion: CoercionPolicy::None,
2161 max: None,
2162 repeating: None,
2163 default: None,
2164 },
2165 ArgSchema {
2167 kinds: smallvec::smallvec![ArgKind::Range],
2168 required: true,
2169 by_ref: true,
2170 shape: ShapeKind::Range,
2171 coercion: CoercionPolicy::None,
2172 max: None,
2173 repeating: None,
2174 default: None,
2175 },
2176 ArgSchema {
2178 kinds: smallvec::smallvec![ArgKind::Any],
2179 required: true,
2180 by_ref: false,
2181 shape: ShapeKind::Scalar,
2182 coercion: CoercionPolicy::None,
2183 max: None,
2184 repeating: None,
2185 default: None,
2186 },
2187 ArgSchema {
2189 kinds: smallvec::smallvec![ArgKind::Number],
2190 required: false,
2191 by_ref: false,
2192 shape: ShapeKind::Scalar,
2193 coercion: CoercionPolicy::NumberLenientText,
2194 max: None,
2195 repeating: None,
2196 default: Some(LiteralValue::Int(1)),
2197 },
2198 ArgSchema {
2200 kinds: smallvec::smallvec![ArgKind::Number],
2201 required: false,
2202 by_ref: false,
2203 shape: ShapeKind::Scalar,
2204 coercion: CoercionPolicy::NumberLenientText,
2205 max: None,
2206 repeating: None,
2207 default: Some(LiteralValue::Int(0)),
2208 },
2209 ArgSchema {
2211 kinds: smallvec::smallvec![ArgKind::Number],
2212 required: false,
2213 by_ref: false,
2214 shape: ShapeKind::Scalar,
2215 coercion: CoercionPolicy::NumberLenientText,
2216 max: None,
2217 repeating: None,
2218 default: Some(LiteralValue::Int(0)),
2219 },
2220 ArgSchema {
2222 kinds: smallvec::smallvec![ArgKind::Number],
2223 required: false,
2224 by_ref: false,
2225 shape: ShapeKind::Scalar,
2226 coercion: CoercionPolicy::NumberLenientText,
2227 max: None,
2228 repeating: None,
2229 default: Some(LiteralValue::Int(0)),
2230 },
2231 ArgSchema {
2233 kinds: smallvec::smallvec![ArgKind::Number],
2234 required: false,
2235 by_ref: false,
2236 shape: ShapeKind::Scalar,
2237 coercion: CoercionPolicy::NumberLenientText,
2238 max: None,
2239 repeating: None,
2240 default: Some(LiteralValue::Int(0)),
2241 },
2242 ]
2243 });
2244 &SCHEMA
2245 }
2246
2247 fn eval<'a, 'b, 'c>(
2248 &self,
2249 args: &'c [ArgumentHandle<'a, 'b>],
2250 _ctx: &dyn FunctionContext<'b>,
2251 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2252 if args.len() < 4 {
2253 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2254 ExcelError::new(ExcelErrorKind::Value),
2255 )));
2256 }
2257
2258 let row_fields_view = match args[0].range_view() {
2260 Ok(v) => v,
2261 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2262 };
2263 let col_fields_view = match args[1].range_view() {
2264 Ok(v) => v,
2265 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2266 };
2267 let values_view = match args[2].range_view() {
2268 Ok(v) => v,
2269 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2270 };
2271
2272 let agg_val = args[3].value()?.into_literal();
2274 let aggregation = match GroupAggregation::from_literal(&agg_val) {
2275 Some(a) => a,
2276 None => {
2277 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2278 ExcelError::new(ExcelErrorKind::Value)
2279 .with_message("Invalid aggregation function"),
2280 )));
2281 }
2282 };
2283
2284 let field_headers = if args.len() >= 5 {
2286 match args[4].value()?.into_literal() {
2287 LiteralValue::Int(i) => i as i32,
2288 LiteralValue::Number(n) => n as i32,
2289 _ => 1,
2290 }
2291 } else {
2292 1
2293 };
2294
2295 let row_total_depth = if args.len() >= 6 {
2296 match args[5].value()?.into_literal() {
2297 LiteralValue::Int(i) => i as i32,
2298 LiteralValue::Number(n) => n as i32,
2299 _ => 0,
2300 }
2301 } else {
2302 0
2303 };
2304
2305 let row_sort_order = if args.len() >= 7 {
2306 match args[6].value()?.into_literal() {
2307 LiteralValue::Int(i) => i as i32,
2308 LiteralValue::Number(n) => n as i32,
2309 _ => 0,
2310 }
2311 } else {
2312 0
2313 };
2314
2315 let col_total_depth = if args.len() >= 8 {
2316 match args[7].value()?.into_literal() {
2317 LiteralValue::Int(i) => i as i32,
2318 LiteralValue::Number(n) => n as i32,
2319 _ => 0,
2320 }
2321 } else {
2322 0
2323 };
2324
2325 let col_sort_order = if args.len() >= 9 {
2326 match args[8].value()?.into_literal() {
2327 LiteralValue::Int(i) => i as i32,
2328 LiteralValue::Number(n) => n as i32,
2329 _ => 0,
2330 }
2331 } else {
2332 0
2333 };
2334
2335 let (rf_rows, rf_cols) = row_fields_view.dims();
2336 let (cf_rows, _cf_cols) = col_fields_view.dims();
2337 let (val_rows, _val_cols) = values_view.dims();
2338
2339 if rf_rows != cf_rows || rf_rows != val_rows {
2341 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2342 ExcelError::new(ExcelErrorKind::Value)
2343 .with_message("All ranges must have same number of rows"),
2344 )));
2345 }
2346
2347 let has_headers = field_headers == 1 || field_headers == 3;
2348 let data_start_row = if has_headers { 1 } else { 0 };
2349
2350 if rf_rows <= data_start_row {
2351 return Ok(crate::traits::CalcValue::Range(
2352 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2353 ));
2354 }
2355
2356 let mut row_keys: Vec<String> = Vec::new();
2358 let mut col_keys: Vec<String> = Vec::new();
2359 let mut row_key_set: std::collections::HashSet<String> = std::collections::HashSet::new();
2360 let mut col_key_set: std::collections::HashSet<String> = std::collections::HashSet::new();
2361
2362 let mut pivot_data: HashMap<(String, String), Vec<f64>> = HashMap::new();
2364
2365 for r in data_start_row..rf_rows {
2366 let mut row_key_parts: Vec<String> = Vec::with_capacity(rf_cols);
2368 for c in 0..rf_cols {
2369 row_key_parts.push(literal_to_group_key(&row_fields_view.get_cell(r, c)));
2370 }
2371 let row_key = row_key_parts.join("\x00");
2372
2373 let col_key = literal_to_group_key(&col_fields_view.get_cell(r, 0));
2376
2377 let val = literal_to_num_opt(&values_view.get_cell(r, 0));
2379
2380 if !row_key_set.contains(&row_key) {
2382 row_key_set.insert(row_key.clone());
2383 row_keys.push(row_key.clone());
2384 }
2385 if !col_key_set.contains(&col_key) {
2386 col_key_set.insert(col_key.clone());
2387 col_keys.push(col_key.clone());
2388 }
2389
2390 let entry = pivot_data.entry((row_key, col_key)).or_default();
2392 if let Some(v) = val {
2393 entry.push(v);
2394 }
2395 }
2396
2397 if row_sort_order != 0 {
2399 row_keys.sort_by(|a, b| {
2400 if row_sort_order > 0 {
2401 a.cmp(b)
2402 } else {
2403 b.cmp(a)
2404 }
2405 });
2406 }
2407 if col_sort_order != 0 {
2408 col_keys.sort_by(|a, b| {
2409 if col_sort_order > 0 {
2410 a.cmp(b)
2411 } else {
2412 b.cmp(a)
2413 }
2414 });
2415 }
2416
2417 let mut output: Vec<Vec<LiteralValue>> = Vec::new();
2419
2420 let generate_headers = field_headers == 2 || field_headers == 3;
2422 if generate_headers || has_headers {
2423 let mut header_row: Vec<LiteralValue> = Vec::new();
2424 for _ in 0..rf_cols {
2426 header_row.push(LiteralValue::Empty);
2427 }
2428 for col_key in &col_keys {
2430 let parts: Vec<&str> = col_key.split('\x00').collect();
2432 header_row.push(LiteralValue::Text(parts.join(" ")));
2433 }
2434 if col_total_depth >= 1 {
2436 header_row.push(LiteralValue::Text("Total".to_string()));
2437 }
2438 output.push(header_row);
2439 }
2440
2441 for row_key in &row_keys {
2443 let mut row: Vec<LiteralValue> = Vec::new();
2444
2445 let row_parts: Vec<&str> = row_key.split('\x00').collect();
2447 for part in &row_parts {
2448 row.push(LiteralValue::Text(part.to_string()));
2449 }
2450
2451 let mut row_total_vals: Vec<f64> = Vec::new();
2453 for col_key in &col_keys {
2454 let key = (row_key.clone(), col_key.clone());
2455 let vals = pivot_data.get(&key).map(|v| v.as_slice()).unwrap_or(&[]);
2456 let result = aggregation.apply(vals);
2457
2458 row_total_vals.extend(vals);
2460
2461 if result.is_nan() || vals.is_empty() {
2462 row.push(LiteralValue::Empty);
2463 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2464 row.push(LiteralValue::Int(result as i64));
2465 } else {
2466 row.push(LiteralValue::Number(result));
2467 }
2468 }
2469
2470 if col_total_depth >= 1 {
2472 let result = aggregation.apply(&row_total_vals);
2473 if result.is_nan() {
2474 row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2475 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2476 row.push(LiteralValue::Int(result as i64));
2477 } else {
2478 row.push(LiteralValue::Number(result));
2479 }
2480 }
2481
2482 output.push(row);
2483 }
2484
2485 if row_total_depth >= 1 {
2487 let mut total_row: Vec<LiteralValue> = Vec::new();
2488 total_row.push(LiteralValue::Text("Total".to_string()));
2489 for _ in 1..rf_cols {
2490 total_row.push(LiteralValue::Empty);
2491 }
2492
2493 let mut grand_total_vals: Vec<f64> = Vec::new();
2494 for col_key in &col_keys {
2495 let mut col_vals: Vec<f64> = Vec::new();
2496 for row_key in &row_keys {
2497 let key = (row_key.clone(), col_key.clone());
2498 if let Some(vals) = pivot_data.get(&key) {
2499 col_vals.extend(vals);
2500 }
2501 }
2502 grand_total_vals.extend(&col_vals);
2503 let result = aggregation.apply(&col_vals);
2504 if result.is_nan() {
2505 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2506 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2507 total_row.push(LiteralValue::Int(result as i64));
2508 } else {
2509 total_row.push(LiteralValue::Number(result));
2510 }
2511 }
2512
2513 if col_total_depth >= 1 {
2515 let result = aggregation.apply(&grand_total_vals);
2516 if result.is_nan() {
2517 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2518 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2519 total_row.push(LiteralValue::Int(result as i64));
2520 } else {
2521 total_row.push(LiteralValue::Number(result));
2522 }
2523 }
2524
2525 output.push(total_row);
2526 }
2527
2528 if output.is_empty() {
2529 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2530 ExcelError::new(ExcelErrorKind::Calc),
2531 )));
2532 }
2533
2534 Ok(collapse_if_scalar(output, _ctx.date_system()))
2535 }
2536}
2537
2538#[derive(Debug)]
2541pub struct FilterFn;
2542impl Function for FilterFn {
2600 func_caps!(PURE);
2601 fn name(&self) -> &'static str {
2602 "FILTER"
2603 }
2604 fn min_args(&self) -> usize {
2605 2
2606 }
2607 fn variadic(&self) -> bool {
2608 true
2609 }
2610 fn arg_schema(&self) -> &'static [ArgSchema] {
2611 use once_cell::sync::Lazy;
2612 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2613 vec![
2614 ArgSchema {
2616 kinds: smallvec::smallvec![ArgKind::Range],
2617 required: true,
2618 by_ref: true,
2619 shape: ShapeKind::Range,
2620 coercion: CoercionPolicy::None,
2621 max: None,
2622 repeating: None,
2623 default: None,
2624 },
2625 ArgSchema {
2627 kinds: smallvec::smallvec![ArgKind::Range],
2628 required: true,
2629 by_ref: true,
2630 shape: ShapeKind::Range,
2631 coercion: CoercionPolicy::None,
2632 max: None,
2633 repeating: None,
2634 default: None,
2635 },
2636 ArgSchema {
2638 kinds: smallvec::smallvec![ArgKind::Any],
2639 required: false,
2640 by_ref: false,
2641 shape: ShapeKind::Scalar,
2642 coercion: CoercionPolicy::None,
2643 max: None,
2644 repeating: None,
2645 default: None,
2646 },
2647 ]
2648 });
2649 &SCHEMA
2650 }
2651 fn eval<'a, 'b, 'c>(
2652 &self,
2653 args: &'c [ArgumentHandle<'a, 'b>],
2654 _ctx: &dyn FunctionContext<'b>,
2655 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2656 if args.len() < 2 {
2657 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2658 ExcelError::new(ExcelErrorKind::Value),
2659 )));
2660 }
2661 let array_view = args[0].range_view()?;
2662 let include_view = args[1].range_view()?;
2663
2664 let (array_rows, array_cols) = array_view.dims();
2665 if array_rows == 0 || array_cols == 0 {
2666 return Ok(crate::traits::CalcValue::Range(
2667 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2668 ));
2669 }
2670
2671 let (include_rows, include_cols) = include_view.dims();
2672 if include_rows != array_rows && include_rows != 1 {
2673 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2674 ExcelError::new(ExcelErrorKind::Value),
2675 )));
2676 }
2677
2678 let mut result: Vec<Vec<LiteralValue>> = Vec::new();
2679 for r in 0..array_rows {
2680 let include_r = if include_rows == array_rows { r } else { 0 };
2681 let mut include = false;
2682 for c in 0..include_cols {
2683 if include_view.get_cell(include_r, c).is_truthy() {
2684 include = true;
2685 break;
2686 }
2687 }
2688
2689 if include {
2690 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(array_cols);
2691 for c in 0..array_cols {
2692 row_out.push(array_view.get_cell(r, c));
2693 }
2694 result.push(row_out);
2695 }
2696 }
2697
2698 if result.is_empty() {
2699 if args.len() >= 3 {
2700 return args[2].value();
2701 }
2702 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2703 ExcelError::new(ExcelErrorKind::Calc),
2704 )));
2705 }
2706
2707 Ok(crate::traits::CalcValue::Range(
2708 crate::engine::range_view::RangeView::from_owned_rows(result, _ctx.date_system()),
2709 ))
2710 }
2711}
2712
2713#[derive(Debug)]
2716pub struct UniqueFn;
2717impl Function for UniqueFn {
2773 func_caps!(PURE);
2774 fn name(&self) -> &'static str {
2775 "UNIQUE"
2776 }
2777 fn min_args(&self) -> usize {
2778 1
2779 }
2780 fn variadic(&self) -> bool {
2781 true
2782 }
2783 fn arg_schema(&self) -> &'static [ArgSchema] {
2784 use once_cell::sync::Lazy;
2785 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2786 vec![
2787 ArgSchema {
2788 kinds: smallvec::smallvec![ArgKind::Range],
2789 required: true,
2790 by_ref: true,
2791 shape: ShapeKind::Range,
2792 coercion: CoercionPolicy::None,
2793 max: None,
2794 repeating: None,
2795 default: None,
2796 },
2797 ArgSchema {
2798 kinds: smallvec::smallvec![ArgKind::Logical],
2799 required: false,
2800 by_ref: false,
2801 shape: ShapeKind::Scalar,
2802 coercion: CoercionPolicy::Logical,
2803 max: None,
2804 repeating: None,
2805 default: Some(LiteralValue::Boolean(false)),
2806 },
2807 ArgSchema {
2808 kinds: smallvec::smallvec![ArgKind::Logical],
2809 required: false,
2810 by_ref: false,
2811 shape: ShapeKind::Scalar,
2812 coercion: CoercionPolicy::Logical,
2813 max: None,
2814 repeating: None,
2815 default: Some(LiteralValue::Boolean(false)),
2816 },
2817 ]
2818 });
2819 &SCHEMA
2820 }
2821 fn eval<'a, 'b, 'c>(
2822 &self,
2823 args: &'c [ArgumentHandle<'a, 'b>],
2824 _ctx: &dyn FunctionContext<'b>,
2825 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2826 let view = match args[0].range_view() {
2827 Ok(v) => v,
2828 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2829 };
2830 let (rows, cols) = view.dims();
2831 if rows == 0 || cols == 0 {
2832 return Ok(crate::traits::CalcValue::Range(
2833 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2834 ));
2835 }
2836
2837 let by_col = if args.len() >= 2 {
2838 matches!(args[1].value()?.into_literal(), LiteralValue::Boolean(true))
2839 } else {
2840 false
2841 };
2842 let exactly_once = if args.len() >= 3 {
2843 matches!(args[2].value()?.into_literal(), LiteralValue::Boolean(true))
2844 } else {
2845 false
2846 };
2847
2848 if by_col {
2849 #[derive(Hash, Eq, PartialEq, Clone)]
2850 struct ColKey(Vec<LiteralValue>);
2851
2852 let mut order: Vec<ColKey> = Vec::new();
2853 let mut counts: HashMap<ColKey, usize> = HashMap::new();
2854
2855 for c in 0..cols {
2856 let mut col_vals: Vec<LiteralValue> = Vec::with_capacity(rows);
2857 for r in 0..rows {
2858 col_vals.push(view.get_cell(r, c));
2859 }
2860 let key = ColKey(col_vals);
2861 if !counts.contains_key(&key) {
2862 order.push(key.clone());
2863 }
2864 *counts.entry(key).or_insert(0) += 1;
2865 }
2866
2867 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
2868 for k in order {
2869 if !exactly_once || counts.get(&k) == Some(&1) {
2870 out.push(k.0);
2871 }
2872 }
2873 return Ok(collapse_if_scalar(out, _ctx.date_system()));
2874 }
2875
2876 #[derive(Hash, Eq, PartialEq, Clone)]
2877 struct RowKey(Vec<LiteralValue>);
2878
2879 let mut order: Vec<RowKey> = Vec::new();
2880 let mut counts: HashMap<RowKey, usize> = HashMap::new();
2881 for r in 0..rows {
2882 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
2883 for c in 0..cols {
2884 row_vals.push(view.get_cell(r, c));
2885 }
2886 let key = RowKey(row_vals);
2887 if !counts.contains_key(&key) {
2888 order.push(key.clone());
2889 }
2890 *counts.entry(key).or_insert(0) += 1;
2891 }
2892
2893 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
2894 for k in order {
2895 if !exactly_once || counts.get(&k) == Some(&1) {
2896 out.push(k.0);
2897 }
2898 }
2899 Ok(collapse_if_scalar(out, _ctx.date_system()))
2900 }
2901}
2902
2903#[derive(Debug)]
2906pub struct SequenceFn;
2907impl Function for SequenceFn {
2952 func_caps!(PURE);
2953 fn name(&self) -> &'static str {
2954 "SEQUENCE"
2955 }
2956 fn min_args(&self) -> usize {
2957 1
2958 }
2959 fn variadic(&self) -> bool {
2960 true
2961 }
2962 fn arg_schema(&self) -> &'static [ArgSchema] {
2963 use once_cell::sync::Lazy;
2964 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2965 vec![
2966 ArgSchema {
2968 kinds: smallvec::smallvec![ArgKind::Number],
2969 required: true,
2970 by_ref: false,
2971 shape: ShapeKind::Scalar,
2972 coercion: CoercionPolicy::NumberLenientText,
2973 max: None,
2974 repeating: None,
2975 default: None,
2976 },
2977 ArgSchema {
2979 kinds: smallvec::smallvec![ArgKind::Number],
2980 required: false,
2981 by_ref: false,
2982 shape: ShapeKind::Scalar,
2983 coercion: CoercionPolicy::NumberLenientText,
2984 max: None,
2985 repeating: None,
2986 default: Some(LiteralValue::Int(1)),
2987 },
2988 ArgSchema {
2990 kinds: smallvec::smallvec![ArgKind::Number],
2991 required: false,
2992 by_ref: false,
2993 shape: ShapeKind::Scalar,
2994 coercion: CoercionPolicy::NumberLenientText,
2995 max: None,
2996 repeating: None,
2997 default: Some(LiteralValue::Int(1)),
2998 },
2999 ArgSchema {
3001 kinds: smallvec::smallvec![ArgKind::Number],
3002 required: false,
3003 by_ref: false,
3004 shape: ShapeKind::Scalar,
3005 coercion: CoercionPolicy::NumberLenientText,
3006 max: None,
3007 repeating: None,
3008 default: Some(LiteralValue::Int(1)),
3009 },
3010 ]
3011 });
3012 &SCHEMA
3013 }
3014 fn eval<'a, 'b, 'c>(
3015 &self,
3016 args: &'c [ArgumentHandle<'a, 'b>],
3017 _ctx: &dyn FunctionContext<'b>,
3018 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3019 let num = |a: &ArgumentHandle| -> Result<f64, ExcelError> {
3021 Ok(match a.value()?.into_literal() {
3022 LiteralValue::Int(i) => i as f64,
3023 LiteralValue::Number(n) => n,
3024 _other => {
3025 return Err(ExcelError::new(ExcelErrorKind::Value));
3026 }
3027 })
3028 };
3029 let rows_f = num(&args[0])?;
3030 let rows = rows_f as i64;
3031 let cols = if args.len() >= 2 {
3032 num(&args[1])? as i64
3033 } else {
3034 1
3035 };
3036 let start = if args.len() >= 3 { num(&args[2])? } else { 1.0 };
3037 let step = if args.len() >= 4 { num(&args[3])? } else { 1.0 };
3038 if rows <= 0 || cols <= 0 {
3039 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3040 ExcelError::new(ExcelErrorKind::Value),
3041 )));
3042 }
3043 let total = rows.saturating_mul(cols);
3044 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows as usize);
3046 let mut current = start;
3047 for _r in 0..rows {
3048 let mut row_vec: Vec<LiteralValue> = Vec::with_capacity(cols as usize);
3049 for _c in 0..cols {
3050 if (current.fract().abs() < 1e-12) && current.abs() < (i64::MAX as f64) {
3052 row_vec.push(LiteralValue::Int(current as i64));
3053 } else {
3054 row_vec.push(LiteralValue::Number(current));
3055 }
3056 current += step;
3057 }
3058 out.push(row_vec);
3059 }
3060
3061 Ok(collapse_if_scalar(out, _ctx.date_system()))
3062 }
3063}
3064
3065#[derive(Debug)]
3068pub struct TransposeFn;
3069impl Function for TransposeFn {
3122 func_caps!(PURE);
3123 fn name(&self) -> &'static str {
3124 "TRANSPOSE"
3125 }
3126 fn min_args(&self) -> usize {
3127 1
3128 }
3129 fn variadic(&self) -> bool {
3130 false
3131 }
3132 fn arg_schema(&self) -> &'static [ArgSchema] {
3133 use once_cell::sync::Lazy;
3134 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3135 vec![ArgSchema {
3136 kinds: smallvec::smallvec![ArgKind::Range],
3137 required: true,
3138 by_ref: true,
3139 shape: ShapeKind::Range,
3140 coercion: CoercionPolicy::None,
3141 max: None,
3142 repeating: None,
3143 default: None,
3144 }]
3145 });
3146 &SCHEMA
3147 }
3148 fn eval<'a, 'b, 'c>(
3149 &self,
3150 args: &'c [ArgumentHandle<'a, 'b>],
3151 _ctx: &dyn FunctionContext<'b>,
3152 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3153 let view = match args[0].range_view() {
3154 Ok(v) => v,
3155 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3156 };
3157 let (rows, cols) = view.dims();
3158 if rows == 0 || cols == 0 {
3159 return Ok(crate::traits::CalcValue::Range(
3160 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3161 ));
3162 }
3163
3164 let mut out: Vec<Vec<LiteralValue>> = vec![Vec::with_capacity(rows); cols];
3165 for (c, col) in out.iter_mut().enumerate().take(cols) {
3166 for r in 0..rows {
3167 col.push(view.get_cell(r, c));
3168 }
3169 }
3170 Ok(collapse_if_scalar(out, _ctx.date_system()))
3171 }
3172}
3173
3174#[derive(Debug)]
3177pub struct TakeFn;
3178impl Function for TakeFn {
3234 func_caps!(PURE);
3235 fn name(&self) -> &'static str {
3236 "TAKE"
3237 }
3238 fn min_args(&self) -> usize {
3239 2
3240 }
3241 fn variadic(&self) -> bool {
3242 true
3243 }
3244 fn arg_schema(&self) -> &'static [ArgSchema] {
3245 use once_cell::sync::Lazy;
3246 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3247 vec![
3248 ArgSchema {
3249 kinds: smallvec::smallvec![ArgKind::Range],
3250 required: true,
3251 by_ref: true,
3252 shape: ShapeKind::Range,
3253 coercion: CoercionPolicy::None,
3254 max: None,
3255 repeating: None,
3256 default: None,
3257 },
3258 ArgSchema {
3259 kinds: smallvec::smallvec![ArgKind::Number],
3260 required: true,
3261 by_ref: false,
3262 shape: ShapeKind::Scalar,
3263 coercion: CoercionPolicy::NumberLenientText,
3264 max: None,
3265 repeating: None,
3266 default: None,
3267 },
3268 ArgSchema {
3269 kinds: smallvec::smallvec![ArgKind::Number],
3270 required: false,
3271 by_ref: false,
3272 shape: ShapeKind::Scalar,
3273 coercion: CoercionPolicy::NumberLenientText,
3274 max: None,
3275 repeating: None,
3276 default: None,
3277 },
3278 ]
3279 });
3280 &SCHEMA
3281 }
3282 fn eval<'a, 'b, 'c>(
3283 &self,
3284 args: &'c [ArgumentHandle<'a, 'b>],
3285 _ctx: &dyn FunctionContext<'b>,
3286 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3287 let view = match args[0].range_view() {
3288 Ok(v) => v,
3289 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3290 };
3291 let (rows, cols) = view.dims();
3292 if rows == 0 || cols == 0 {
3293 return Ok(crate::traits::CalcValue::Range(
3294 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3295 ));
3296 }
3297
3298 let height = rows as i64;
3299 let width = cols as i64;
3300
3301 let num = |a: &ArgumentHandle| -> Result<i64, ExcelError> {
3302 Ok(match a.value()?.into_literal() {
3303 LiteralValue::Int(i) => i,
3304 LiteralValue::Number(n) => n as i64,
3305 _ => 0,
3306 })
3307 };
3308 let take_rows = num(&args[1])?;
3309 let take_cols = if args.len() >= 3 {
3310 Some(num(&args[2])?)
3311 } else {
3312 None
3313 };
3314
3315 if take_rows.abs() > height {
3316 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3317 ExcelError::new(ExcelErrorKind::Value),
3318 )));
3319 }
3320
3321 let (row_start, row_end) = if take_rows >= 0 {
3322 (0usize, take_rows as usize)
3323 } else {
3324 ((height + take_rows) as usize, height as usize)
3325 };
3326
3327 let (col_start, col_end) = if let Some(tc) = take_cols {
3328 if tc.abs() > width {
3329 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3330 ExcelError::new(ExcelErrorKind::Value),
3331 )));
3332 }
3333 if tc >= 0 {
3334 (0usize, tc as usize)
3335 } else {
3336 ((width + tc) as usize, width as usize)
3337 }
3338 } else {
3339 (0usize, width as usize)
3340 };
3341
3342 if row_start >= row_end || col_start >= col_end {
3343 return Ok(crate::traits::CalcValue::Range(
3344 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3345 ));
3346 }
3347
3348 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_end - row_start);
3349 for r in row_start..row_end {
3350 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(col_end - col_start);
3351 for c in col_start..col_end {
3352 row_out.push(view.get_cell(r, c));
3353 }
3354 out.push(row_out);
3355 }
3356
3357 Ok(collapse_if_scalar(out, _ctx.date_system()))
3358 }
3359}
3360
3361#[derive(Debug)]
3364pub struct DropFn;
3365impl Function for DropFn {
3418 func_caps!(PURE);
3419 fn name(&self) -> &'static str {
3420 "DROP"
3421 }
3422 fn min_args(&self) -> usize {
3423 2
3424 }
3425 fn variadic(&self) -> bool {
3426 true
3427 }
3428 fn arg_schema(&self) -> &'static [ArgSchema] {
3429 use once_cell::sync::Lazy;
3430 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3431 vec![
3432 ArgSchema {
3433 kinds: smallvec::smallvec![ArgKind::Range],
3434 required: true,
3435 by_ref: true,
3436 shape: ShapeKind::Range,
3437 coercion: CoercionPolicy::None,
3438 max: None,
3439 repeating: None,
3440 default: None,
3441 },
3442 ArgSchema {
3443 kinds: smallvec::smallvec![ArgKind::Number],
3444 required: true,
3445 by_ref: false,
3446 shape: ShapeKind::Scalar,
3447 coercion: CoercionPolicy::NumberLenientText,
3448 max: None,
3449 repeating: None,
3450 default: None,
3451 },
3452 ArgSchema {
3453 kinds: smallvec::smallvec![ArgKind::Number],
3454 required: false,
3455 by_ref: false,
3456 shape: ShapeKind::Scalar,
3457 coercion: CoercionPolicy::NumberLenientText,
3458 max: None,
3459 repeating: None,
3460 default: None,
3461 },
3462 ]
3463 });
3464 &SCHEMA
3465 }
3466 fn eval<'a, 'b, 'c>(
3467 &self,
3468 args: &'c [ArgumentHandle<'a, 'b>],
3469 _ctx: &dyn FunctionContext<'b>,
3470 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3471 let view = match args[0].range_view() {
3472 Ok(v) => v,
3473 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3474 };
3475 let (rows, cols) = view.dims();
3476 if rows == 0 || cols == 0 {
3477 return Ok(crate::traits::CalcValue::Range(
3478 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3479 ));
3480 }
3481
3482 let height = rows as i64;
3483 let width = cols as i64;
3484
3485 let num = |a: &ArgumentHandle| -> Result<i64, ExcelError> {
3486 Ok(match a.value()?.into_literal() {
3487 LiteralValue::Int(i) => i,
3488 LiteralValue::Number(n) => n as i64,
3489 _ => 0,
3490 })
3491 };
3492 let drop_rows = num(&args[1])?;
3493 let drop_cols = if args.len() >= 3 {
3494 Some(num(&args[2])?)
3495 } else {
3496 None
3497 };
3498
3499 let (row_start, row_end) = if drop_rows >= 0 {
3500 ((drop_rows as usize).min(height as usize), height as usize)
3501 } else {
3502 (0usize, (height + drop_rows).max(0) as usize)
3503 };
3504
3505 let (col_start, col_end) = if let Some(dc) = drop_cols {
3506 if dc >= 0 {
3507 ((dc as usize).min(width as usize), width as usize)
3508 } else {
3509 (0usize, (width + dc).max(0) as usize)
3510 }
3511 } else {
3512 (0usize, width as usize)
3513 };
3514
3515 if row_start >= row_end || col_start >= col_end {
3516 return Ok(crate::traits::CalcValue::Range(
3517 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3518 ));
3519 }
3520
3521 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_end - row_start);
3522 for r in row_start..row_end {
3523 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(col_end - col_start);
3524 for c in col_start..col_end {
3525 row_out.push(view.get_cell(r, c));
3526 }
3527 out.push(row_out);
3528 }
3529
3530 Ok(collapse_if_scalar(out, _ctx.date_system()))
3531 }
3532}
3533
3534pub fn register_builtins() {
3535 use crate::function_registry::register_function;
3536 use std::sync::Arc;
3537 register_function(Arc::new(XLookupFn));
3538 register_function(Arc::new(FilterFn));
3539 register_function(Arc::new(UniqueFn));
3540 register_function(Arc::new(SequenceFn));
3541 register_function(Arc::new(TransposeFn));
3542 register_function(Arc::new(TakeFn));
3543 register_function(Arc::new(DropFn));
3544 register_function(Arc::new(XMatchFn));
3545 register_function(Arc::new(SortFn));
3546 register_function(Arc::new(SortByFn));
3547 register_function(Arc::new(RandArrayFn));
3548 register_function(Arc::new(GroupByFn));
3549 register_function(Arc::new(PivotByFn));
3550}
3551
3552#[cfg(test)]
3555mod tests {
3556 use super::*;
3557 use crate::test_workbook::TestWorkbook;
3558 use crate::traits::ArgumentHandle;
3559 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
3560 use std::sync::Arc;
3561
3562 #[test]
3563 fn test_all_dynamic_functions_registered() {
3564 crate::builtins::load_builtins();
3566
3567 let functions = [
3568 "XLOOKUP",
3569 "FILTER",
3570 "UNIQUE",
3571 "SEQUENCE",
3572 "TRANSPOSE",
3573 "TAKE",
3574 "DROP",
3575 "XMATCH",
3576 "SORT",
3577 "SORTBY",
3578 "RANDARRAY",
3579 "GROUPBY",
3580 "PIVOTBY",
3581 ];
3582
3583 for name in &functions {
3584 let result = crate::function_registry::get("", name);
3585 assert!(result.is_some(), "Function {} should be registered", name);
3586 }
3587 }
3588
3589 fn lit(v: LiteralValue) -> ASTNode {
3590 ASTNode::new(ASTNodeType::Literal(v), None)
3591 }
3592
3593 fn range(r: &str, sr: u32, sc: u32, er: u32, ec: u32) -> ASTNode {
3594 ASTNode::new(
3595 ASTNodeType::Reference {
3596 original: r.into(),
3597 reference: ReferenceType::range(None, Some(sr), Some(sc), Some(er), Some(ec)),
3598 },
3599 None,
3600 )
3601 }
3602
3603 #[test]
3604 fn xlookup_basic_exact_and_if_not_found() {
3605 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3606 let wb = wb
3607 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("a".into()))
3608 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("b".into()))
3609 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3610 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20));
3611 let ctx = wb.interpreter();
3612 let lookup_range = range("A1:A2", 1, 1, 2, 1);
3613 let return_range = range("B1:B2", 1, 2, 2, 2);
3614 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3615 let key_b = lit(LiteralValue::Text("b".into()));
3616 let args = vec![
3617 ArgumentHandle::new(&key_b, &ctx),
3618 ArgumentHandle::new(&lookup_range, &ctx),
3619 ArgumentHandle::new(&return_range, &ctx),
3620 ];
3621 let v = f
3622 .dispatch(&args, &ctx.function_context(None))
3623 .unwrap()
3624 .into_literal();
3625 assert_eq!(v, LiteralValue::Number(20.0));
3626 let key_missing = lit(LiteralValue::Text("z".into()));
3627 let if_nf = lit(LiteralValue::Text("NF".into()));
3628 let args_nf = vec![
3629 ArgumentHandle::new(&key_missing, &ctx),
3630 ArgumentHandle::new(&lookup_range, &ctx),
3631 ArgumentHandle::new(&return_range, &ctx),
3632 ArgumentHandle::new(&if_nf, &ctx),
3633 ];
3634 let v_nf = f
3635 .dispatch(&args_nf, &ctx.function_context(None))
3636 .unwrap()
3637 .into_literal();
3638 assert_eq!(v_nf, LiteralValue::Text("NF".into()));
3639 }
3640
3641 #[test]
3642 fn xlookup_match_modes_next_smaller_larger() {
3643 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3644 let wb = wb
3645 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3646 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
3647 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
3648 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(1))
3649 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(2))
3650 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(3));
3651 let ctx = wb.interpreter();
3652 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3653 let return_range = range("B1:B3", 1, 2, 3, 2);
3654 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3655 let needle_25 = lit(LiteralValue::Int(25));
3656 let mm_next_smaller = lit(LiteralValue::Int(-1));
3657 let nf_text = lit(LiteralValue::Text("NF".into()));
3658 let args_smaller = vec![
3659 ArgumentHandle::new(&needle_25, &ctx),
3660 ArgumentHandle::new(&lookup_range, &ctx),
3661 ArgumentHandle::new(&return_range, &ctx),
3662 ArgumentHandle::new(&nf_text, &ctx),
3663 ArgumentHandle::new(&mm_next_smaller, &ctx),
3664 ];
3665 let v_smaller = f
3666 .dispatch(&args_smaller, &ctx.function_context(None))
3667 .unwrap()
3668 .into_literal();
3669 assert_eq!(v_smaller, LiteralValue::Number(2.0));
3670 let mm_next_larger = lit(LiteralValue::Int(1));
3671 let nf_text2 = lit(LiteralValue::Text("NF".into()));
3672 let args_larger = vec![
3673 ArgumentHandle::new(&needle_25, &ctx),
3674 ArgumentHandle::new(&lookup_range, &ctx),
3675 ArgumentHandle::new(&return_range, &ctx),
3676 ArgumentHandle::new(&nf_text2, &ctx),
3677 ArgumentHandle::new(&mm_next_larger, &ctx),
3678 ];
3679 let v_larger = f
3680 .dispatch(&args_larger, &ctx.function_context(None))
3681 .unwrap()
3682 .into_literal();
3683 assert_eq!(v_larger, LiteralValue::Number(3.0));
3684 }
3685
3686 #[test]
3687 fn xlookup_wildcard_and_not_found_default_na() {
3688 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3689 let wb = wb
3690 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Alpha".into()))
3691 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Beta".into()))
3692 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Gamma".into()))
3693 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
3694 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
3695 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(300));
3696 let ctx = wb.interpreter();
3697 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3698 let return_range = range("B1:B3", 1, 2, 3, 2);
3699 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3700 let pattern = lit(LiteralValue::Text("*et*".into()));
3702 let match_mode_wild = lit(LiteralValue::Int(2));
3703 let nf_binding = lit(LiteralValue::Text("NF".into()));
3704 let args_wild = vec![
3705 ArgumentHandle::new(&pattern, &ctx),
3706 ArgumentHandle::new(&lookup_range, &ctx),
3707 ArgumentHandle::new(&return_range, &ctx),
3708 ArgumentHandle::new(&nf_binding, &ctx),
3709 ArgumentHandle::new(&match_mode_wild, &ctx),
3710 ];
3711 let v_wild = f
3712 .dispatch(&args_wild, &ctx.function_context(None))
3713 .unwrap()
3714 .into_literal();
3715 assert_eq!(v_wild, LiteralValue::Number(200.0));
3716 let pattern_lit_star = lit(LiteralValue::Text("~*eta".into()));
3718 let args_lit = vec![
3719 ArgumentHandle::new(&pattern_lit_star, &ctx),
3720 ArgumentHandle::new(&lookup_range, &ctx),
3721 ArgumentHandle::new(&return_range, &ctx),
3722 ArgumentHandle::new(&nf_binding, &ctx),
3723 ArgumentHandle::new(&match_mode_wild, &ctx),
3724 ];
3725 let v_lit = f
3726 .dispatch(&args_lit, &ctx.function_context(None))
3727 .unwrap()
3728 .into_literal();
3729 match v_lit {
3730 LiteralValue::Text(s) => assert_eq!(s, "NF"),
3731 other => panic!("expected NF text got {other:?}"),
3732 }
3733 let missing = lit(LiteralValue::Text("Zeta".into()));
3735 let args_nf = vec![
3736 ArgumentHandle::new(&missing, &ctx),
3737 ArgumentHandle::new(&lookup_range, &ctx),
3738 ArgumentHandle::new(&return_range, &ctx),
3739 ];
3740 let v_nf = f
3741 .dispatch(&args_nf, &ctx.function_context(None))
3742 .unwrap()
3743 .into_literal();
3744 match v_nf {
3745 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Na),
3746 other => panic!("expected #N/A got {other:?}"),
3747 }
3748 }
3749
3750 #[test]
3751 fn xlookup_reverse_search_mode_picks_last() {
3752 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3753 let wb = wb
3754 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3755 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3756 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(1))
3757 .with_cell_a1("Sheet1", "B1", LiteralValue::Text("First".into()))
3758 .with_cell_a1("Sheet1", "B2", LiteralValue::Text("Mid".into()))
3759 .with_cell_a1("Sheet1", "B3", LiteralValue::Text("Last".into()));
3760 let ctx = wb.interpreter();
3761 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3762 let return_range = range("B1:B3", 1, 2, 3, 2);
3763 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3764 let needle_one = lit(LiteralValue::Int(1));
3765 let search_rev = lit(LiteralValue::Int(-1));
3766 let nf_binding2 = lit(LiteralValue::Text("NF".into()));
3767 let match_mode_zero = lit(LiteralValue::Int(0));
3768 let args_rev = vec![
3769 ArgumentHandle::new(&needle_one, &ctx),
3770 ArgumentHandle::new(&lookup_range, &ctx),
3771 ArgumentHandle::new(&return_range, &ctx),
3772 ArgumentHandle::new(&nf_binding2, &ctx),
3773 ArgumentHandle::new(&match_mode_zero, &ctx),
3774 ArgumentHandle::new(&search_rev, &ctx),
3775 ];
3776 let v_rev = f
3777 .dispatch(&args_rev, &ctx.function_context(None))
3778 .unwrap()
3779 .into_literal();
3780 assert_eq!(v_rev, LiteralValue::Text("Last".into()));
3781 }
3782
3783 #[test]
3784 fn xlookup_horizontal_returns_column_vector_for_matrix_return() {
3785 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3786 let wb = wb
3787 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3788 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(20))
3789 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(30))
3790 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(1))
3791 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(2))
3792 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(3))
3793 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(4))
3794 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(5))
3795 .with_cell_a1("Sheet1", "C3", LiteralValue::Int(6));
3796 let ctx = wb.interpreter();
3797 let lookup_range = range("A1:C1", 1, 1, 1, 3);
3798 let return_range = range("A2:C3", 2, 1, 3, 3);
3799 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3800 let needle = lit(LiteralValue::Int(20));
3801 let args = vec![
3802 ArgumentHandle::new(&needle, &ctx),
3803 ArgumentHandle::new(&lookup_range, &ctx),
3804 ArgumentHandle::new(&return_range, &ctx),
3805 ];
3806 let v = f
3807 .dispatch(&args, &ctx.function_context(None))
3808 .unwrap()
3809 .into_literal();
3810 match v {
3811 LiteralValue::Array(a) => {
3812 assert_eq!(
3813 a,
3814 vec![
3815 vec![LiteralValue::Number(2.0)],
3816 vec![LiteralValue::Number(5.0)]
3817 ]
3818 );
3819 }
3820 other => panic!("expected array got {other:?}"),
3821 }
3822 }
3823
3824 #[test]
3825 fn xlookup_vertical_returns_row_vector_for_matrix_return() {
3826 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3827 let wb = wb
3828 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3829 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
3830 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
3831 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(101))
3832 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(102))
3833 .with_cell_a1("Sheet1", "D1", LiteralValue::Int(103))
3834 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(201))
3835 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(202))
3836 .with_cell_a1("Sheet1", "D2", LiteralValue::Int(203))
3837 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(301))
3838 .with_cell_a1("Sheet1", "C3", LiteralValue::Int(302))
3839 .with_cell_a1("Sheet1", "D3", LiteralValue::Int(303));
3840 let ctx = wb.interpreter();
3841 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3842 let return_range = range("B1:D3", 1, 2, 3, 4);
3843 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3844 let needle = lit(LiteralValue::Int(20));
3845 let args = vec![
3846 ArgumentHandle::new(&needle, &ctx),
3847 ArgumentHandle::new(&lookup_range, &ctx),
3848 ArgumentHandle::new(&return_range, &ctx),
3849 ];
3850 let v = f
3851 .dispatch(&args, &ctx.function_context(None))
3852 .unwrap()
3853 .into_literal();
3854 match v {
3855 LiteralValue::Array(a) => {
3856 assert_eq!(
3857 a,
3858 vec![vec![
3859 LiteralValue::Number(201.0),
3860 LiteralValue::Number(202.0),
3861 LiteralValue::Number(203.0)
3862 ]]
3863 );
3864 }
3865 other => panic!("expected array got {other:?}"),
3866 }
3867 }
3868
3869 #[test]
3870 fn filter_basic_and_if_empty() {
3871 let wb = TestWorkbook::new().with_function(Arc::new(FilterFn));
3872 let wb = wb
3873 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3874 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3875 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3876 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
3877 .with_cell_a1("Sheet1", "C1", LiteralValue::Boolean(true))
3878 .with_cell_a1("Sheet1", "C2", LiteralValue::Boolean(false));
3879 let ctx = wb.interpreter();
3880 let array_range = range("A1:B2", 1, 1, 2, 2);
3881 let include_range = range("C1:C2", 1, 3, 2, 3);
3882 let f = ctx.context.get_function("", "FILTER").unwrap();
3883 let args = vec![
3884 ArgumentHandle::new(&array_range, &ctx),
3885 ArgumentHandle::new(&include_range, &ctx),
3886 ];
3887 let v = f
3888 .dispatch(&args, &ctx.function_context(None))
3889 .unwrap()
3890 .into_literal();
3891 match v {
3892 LiteralValue::Array(a) => {
3893 assert_eq!(a.len(), 1);
3894 assert_eq!(
3895 a[0],
3896 vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)]
3897 );
3898 }
3899 other => panic!("expected array got {other:?}"),
3900 }
3901 let wb2 = wb
3902 .with_cell_a1("Sheet1", "C1", LiteralValue::Boolean(false))
3903 .with_cell_a1("Sheet1", "C2", LiteralValue::Boolean(false));
3904 let ctx2 = wb2.interpreter();
3905 let f2 = ctx2.context.get_function("", "FILTER").unwrap();
3906 let empty_text = lit(LiteralValue::Text("EMPTY".into()));
3907 let args_empty = vec![
3908 ArgumentHandle::new(&array_range, &ctx2),
3909 ArgumentHandle::new(&include_range, &ctx2),
3910 ArgumentHandle::new(&empty_text, &ctx2),
3911 ];
3912 let v_empty = f2
3913 .dispatch(&args_empty, &ctx2.function_context(None))
3914 .unwrap()
3915 .into_literal();
3916 assert_eq!(v_empty, LiteralValue::Text("EMPTY".into()));
3917 }
3918
3919 #[test]
3920 fn unique_basic_and_exactly_once() {
3921 let wb = TestWorkbook::new().with_function(Arc::new(UniqueFn));
3922 let wb = wb
3923 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3924 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(1))
3925 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(2))
3926 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(3));
3927 let ctx = wb.interpreter();
3928 let range = range("A1:A4", 1, 1, 4, 1);
3929 let f = ctx.context.get_function("", "UNIQUE").unwrap();
3930 let args = vec![ArgumentHandle::new(&range, &ctx)];
3931 let v = f
3932 .dispatch(&args, &ctx.function_context(None))
3933 .unwrap()
3934 .into_literal();
3935 match v {
3936 LiteralValue::Array(a) => {
3937 assert_eq!(a.len(), 3);
3938 assert_eq!(a[0][0], LiteralValue::Number(1.0));
3939 }
3940 _ => panic!("expected array"),
3941 }
3942 }
3943
3944 #[test]
3945 fn sequence_basic_rows_cols_step() {
3946 let wb = TestWorkbook::new().with_function(Arc::new(SequenceFn));
3947 let ctx = wb.interpreter();
3948 let f = ctx.context.get_function("", "SEQUENCE").unwrap();
3949 let rows = lit(LiteralValue::Int(2));
3950 let cols = lit(LiteralValue::Int(3));
3951 let start = lit(LiteralValue::Int(5));
3952 let step = lit(LiteralValue::Int(2));
3953 let args = vec![
3954 ArgumentHandle::new(&rows, &ctx),
3955 ArgumentHandle::new(&cols, &ctx),
3956 ArgumentHandle::new(&start, &ctx),
3957 ArgumentHandle::new(&step, &ctx),
3958 ];
3959 let v = f
3960 .dispatch(&args, &ctx.function_context(None))
3961 .unwrap()
3962 .into_literal();
3963 match v {
3964 LiteralValue::Array(a) => {
3965 assert_eq!(a.len(), 2);
3966 assert_eq!(a[0][0], LiteralValue::Number(5.0));
3967 }
3968 other => panic!("expected array got {other:?}"),
3969 }
3970 }
3971
3972 #[test]
3973 fn transpose_basic() {
3974 let wb = TestWorkbook::new().with_function(Arc::new(TransposeFn));
3975 let wb = wb
3976 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3977 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3978 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3979 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20));
3980 let ctx = wb.interpreter();
3981 let arr = range("A1:B2", 1, 1, 2, 2);
3982 let f = ctx.context.get_function("", "TRANSPOSE").unwrap();
3983 let args = vec![ArgumentHandle::new(&arr, &ctx)];
3984 let v = f
3985 .dispatch(&args, &ctx.function_context(None))
3986 .unwrap()
3987 .into_literal();
3988 match v {
3989 LiteralValue::Array(a) => {
3990 assert_eq!(a.len(), 2);
3991 assert_eq!(
3992 a[0],
3993 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)]
3994 );
3995 }
3996 other => panic!("expected array got {other:?}"),
3997 }
3998 }
3999
4000 #[test]
4001 fn take_basic() {
4002 let wb = TestWorkbook::new().with_function(Arc::new(TakeFn));
4003 let wb = wb
4004 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4005 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2));
4006 let ctx = wb.interpreter();
4007 let arr = range("A1:A2", 1, 1, 2, 1);
4008 let f = ctx.context.get_function("", "TAKE").unwrap();
4009 let one = lit(LiteralValue::Int(1));
4010 let args = vec![
4011 ArgumentHandle::new(&arr, &ctx),
4012 ArgumentHandle::new(&one, &ctx),
4013 ];
4014 let v = f
4015 .dispatch(&args, &ctx.function_context(None))
4016 .unwrap()
4017 .into_literal();
4018 assert_eq!(v, LiteralValue::Number(1.0));
4019 }
4020
4021 #[test]
4022 fn drop_basic() {
4023 let wb = TestWorkbook::new().with_function(Arc::new(DropFn));
4024 let wb = wb
4025 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4026 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2));
4027 let ctx = wb.interpreter();
4028 let arr = range("A1:A2", 1, 1, 2, 1);
4029 let f = ctx.context.get_function("", "DROP").unwrap();
4030 let one = lit(LiteralValue::Int(1));
4031 let args = vec![
4032 ArgumentHandle::new(&arr, &ctx),
4033 ArgumentHandle::new(&one, &ctx),
4034 ];
4035 let v = f
4036 .dispatch(&args, &ctx.function_context(None))
4037 .unwrap()
4038 .into_literal();
4039 assert_eq!(v, LiteralValue::Number(2.0));
4040 }
4041
4042 #[test]
4043 fn xmatch_exact_match_default() {
4044 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4045 let wb = wb
4046 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("apple".into()))
4047 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("banana".into()))
4048 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("cherry".into()));
4049 let ctx = wb.interpreter();
4050 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4051 let f = ctx.context.get_function("", "XMATCH").unwrap();
4052 let key = lit(LiteralValue::Text("banana".into()));
4053 let args = vec![
4054 ArgumentHandle::new(&key, &ctx),
4055 ArgumentHandle::new(&lookup_range, &ctx),
4056 ];
4057 let v = f
4058 .dispatch(&args, &ctx.function_context(None))
4059 .unwrap()
4060 .into_literal();
4061 assert_eq!(v, LiteralValue::Int(2));
4062 }
4063
4064 #[test]
4065 fn xmatch_exact_or_next_smaller() {
4066 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4067 let wb = wb
4068 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
4069 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
4070 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30));
4071 let ctx = wb.interpreter();
4072 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4073 let f = ctx.context.get_function("", "XMATCH").unwrap();
4074 let needle = lit(LiteralValue::Int(25));
4075 let match_mode = lit(LiteralValue::Int(-1)); let args = vec![
4077 ArgumentHandle::new(&needle, &ctx),
4078 ArgumentHandle::new(&lookup_range, &ctx),
4079 ArgumentHandle::new(&match_mode, &ctx),
4080 ];
4081 let v = f
4082 .dispatch(&args, &ctx.function_context(None))
4083 .unwrap()
4084 .into_literal();
4085 assert_eq!(v, LiteralValue::Int(2)); }
4087
4088 #[test]
4089 fn xmatch_exact_or_next_larger() {
4090 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4091 let wb = wb
4092 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
4093 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
4094 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30));
4095 let ctx = wb.interpreter();
4096 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4097 let f = ctx.context.get_function("", "XMATCH").unwrap();
4098 let needle = lit(LiteralValue::Int(25));
4099 let match_mode = lit(LiteralValue::Int(1)); let args = vec![
4101 ArgumentHandle::new(&needle, &ctx),
4102 ArgumentHandle::new(&lookup_range, &ctx),
4103 ArgumentHandle::new(&match_mode, &ctx),
4104 ];
4105 let v = f
4106 .dispatch(&args, &ctx.function_context(None))
4107 .unwrap()
4108 .into_literal();
4109 assert_eq!(v, LiteralValue::Int(3)); }
4111
4112 #[test]
4113 fn xmatch_wildcard() {
4114 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4115 let wb = wb
4116 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("alpha".into()))
4117 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("beta".into()))
4118 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("gamma".into()));
4119 let ctx = wb.interpreter();
4120 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4121 let f = ctx.context.get_function("", "XMATCH").unwrap();
4122 let pattern = lit(LiteralValue::Text("*eta".into()));
4123 let match_mode = lit(LiteralValue::Int(2)); let args = vec![
4125 ArgumentHandle::new(&pattern, &ctx),
4126 ArgumentHandle::new(&lookup_range, &ctx),
4127 ArgumentHandle::new(&match_mode, &ctx),
4128 ];
4129 let v = f
4130 .dispatch(&args, &ctx.function_context(None))
4131 .unwrap()
4132 .into_literal();
4133 assert_eq!(v, LiteralValue::Int(2)); }
4135
4136 #[test]
4137 fn xmatch_reverse_search() {
4138 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4139 let wb = wb
4140 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4141 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
4142 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(1)); let ctx = wb.interpreter();
4144 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4145 let f = ctx.context.get_function("", "XMATCH").unwrap();
4146 let needle = lit(LiteralValue::Int(1));
4147 let match_mode = lit(LiteralValue::Int(0));
4148 let search_mode = lit(LiteralValue::Int(-1)); let args = vec![
4150 ArgumentHandle::new(&needle, &ctx),
4151 ArgumentHandle::new(&lookup_range, &ctx),
4152 ArgumentHandle::new(&match_mode, &ctx),
4153 ArgumentHandle::new(&search_mode, &ctx),
4154 ];
4155 let v = f
4156 .dispatch(&args, &ctx.function_context(None))
4157 .unwrap()
4158 .into_literal();
4159 assert_eq!(v, LiteralValue::Int(3)); }
4161
4162 #[test]
4163 fn xmatch_not_found() {
4164 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4165 let wb = wb
4166 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4167 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
4168 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(3));
4169 let ctx = wb.interpreter();
4170 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4171 let f = ctx.context.get_function("", "XMATCH").unwrap();
4172 let needle = lit(LiteralValue::Int(5));
4173 let args = vec![
4174 ArgumentHandle::new(&needle, &ctx),
4175 ArgumentHandle::new(&lookup_range, &ctx),
4176 ];
4177 let v = f
4178 .dispatch(&args, &ctx.function_context(None))
4179 .unwrap()
4180 .into_literal();
4181 match v {
4182 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Na),
4183 other => panic!("expected #N/A got {other:?}"),
4184 }
4185 }
4186
4187 #[test]
4188 fn sort_basic_ascending() {
4189 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4190 let wb = wb
4191 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(30))
4192 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
4193 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20));
4194 let ctx = wb.interpreter();
4195 let arr = range("A1:A3", 1, 1, 3, 1);
4196 let f = ctx.context.get_function("", "SORT").unwrap();
4197 let args = vec![ArgumentHandle::new(&arr, &ctx)];
4198 let v = f
4199 .dispatch(&args, &ctx.function_context(None))
4200 .unwrap()
4201 .into_literal();
4202 match v {
4203 LiteralValue::Array(a) => {
4204 assert_eq!(a.len(), 3);
4205 assert_eq!(a[0][0], LiteralValue::Number(10.0));
4206 assert_eq!(a[1][0], LiteralValue::Number(20.0));
4207 assert_eq!(a[2][0], LiteralValue::Number(30.0));
4208 }
4209 other => panic!("expected array got {other:?}"),
4210 }
4211 }
4212
4213 #[test]
4214 fn sort_descending() {
4215 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4216 let wb = wb
4217 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(30))
4218 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
4219 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20));
4220 let ctx = wb.interpreter();
4221 let arr = range("A1:A3", 1, 1, 3, 1);
4222 let f = ctx.context.get_function("", "SORT").unwrap();
4223 let sort_index = lit(LiteralValue::Int(1));
4224 let sort_order = lit(LiteralValue::Int(-1)); let args = vec![
4226 ArgumentHandle::new(&arr, &ctx),
4227 ArgumentHandle::new(&sort_index, &ctx),
4228 ArgumentHandle::new(&sort_order, &ctx),
4229 ];
4230 let v = f
4231 .dispatch(&args, &ctx.function_context(None))
4232 .unwrap()
4233 .into_literal();
4234 match v {
4235 LiteralValue::Array(a) => {
4236 assert_eq!(a.len(), 3);
4237 assert_eq!(a[0][0], LiteralValue::Number(30.0));
4238 assert_eq!(a[1][0], LiteralValue::Number(20.0));
4239 assert_eq!(a[2][0], LiteralValue::Number(10.0));
4240 }
4241 other => panic!("expected array got {other:?}"),
4242 }
4243 }
4244
4245 #[test]
4246 fn sort_by_column() {
4247 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4248 let wb = wb
4249 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4250 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(30))
4251 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4252 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(10))
4253 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4254 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(20));
4255 let ctx = wb.interpreter();
4256 let arr = range("A1:B3", 1, 1, 3, 2);
4257 let f = ctx.context.get_function("", "SORT").unwrap();
4258 let sort_index = lit(LiteralValue::Int(2)); let args = vec![
4260 ArgumentHandle::new(&arr, &ctx),
4261 ArgumentHandle::new(&sort_index, &ctx),
4262 ];
4263 let v = f
4264 .dispatch(&args, &ctx.function_context(None))
4265 .unwrap()
4266 .into_literal();
4267 match v {
4268 LiteralValue::Array(a) => {
4269 assert_eq!(a.len(), 3);
4270 assert_eq!(a[0][0], LiteralValue::Text("Alice".into()));
4272 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4273 assert_eq!(a[2][0], LiteralValue::Text("Charlie".into()));
4274 }
4275 other => panic!("expected array got {other:?}"),
4276 }
4277 }
4278
4279 #[test]
4280 fn sortby_basic() {
4281 let wb = TestWorkbook::new().with_function(Arc::new(SortByFn));
4282 let wb = wb
4283 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4284 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4285 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4286 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(3))
4287 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(1))
4288 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(2));
4289 let ctx = wb.interpreter();
4290 let arr = range("A1:A3", 1, 1, 3, 1);
4291 let by_arr = range("B1:B3", 1, 2, 3, 2);
4292 let f = ctx.context.get_function("", "SORTBY").unwrap();
4293 let args = vec![
4294 ArgumentHandle::new(&arr, &ctx),
4295 ArgumentHandle::new(&by_arr, &ctx),
4296 ];
4297 let v = f
4298 .dispatch(&args, &ctx.function_context(None))
4299 .unwrap()
4300 .into_literal();
4301 match v {
4302 LiteralValue::Array(a) => {
4303 assert_eq!(a.len(), 3);
4304 assert_eq!(a[0][0], LiteralValue::Text("Alice".into()));
4306 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4307 assert_eq!(a[2][0], LiteralValue::Text("Charlie".into()));
4308 }
4309 other => panic!("expected array got {other:?}"),
4310 }
4311 }
4312
4313 #[test]
4314 fn sortby_descending() {
4315 let wb = TestWorkbook::new().with_function(Arc::new(SortByFn));
4316 let wb = wb
4317 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4318 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4319 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4320 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(3))
4321 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(1))
4322 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(2));
4323 let ctx = wb.interpreter();
4324 let arr = range("A1:A3", 1, 1, 3, 1);
4325 let by_arr = range("B1:B3", 1, 2, 3, 2);
4326 let sort_order = lit(LiteralValue::Int(-1)); let f = ctx.context.get_function("", "SORTBY").unwrap();
4328 let args = vec![
4329 ArgumentHandle::new(&arr, &ctx),
4330 ArgumentHandle::new(&by_arr, &ctx),
4331 ArgumentHandle::new(&sort_order, &ctx),
4332 ];
4333 let v = f
4334 .dispatch(&args, &ctx.function_context(None))
4335 .unwrap()
4336 .into_literal();
4337 match v {
4338 LiteralValue::Array(a) => {
4339 assert_eq!(a.len(), 3);
4340 assert_eq!(a[0][0], LiteralValue::Text("Charlie".into()));
4342 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4343 assert_eq!(a[2][0], LiteralValue::Text("Alice".into()));
4344 }
4345 other => panic!("expected array got {other:?}"),
4346 }
4347 }
4348
4349 #[test]
4350 fn randarray_basic() {
4351 let wb = TestWorkbook::new().with_function(Arc::new(RandArrayFn));
4352 let ctx = wb.interpreter();
4353 let f = ctx.context.get_function("", "RANDARRAY").unwrap();
4354
4355 let rows = lit(LiteralValue::Int(2));
4357 let cols = lit(LiteralValue::Int(3));
4358 let args = vec![
4359 ArgumentHandle::new(&rows, &ctx),
4360 ArgumentHandle::new(&cols, &ctx),
4361 ];
4362 let v = f
4363 .dispatch(&args, &ctx.function_context(None))
4364 .unwrap()
4365 .into_literal();
4366 match v {
4367 LiteralValue::Array(a) => {
4368 assert_eq!(a.len(), 2);
4369 assert_eq!(a[0].len(), 3);
4370 for row in &a {
4372 for cell in row {
4373 match cell {
4374 LiteralValue::Number(n) => {
4375 assert!(*n >= 0.0 && *n < 1.0, "Value {n} not in [0, 1)");
4376 }
4377 other => panic!("expected Number got {other:?}"),
4378 }
4379 }
4380 }
4381 }
4382 other => panic!("expected array got {other:?}"),
4383 }
4384 }
4385
4386 #[test]
4387 fn randarray_whole_numbers() {
4388 let wb = TestWorkbook::new().with_function(Arc::new(RandArrayFn));
4389 let ctx = wb.interpreter();
4390 let f = ctx.context.get_function("", "RANDARRAY").unwrap();
4391
4392 let rows = lit(LiteralValue::Int(3));
4394 let cols = lit(LiteralValue::Int(2));
4395 let min = lit(LiteralValue::Int(1));
4396 let max = lit(LiteralValue::Int(10));
4397 let whole = lit(LiteralValue::Boolean(true));
4398 let args = vec![
4399 ArgumentHandle::new(&rows, &ctx),
4400 ArgumentHandle::new(&cols, &ctx),
4401 ArgumentHandle::new(&min, &ctx),
4402 ArgumentHandle::new(&max, &ctx),
4403 ArgumentHandle::new(&whole, &ctx),
4404 ];
4405 let v = f
4406 .dispatch(&args, &ctx.function_context(None))
4407 .unwrap()
4408 .into_literal();
4409 match v {
4410 LiteralValue::Array(a) => {
4411 assert_eq!(a.len(), 3);
4412 assert_eq!(a[0].len(), 2);
4413 for row in &a {
4415 for cell in row {
4416 let n = match cell {
4417 LiteralValue::Int(n) => *n as f64,
4418 LiteralValue::Number(n) => *n,
4419 other => panic!("expected Int or Number got {other:?}"),
4420 };
4421 assert!((1.0..=10.0).contains(&n), "Value {n} not in [1, 10]");
4422 assert!(n.fract() == 0.0, "Value {n} is not a whole number");
4424 }
4425 }
4426 }
4427 other => panic!("expected array got {other:?}"),
4428 }
4429 }
4430}