1use super::super::utils::collapse_if_scalar;
23use super::lookup_utils::{PreparedLookupMatcher, cmp_for_lookup, value_to_f64_lenient};
24use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
25use crate::engine::lookup_index_cache::LookupAxis;
26use crate::function::Function; use crate::traits::{ArgumentHandle, FunctionContext};
28use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
29use formualizer_macros::func_caps;
30use std::collections::HashMap;
31
32const GENERATED_ARRAY_MAX_ROWS: i64 = 1_048_576;
52const GENERATED_ARRAY_MAX_COLS: i64 = 16_384;
53const GENERATED_ARRAY_MAX_CELLS: i64 = 1 << 24;
54
55fn generated_array_too_large(rows: i64, cols: i64) -> Option<ExcelError> {
59 if rows > GENERATED_ARRAY_MAX_ROWS || cols > GENERATED_ARRAY_MAX_COLS {
60 return Some(ExcelError::new(ExcelErrorKind::Num));
61 }
62 match rows.checked_mul(cols) {
63 Some(total) if total <= GENERATED_ARRAY_MAX_CELLS => None,
64 _ => Some(ExcelError::new(ExcelErrorKind::Num)),
65 }
66}
67
68pub fn super_wildcard_match(pattern: &str, text: &str) -> bool {
71 super::lookup_utils::wildcard_pattern_match(pattern, text)
72}
73
74#[derive(Debug)]
77pub struct XLookupFn;
78
79impl Function for XLookupFn {
142 func_caps!(PURE, LOOKUP);
143 fn name(&self) -> &'static str {
144 "XLOOKUP"
145 }
146 fn min_args(&self) -> usize {
147 3
148 }
149 fn variadic(&self) -> bool {
150 true
151 }
152 fn arg_schema(&self) -> &'static [ArgSchema] {
153 use once_cell::sync::Lazy;
154 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
155 vec![
156 ArgSchema {
158 kinds: smallvec::smallvec![ArgKind::Any],
159 required: true,
160 by_ref: false,
161 shape: ShapeKind::Scalar,
162 coercion: CoercionPolicy::None,
163 max: None,
164 repeating: None,
165 default: None,
166 },
167 ArgSchema {
169 kinds: smallvec::smallvec![ArgKind::Range],
170 required: true,
171 by_ref: true,
172 shape: ShapeKind::Range,
173 coercion: CoercionPolicy::None,
174 max: None,
175 repeating: None,
176 default: None,
177 },
178 ArgSchema {
180 kinds: smallvec::smallvec![ArgKind::Range],
181 required: true,
182 by_ref: true,
183 shape: ShapeKind::Range,
184 coercion: CoercionPolicy::None,
185 max: None,
186 repeating: None,
187 default: None,
188 },
189 ArgSchema {
191 kinds: smallvec::smallvec![ArgKind::Any],
192 required: false,
193 by_ref: false,
194 shape: ShapeKind::Scalar,
195 coercion: CoercionPolicy::None,
196 max: None,
197 repeating: None,
198 default: None,
199 },
200 ArgSchema {
202 kinds: smallvec::smallvec![ArgKind::Number],
203 required: false,
204 by_ref: false,
205 shape: ShapeKind::Scalar,
206 coercion: CoercionPolicy::NumberLenientText,
207 max: None,
208 repeating: None,
209 default: Some(LiteralValue::Int(0)),
210 },
211 ArgSchema {
213 kinds: smallvec::smallvec![ArgKind::Number],
214 required: false,
215 by_ref: false,
216 shape: ShapeKind::Scalar,
217 coercion: CoercionPolicy::NumberLenientText,
218 max: None,
219 repeating: None,
220 default: Some(LiteralValue::Int(1)),
221 },
222 ]
223 });
224 &SCHEMA
225 }
226 fn eval<'a, 'b, 'c>(
227 &self,
228 args: &'c [ArgumentHandle<'a, 'b>],
229 _ctx: &dyn FunctionContext<'b>,
230 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
231 if args.len() < 3 {
232 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
233 ExcelError::new(ExcelErrorKind::Value),
234 )));
235 }
236 let lookup_value = args[0].value()?.into_literal();
237 if let LiteralValue::Error(ref e) = lookup_value {
238 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
239 e.clone(),
240 )));
241 }
242 let lookup_view = match args[1].range_view() {
243 Ok(v) => v,
244 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
245 };
246 let ret_view = match args[2].range_view() {
247 Ok(v) => v,
248 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
249 };
250
251 let (lookup_rows, lookup_cols) = lookup_view.dims();
252 let (ret_rows, ret_cols) = ret_view.dims();
253
254 let vertical = if lookup_cols == 1 {
259 true
260 } else if lookup_rows == 1 {
261 false
262 } else if lookup_rows == 0 && lookup_cols == 0 {
263 if ret_cols == 1 {
264 true
265 } else if ret_rows == 1 {
266 false
267 } else {
268 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
269 ExcelError::new(ExcelErrorKind::Value),
270 )));
271 }
272 } else {
273 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
274 ExcelError::new(ExcelErrorKind::Value),
275 )));
276 };
277
278 let lookup_len = {
279 let raw = if vertical { lookup_rows } else { lookup_cols };
280 if raw == 0 {
281 if vertical { ret_rows } else { ret_cols }
282 } else {
283 raw
284 }
285 };
286
287 if lookup_len == 0 {
288 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
289 ExcelError::new(ExcelErrorKind::Na),
290 )));
291 }
292
293 let match_mode = if args.len() >= 5 {
294 match args[4].value()?.into_literal() {
295 LiteralValue::Int(i) => i,
296 LiteralValue::Number(n) => n as i64,
297 _ => 0,
298 }
299 } else {
300 0
301 };
302 let search_mode = if args.len() >= 6 {
303 match args[5].value()?.into_literal() {
304 LiteralValue::Int(i) => i,
305 LiteralValue::Number(n) => n as i64,
306 _ => 1,
307 }
308 } else {
309 1
310 };
311
312 let wildcard = match_mode == 2;
313
314 let mut found: Option<usize> = None;
315 let needle = lookup_value;
316 let prepared_matcher = PreparedLookupMatcher::new(&needle, wildcard);
317 if match_mode == 0 || wildcard {
318 if match_mode == 0 && search_mode == 1 && lookup_rows > 0 && lookup_cols > 0 {
319 let axis = if vertical {
320 LookupAxis::ColumnInView(0)
321 } else {
322 LookupAxis::RowInView(0)
323 };
324 if let Some(index) = _ctx.get_lookup_index(&lookup_view, axis) {
325 found = index.find_first_exact(&needle);
326 } else {
327 found = super::lookup_utils::find_exact_index_in_view(
328 &lookup_view,
329 &needle,
330 false,
331 )?;
332 }
333 } else if search_mode == 1 && lookup_rows > 0 && lookup_cols > 0 {
334 found =
335 super::lookup_utils::find_exact_index_in_view(&lookup_view, &needle, wildcard)?;
336 } else if search_mode == -1 {
337 for i in (0..lookup_len).rev() {
338 let cand = if vertical {
339 lookup_view.get_cell(i, 0)
340 } else {
341 lookup_view.get_cell(0, i)
342 };
343 if prepared_matcher.matches(&cand) {
344 found = Some(i);
345 break;
346 }
347 }
348 } else {
349 for i in 0..lookup_len {
352 let cand = if vertical {
353 lookup_view.get_cell(i, 0)
354 } else {
355 lookup_view.get_cell(0, i)
356 };
357 if prepared_matcher.matches(&cand) {
358 found = Some(i);
359 break;
360 }
361 }
362 }
363 } else if match_mode == -1 || match_mode == 1 {
364 let needle_num = value_to_f64_lenient(&needle);
365 let mut best_idx: Option<usize> = None;
366 let mut best_val: f64 = if match_mode == -1 {
367 f64::NEG_INFINITY
368 } else {
369 f64::INFINITY
370 };
371
372 let mut prev: Option<LiteralValue> = None;
373 for i in 0..lookup_len {
374 let cand = if vertical {
375 lookup_view.get_cell(i, 0)
376 } else {
377 lookup_view.get_cell(0, i)
378 };
379
380 if let Some(p) = prev.as_ref() {
381 let sorted_ok = cmp_for_lookup(p, &cand).is_some_and(|o| o <= 0);
382 if !sorted_ok {
383 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
384 ExcelError::new(ExcelErrorKind::Na),
385 )));
386 }
387 }
388 prev = Some(cand.clone());
389
390 if cmp_for_lookup(&cand, &needle).is_some_and(|o| o == 0) {
391 found = Some(i);
392 break;
393 }
394
395 if let (Some(nn), Some(vv)) = (needle_num, value_to_f64_lenient(&cand)) {
396 if match_mode == -1 {
397 if vv <= nn && vv > best_val {
398 best_val = vv;
399 best_idx = Some(i);
400 }
401 } else if vv >= nn && vv < best_val {
402 best_val = vv;
403 best_idx = Some(i);
404 }
405 }
406 }
407
408 if found.is_none() {
409 found = best_idx;
410 }
411 } else {
412 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
413 ExcelError::new(ExcelErrorKind::Value),
414 )));
415 }
416
417 if let Some(idx) = found {
418 let (ret_rows, ret_cols) = ret_view.dims();
419 if ret_rows == 0 || ret_cols == 0 {
420 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Empty));
421 }
422
423 if vertical {
424 if ret_cols == 1 {
425 return Ok(crate::traits::CalcValue::Scalar(ret_view.get_cell(idx, 0)));
426 }
427 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(ret_cols);
428 for c in 0..ret_cols {
429 row_out.push(ret_view.get_cell(idx, c));
430 }
431 return Ok(crate::traits::CalcValue::Range(
432 crate::engine::range_view::RangeView::from_owned_rows(
433 vec![row_out],
434 _ctx.date_system(),
435 ),
436 ));
437 }
438
439 if ret_rows == 1 {
441 return Ok(crate::traits::CalcValue::Scalar(ret_view.get_cell(0, idx)));
442 }
443
444 let mut col_out: Vec<Vec<LiteralValue>> = Vec::with_capacity(ret_rows);
445 for r in 0..ret_rows {
446 col_out.push(vec![ret_view.get_cell(r, idx)]);
447 }
448 return Ok(crate::traits::CalcValue::Range(
449 crate::engine::range_view::RangeView::from_owned_rows(col_out, _ctx.date_system()),
450 ));
451 }
452
453 if args.len() >= 4 {
454 return args[3].value();
455 }
456 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
457 ExcelError::new(ExcelErrorKind::Na),
458 )))
459 }
460}
461
462#[derive(Debug)]
465pub struct XMatchFn;
466impl Function for XMatchFn {
520 func_caps!(PURE, LOOKUP);
521 fn name(&self) -> &'static str {
522 "XMATCH"
523 }
524 fn min_args(&self) -> usize {
525 2
526 }
527 fn variadic(&self) -> bool {
528 true
529 }
530 fn arg_schema(&self) -> &'static [ArgSchema] {
531 use once_cell::sync::Lazy;
532 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
533 vec![
534 ArgSchema {
536 kinds: smallvec::smallvec![ArgKind::Any],
537 required: true,
538 by_ref: false,
539 shape: ShapeKind::Scalar,
540 coercion: CoercionPolicy::None,
541 max: None,
542 repeating: None,
543 default: None,
544 },
545 ArgSchema {
547 kinds: smallvec::smallvec![ArgKind::Range],
548 required: true,
549 by_ref: true,
550 shape: ShapeKind::Range,
551 coercion: CoercionPolicy::None,
552 max: None,
553 repeating: None,
554 default: None,
555 },
556 ArgSchema {
559 kinds: smallvec::smallvec![ArgKind::Number],
560 required: false,
561 by_ref: false,
562 shape: ShapeKind::Scalar,
563 coercion: CoercionPolicy::NumberLenientText,
564 max: None,
565 repeating: None,
566 default: Some(LiteralValue::Int(0)),
567 },
568 ArgSchema {
571 kinds: smallvec::smallvec![ArgKind::Number],
572 required: false,
573 by_ref: false,
574 shape: ShapeKind::Scalar,
575 coercion: CoercionPolicy::NumberLenientText,
576 max: None,
577 repeating: None,
578 default: Some(LiteralValue::Int(1)),
579 },
580 ]
581 });
582 &SCHEMA
583 }
584 fn eval<'a, 'b, 'c>(
585 &self,
586 args: &'c [ArgumentHandle<'a, 'b>],
587 _ctx: &dyn FunctionContext<'b>,
588 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
589 if args.len() < 2 {
590 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
591 ExcelError::new(ExcelErrorKind::Value),
592 )));
593 }
594 let lookup_value = args[0].value()?.into_literal();
595 if let LiteralValue::Error(ref e) = lookup_value {
596 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
597 e.clone(),
598 )));
599 }
600 let lookup_view = match args[1].range_view() {
601 Ok(v) => v,
602 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
603 };
604
605 let (lookup_rows, lookup_cols) = lookup_view.dims();
606
607 let vertical = if lookup_cols == 1 {
609 true
610 } else if lookup_rows == 1 {
611 false
612 } else if lookup_rows == 0 || lookup_cols == 0 {
613 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
614 ExcelError::new(ExcelErrorKind::Na),
615 )));
616 } else {
617 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
618 ExcelError::new(ExcelErrorKind::Value),
619 )));
620 };
621
622 let lookup_len = if vertical { lookup_rows } else { lookup_cols };
623
624 if lookup_len == 0 {
625 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
626 ExcelError::new(ExcelErrorKind::Na),
627 )));
628 }
629
630 let match_mode = if args.len() >= 3 {
631 match args[2].value()?.into_literal() {
632 LiteralValue::Int(i) => i,
633 LiteralValue::Number(n) => n as i64,
634 _ => 0,
635 }
636 } else {
637 0
638 };
639 let search_mode = if args.len() >= 4 {
640 match args[3].value()?.into_literal() {
641 LiteralValue::Int(i) => i,
642 LiteralValue::Number(n) => n as i64,
643 _ => 1,
644 }
645 } else {
646 1
647 };
648
649 let wildcard = match_mode == 2;
650 let needle = lookup_value;
651 let prepared_matcher = PreparedLookupMatcher::new(&needle, wildcard);
652
653 let mut found: Option<usize> = None;
654
655 if match_mode == 0 || wildcard {
656 if search_mode == 1 || search_mode == 2 {
658 if lookup_rows > 0 && lookup_cols > 0 {
660 found = super::lookup_utils::find_exact_index_in_view(
661 &lookup_view,
662 &needle,
663 wildcard,
664 )?;
665 }
666 } else if search_mode == -1 || search_mode == -2 {
667 for i in (0..lookup_len).rev() {
669 let cand = if vertical {
670 lookup_view.get_cell(i, 0)
671 } else {
672 lookup_view.get_cell(0, i)
673 };
674 if prepared_matcher.matches(&cand) {
675 found = Some(i);
676 break;
677 }
678 }
679 } else {
680 for i in 0..lookup_len {
682 let cand = if vertical {
683 lookup_view.get_cell(i, 0)
684 } else {
685 lookup_view.get_cell(0, i)
686 };
687 if prepared_matcher.matches(&cand) {
688 found = Some(i);
689 break;
690 }
691 }
692 }
693 } else if match_mode == -1 || match_mode == 1 {
694 let needle_num = value_to_f64_lenient(&needle);
696 let mut best_idx: Option<usize> = None;
697 let mut best_val: f64 = if match_mode == -1 {
698 f64::NEG_INFINITY
699 } else {
700 f64::INFINITY
701 };
702
703 let use_reverse = search_mode == -1 || search_mode == -2;
705 let indices: Box<dyn Iterator<Item = usize>> = if use_reverse {
706 Box::new((0..lookup_len).rev())
707 } else {
708 Box::new(0..lookup_len)
709 };
710
711 if (search_mode == 2 || search_mode == -2) && match_mode != 0 {
714 let ascending = search_mode == 2;
715 let mut prev: Option<LiteralValue> = None;
716 for i in 0..lookup_len {
717 let cand = if vertical {
718 lookup_view.get_cell(i, 0)
719 } else {
720 lookup_view.get_cell(0, i)
721 };
722 if let Some(p) = prev.as_ref() {
723 let sorted_ok = if ascending {
724 cmp_for_lookup(p, &cand).is_some_and(|o| o <= 0)
725 } else {
726 cmp_for_lookup(p, &cand).is_some_and(|o| o >= 0)
727 };
728 if !sorted_ok {
729 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
730 ExcelError::new(ExcelErrorKind::Na),
731 )));
732 }
733 }
734 prev = Some(cand);
735 }
736 }
737
738 for i in indices {
739 let cand = if vertical {
740 lookup_view.get_cell(i, 0)
741 } else {
742 lookup_view.get_cell(0, i)
743 };
744
745 if cmp_for_lookup(&cand, &needle).is_some_and(|o| o == 0) {
746 found = Some(i);
747 break;
748 }
749
750 if let (Some(nn), Some(vv)) = (needle_num, value_to_f64_lenient(&cand)) {
751 if match_mode == -1 {
752 if vv <= nn && vv > best_val {
754 best_val = vv;
755 best_idx = Some(i);
756 }
757 } else {
758 if vv >= nn && vv < best_val {
760 best_val = vv;
761 best_idx = Some(i);
762 }
763 }
764 }
765 }
766
767 if found.is_none() {
768 found = best_idx;
769 }
770 } else {
771 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
772 ExcelError::new(ExcelErrorKind::Value),
773 )));
774 }
775
776 match found {
777 Some(idx) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
778 (idx + 1) as i64,
779 ))),
780 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
781 ExcelError::new(ExcelErrorKind::Na),
782 ))),
783 }
784 }
785}
786
787#[derive(Debug)]
790pub struct SortFn;
791impl Function for SortFn {
851 func_caps!(PURE);
852 fn name(&self) -> &'static str {
853 "SORT"
854 }
855 fn min_args(&self) -> usize {
856 1
857 }
858 fn variadic(&self) -> bool {
859 true
860 }
861 fn arg_schema(&self) -> &'static [ArgSchema] {
862 use once_cell::sync::Lazy;
863 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
864 vec![
865 ArgSchema {
867 kinds: smallvec::smallvec![ArgKind::Range],
868 required: true,
869 by_ref: true,
870 shape: ShapeKind::Range,
871 coercion: CoercionPolicy::None,
872 max: None,
873 repeating: None,
874 default: None,
875 },
876 ArgSchema {
878 kinds: smallvec::smallvec![ArgKind::Number],
879 required: false,
880 by_ref: false,
881 shape: ShapeKind::Scalar,
882 coercion: CoercionPolicy::NumberLenientText,
883 max: None,
884 repeating: None,
885 default: Some(LiteralValue::Int(1)),
886 },
887 ArgSchema {
889 kinds: smallvec::smallvec![ArgKind::Number],
890 required: false,
891 by_ref: false,
892 shape: ShapeKind::Scalar,
893 coercion: CoercionPolicy::NumberLenientText,
894 max: None,
895 repeating: None,
896 default: Some(LiteralValue::Int(1)),
897 },
898 ArgSchema {
900 kinds: smallvec::smallvec![ArgKind::Logical],
901 required: false,
902 by_ref: false,
903 shape: ShapeKind::Scalar,
904 coercion: CoercionPolicy::Logical,
905 max: None,
906 repeating: None,
907 default: Some(LiteralValue::Boolean(false)),
908 },
909 ]
910 });
911 &SCHEMA
912 }
913 fn eval<'a, 'b, 'c>(
914 &self,
915 args: &'c [ArgumentHandle<'a, 'b>],
916 _ctx: &dyn FunctionContext<'b>,
917 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
918 let view = match args[0].range_view() {
919 Ok(v) => v,
920 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
921 };
922 let (rows, cols) = view.dims();
923 if rows == 0 || cols == 0 {
924 return Ok(crate::traits::CalcValue::Range(
925 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
926 ));
927 }
928
929 let sort_index = if args.len() >= 2 {
930 match args[1].value()?.into_literal() {
931 LiteralValue::Int(i) => i,
932 LiteralValue::Number(n) => n as i64,
933 _ => 1,
934 }
935 } else {
936 1
937 };
938
939 let sort_order = if args.len() >= 3 {
940 match args[2].value()?.into_literal() {
941 LiteralValue::Int(i) => i,
942 LiteralValue::Number(n) => n as i64,
943 _ => 1,
944 }
945 } else {
946 1
947 };
948
949 let by_col = if args.len() >= 4 {
950 matches!(args[3].value()?.into_literal(), LiteralValue::Boolean(true))
951 } else {
952 false
953 };
954
955 let ascending = sort_order >= 0;
956
957 if by_col {
958 let sort_row_idx = (sort_index - 1).max(0) as usize;
960 if sort_row_idx >= rows {
961 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
962 ExcelError::new(ExcelErrorKind::Value),
963 )));
964 }
965
966 let mut columns: Vec<(usize, Vec<LiteralValue>)> = Vec::with_capacity(cols);
968 for c in 0..cols {
969 let mut col_vals: Vec<LiteralValue> = Vec::with_capacity(rows);
970 for r in 0..rows {
971 col_vals.push(view.get_cell(r, c));
972 }
973 columns.push((c, col_vals));
974 }
975
976 columns.sort_by(|a, b| {
978 let val_a = &a.1[sort_row_idx];
979 let val_b = &b.1[sort_row_idx];
980 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
981 if ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) }
982 });
983
984 let mut out: Vec<Vec<LiteralValue>> = vec![Vec::with_capacity(cols); rows];
986 for (_orig_idx, col_vals) in columns {
987 for (r, val) in col_vals.into_iter().enumerate() {
988 out[r].push(val);
989 }
990 }
991
992 Ok(collapse_if_scalar(out, _ctx.date_system()))
993 } else {
994 let sort_col_idx = (sort_index - 1).max(0) as usize;
996 if sort_col_idx >= cols {
997 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
998 ExcelError::new(ExcelErrorKind::Value),
999 )));
1000 }
1001
1002 let mut row_data: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
1004 for r in 0..rows {
1005 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
1006 for c in 0..cols {
1007 row_vals.push(view.get_cell(r, c));
1008 }
1009 row_data.push(row_vals);
1010 }
1011
1012 row_data.sort_by(|a, b| {
1014 let val_a = &a[sort_col_idx];
1015 let val_b = &b[sort_col_idx];
1016 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
1017 if ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) }
1018 });
1019
1020 Ok(collapse_if_scalar(row_data, _ctx.date_system()))
1021 }
1022 }
1023}
1024
1025#[derive(Debug)]
1028pub struct SortByFn;
1029impl Function for SortByFn {
1089 func_caps!(PURE);
1090 fn name(&self) -> &'static str {
1091 "SORTBY"
1092 }
1093 fn min_args(&self) -> usize {
1094 2
1095 }
1096 fn variadic(&self) -> bool {
1097 true
1098 }
1099 fn arg_schema(&self) -> &'static [ArgSchema] {
1100 use once_cell::sync::Lazy;
1101 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1102 vec![
1103 ArgSchema {
1105 kinds: smallvec::smallvec![ArgKind::Range],
1106 required: true,
1107 by_ref: true,
1108 shape: ShapeKind::Range,
1109 coercion: CoercionPolicy::None,
1110 max: None,
1111 repeating: None,
1112 default: None,
1113 },
1114 ArgSchema {
1116 kinds: smallvec::smallvec![ArgKind::Range],
1117 required: true,
1118 by_ref: true,
1119 shape: ShapeKind::Range,
1120 coercion: CoercionPolicy::None,
1121 max: None,
1122 repeating: None,
1123 default: None,
1124 },
1125 ArgSchema {
1127 kinds: smallvec::smallvec![ArgKind::Number],
1128 required: false,
1129 by_ref: false,
1130 shape: ShapeKind::Scalar,
1131 coercion: CoercionPolicy::NumberLenientText,
1132 max: None,
1133 repeating: None,
1134 default: Some(LiteralValue::Int(1)),
1135 },
1136 ]
1138 });
1139 &SCHEMA
1140 }
1141 fn eval<'a, 'b, 'c>(
1142 &self,
1143 args: &'c [ArgumentHandle<'a, 'b>],
1144 _ctx: &dyn FunctionContext<'b>,
1145 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1146 if args.len() < 2 {
1147 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1148 ExcelError::new(ExcelErrorKind::Value),
1149 )));
1150 }
1151
1152 let view = match args[0].range_view() {
1153 Ok(v) => v,
1154 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1155 };
1156 let (rows, cols) = view.dims();
1157 if rows == 0 || cols == 0 {
1158 return Ok(crate::traits::CalcValue::Range(
1159 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
1160 ));
1161 }
1162
1163 let mut sort_criteria: Vec<(Vec<LiteralValue>, bool)> = Vec::new();
1166 let mut arg_idx = 1;
1167
1168 while arg_idx < args.len() {
1169 let by_view = match args[arg_idx].range_view() {
1171 Ok(v) => v,
1172 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1173 };
1174 let (by_rows, by_cols) = by_view.dims();
1175
1176 let by_values: Vec<LiteralValue> = if by_cols == 1 {
1178 if by_rows != rows {
1179 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1180 ExcelError::new(ExcelErrorKind::Value),
1181 )));
1182 }
1183 (0..by_rows).map(|r| by_view.get_cell(r, 0)).collect()
1184 } else if by_rows == 1 {
1185 if by_cols != rows {
1186 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1187 ExcelError::new(ExcelErrorKind::Value),
1188 )));
1189 }
1190 (0..by_cols).map(|c| by_view.get_cell(0, c)).collect()
1191 } else {
1192 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1193 ExcelError::new(ExcelErrorKind::Value),
1194 )));
1195 };
1196
1197 arg_idx += 1;
1198
1199 let ascending = if arg_idx < args.len() {
1201 match args[arg_idx].value() {
1204 Ok(v) => {
1205 let lit = v.into_literal();
1206 match lit {
1207 LiteralValue::Int(i) => {
1208 arg_idx += 1;
1209 i >= 0
1210 }
1211 LiteralValue::Number(n) => {
1212 arg_idx += 1;
1213 n >= 0.0
1214 }
1215 _ => true, }
1217 }
1218 Err(_) => true,
1219 }
1220 } else {
1221 true
1222 };
1223
1224 sort_criteria.push((by_values, ascending));
1225 }
1226
1227 if sort_criteria.is_empty() {
1228 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1229 ExcelError::new(ExcelErrorKind::Value),
1230 )));
1231 }
1232
1233 let mut indexed_rows: Vec<(usize, Vec<LiteralValue>)> = Vec::with_capacity(rows);
1235 for r in 0..rows {
1236 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
1237 for c in 0..cols {
1238 row_vals.push(view.get_cell(r, c));
1239 }
1240 indexed_rows.push((r, row_vals));
1241 }
1242
1243 indexed_rows.sort_by(|a, b| {
1245 for (by_values, ascending) in &sort_criteria {
1246 let val_a = &by_values[a.0];
1247 let val_b = &by_values[b.0];
1248 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
1249 if cmp != 0 {
1250 return if *ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) };
1251 }
1252 }
1253 std::cmp::Ordering::Equal
1254 });
1255
1256 let out: Vec<Vec<LiteralValue>> = indexed_rows.into_iter().map(|(_, row)| row).collect();
1258
1259 Ok(collapse_if_scalar(out, _ctx.date_system()))
1260 }
1261}
1262
1263#[derive(Debug)]
1266pub struct RandArrayFn;
1267impl Function for RandArrayFn {
1312 fn caps(&self) -> crate::function::FnCaps {
1314 crate::function::FnCaps::empty()
1315 }
1316 fn name(&self) -> &'static str {
1317 "RANDARRAY"
1318 }
1319 fn min_args(&self) -> usize {
1320 0
1321 }
1322 fn variadic(&self) -> bool {
1323 true
1324 }
1325 fn arg_schema(&self) -> &'static [ArgSchema] {
1326 use once_cell::sync::Lazy;
1327 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1328 vec![
1329 ArgSchema {
1331 kinds: smallvec::smallvec![ArgKind::Number],
1332 required: false,
1333 by_ref: false,
1334 shape: ShapeKind::Scalar,
1335 coercion: CoercionPolicy::NumberLenientText,
1336 max: None,
1337 repeating: None,
1338 default: Some(LiteralValue::Int(1)),
1339 },
1340 ArgSchema {
1342 kinds: smallvec::smallvec![ArgKind::Number],
1343 required: false,
1344 by_ref: false,
1345 shape: ShapeKind::Scalar,
1346 coercion: CoercionPolicy::NumberLenientText,
1347 max: None,
1348 repeating: None,
1349 default: Some(LiteralValue::Int(1)),
1350 },
1351 ArgSchema {
1353 kinds: smallvec::smallvec![ArgKind::Number],
1354 required: false,
1355 by_ref: false,
1356 shape: ShapeKind::Scalar,
1357 coercion: CoercionPolicy::NumberLenientText,
1358 max: None,
1359 repeating: None,
1360 default: Some(LiteralValue::Int(0)),
1361 },
1362 ArgSchema {
1364 kinds: smallvec::smallvec![ArgKind::Number],
1365 required: false,
1366 by_ref: false,
1367 shape: ShapeKind::Scalar,
1368 coercion: CoercionPolicy::NumberLenientText,
1369 max: None,
1370 repeating: None,
1371 default: Some(LiteralValue::Int(1)),
1372 },
1373 ArgSchema {
1375 kinds: smallvec::smallvec![ArgKind::Logical],
1376 required: false,
1377 by_ref: false,
1378 shape: ShapeKind::Scalar,
1379 coercion: CoercionPolicy::Logical,
1380 max: None,
1381 repeating: None,
1382 default: Some(LiteralValue::Boolean(false)),
1383 },
1384 ]
1385 });
1386 &SCHEMA
1387 }
1388 fn eval<'a, 'b, 'c>(
1389 &self,
1390 args: &'c [ArgumentHandle<'a, 'b>],
1391 ctx: &dyn FunctionContext<'b>,
1392 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1393 use rand::Rng;
1394
1395 let num = |a: &ArgumentHandle| -> Result<f64, ExcelError> {
1397 Ok(match a.value()?.into_literal() {
1398 LiteralValue::Int(i) => i as f64,
1399 LiteralValue::Number(n) => n,
1400 LiteralValue::Error(e) => return Err(e),
1401 _other => {
1402 return Err(ExcelError::new(ExcelErrorKind::Value));
1403 }
1404 })
1405 };
1406
1407 let rows = if !args.is_empty() {
1408 num(&args[0])? as i64
1409 } else {
1410 1
1411 };
1412 let cols = if args.len() >= 2 {
1413 num(&args[1])? as i64
1414 } else {
1415 1
1416 };
1417 let min_val = if args.len() >= 3 { num(&args[2])? } else { 0.0 };
1418 let max_val = if args.len() >= 4 { num(&args[3])? } else { 1.0 };
1419 let whole_number = if args.len() >= 5 {
1420 matches!(args[4].value()?.into_literal(), LiteralValue::Boolean(true))
1421 } else {
1422 false
1423 };
1424
1425 if rows <= 0 || cols <= 0 {
1427 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1428 ExcelError::new(ExcelErrorKind::Value),
1429 )));
1430 }
1431
1432 if whole_number && min_val > max_val {
1434 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1435 ExcelError::new(ExcelErrorKind::Value),
1436 )));
1437 }
1438
1439 if let Some(e) = generated_array_too_large(rows, cols) {
1440 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1441 }
1442
1443 let mut rng = ctx.rng_for_current(self.function_salt());
1444 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows as usize);
1445
1446 for _r in 0..rows {
1447 let mut row_vec: Vec<LiteralValue> = Vec::with_capacity(cols as usize);
1448 for _c in 0..cols {
1449 let value = if whole_number {
1450 let min_int = min_val.ceil() as i64;
1452 let max_int = max_val.floor() as i64;
1453 if min_int > max_int {
1454 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1455 ExcelError::new(ExcelErrorKind::Value),
1456 )));
1457 }
1458 let rand_int = rng.gen_range(min_int..=max_int);
1459 LiteralValue::Int(rand_int)
1460 } else {
1461 let rand_float = rng.r#gen::<f64>() * (max_val - min_val) + min_val;
1463 LiteralValue::Number(rand_float)
1464 };
1465 row_vec.push(value);
1466 }
1467 out.push(row_vec);
1468 }
1469
1470 Ok(collapse_if_scalar(out, ctx.date_system()))
1471 }
1472}
1473
1474#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1478enum GroupAggregation {
1479 Sum,
1480 Average,
1481 Count,
1482 CountA,
1483 Max,
1484 Min,
1485 Product,
1486 StDev,
1487 StDevP,
1488 Var,
1489 VarP,
1490 Median,
1491}
1492
1493impl GroupAggregation {
1494 fn from_literal(val: &LiteralValue) -> Option<Self> {
1495 match val {
1496 LiteralValue::Text(s) => Self::from_str(s),
1497 LiteralValue::Int(n) => Self::from_num(*n as i32),
1498 LiteralValue::Number(n) => Self::from_num(*n as i32),
1499 _ => None,
1500 }
1501 }
1502
1503 fn from_str(s: &str) -> Option<Self> {
1504 let upper = s.to_ascii_uppercase();
1505 match upper.as_str() {
1506 "SUM" => Some(Self::Sum),
1507 "AVERAGE" | "AVG" => Some(Self::Average),
1508 "COUNT" => Some(Self::Count),
1509 "COUNTA" => Some(Self::CountA),
1510 "MAX" => Some(Self::Max),
1511 "MIN" => Some(Self::Min),
1512 "PRODUCT" => Some(Self::Product),
1513 "STDEV" | "STDEV.S" => Some(Self::StDev),
1514 "STDEVP" | "STDEV.P" => Some(Self::StDevP),
1515 "VAR" | "VAR.S" => Some(Self::Var),
1516 "VARP" | "VAR.P" => Some(Self::VarP),
1517 "MEDIAN" => Some(Self::Median),
1518 _ => None,
1519 }
1520 }
1521
1522 fn from_num(n: i32) -> Option<Self> {
1523 match n {
1525 1 => Some(Self::Average),
1526 2 => Some(Self::Count),
1527 3 => Some(Self::CountA),
1528 4 => Some(Self::Max),
1529 5 => Some(Self::Min),
1530 6 => Some(Self::Product),
1531 7 => Some(Self::StDev),
1532 8 => Some(Self::StDevP),
1533 9 => Some(Self::Sum),
1534 10 => Some(Self::Var),
1535 11 => Some(Self::VarP),
1536 12 => Some(Self::Median),
1537 _ => None,
1538 }
1539 }
1540
1541 fn apply(&self, values: &[f64]) -> f64 {
1542 if values.is_empty() {
1543 return match self {
1544 Self::Count | Self::CountA => 0.0,
1545 Self::Sum | Self::Product => 0.0,
1546 _ => f64::NAN,
1547 };
1548 }
1549
1550 match self {
1551 Self::Sum => values.iter().sum(),
1552 Self::Average => values.iter().sum::<f64>() / values.len() as f64,
1553 Self::Count | Self::CountA => values.len() as f64,
1554 Self::Max => values.iter().copied().fold(f64::NEG_INFINITY, f64::max),
1555 Self::Min => values.iter().copied().fold(f64::INFINITY, f64::min),
1556 Self::Product => values.iter().product(),
1557 Self::StDev => {
1558 if values.len() < 2 {
1559 return f64::NAN;
1560 }
1561 let mean = values.iter().sum::<f64>() / values.len() as f64;
1562 let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>()
1563 / (values.len() - 1) as f64;
1564 variance.sqrt()
1565 }
1566 Self::StDevP => {
1567 let mean = values.iter().sum::<f64>() / values.len() as f64;
1568 let variance =
1569 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
1570 variance.sqrt()
1571 }
1572 Self::Var => {
1573 if values.len() < 2 {
1574 return f64::NAN;
1575 }
1576 let mean = values.iter().sum::<f64>() / values.len() as f64;
1577 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (values.len() - 1) as f64
1578 }
1579 Self::VarP => {
1580 let mean = values.iter().sum::<f64>() / values.len() as f64;
1581 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64
1582 }
1583 Self::Median => {
1584 let mut sorted = values.to_vec();
1585 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1586 let mid = sorted.len() / 2;
1587 if sorted.len().is_multiple_of(2) {
1588 (sorted[mid - 1] + sorted[mid]) / 2.0
1589 } else {
1590 sorted[mid]
1591 }
1592 }
1593 }
1594 }
1595}
1596
1597fn literal_to_group_key(v: &LiteralValue) -> String {
1599 match v {
1600 LiteralValue::Text(s) => s.clone(),
1601 LiteralValue::Int(i) => i.to_string(),
1602 LiteralValue::Number(n) => format!("{:.10}", n),
1603 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
1604 LiteralValue::Empty => String::new(),
1605 LiteralValue::Error(e) => format!("#{:?}", e.kind),
1606 LiteralValue::Array(_) => "[Array]".to_string(),
1607 LiteralValue::Date(d) => d.to_string(),
1608 LiteralValue::DateTime(dt) => dt.to_string(),
1609 LiteralValue::Time(t) => t.to_string(),
1610 LiteralValue::Duration(d) => format!("{:?}", d),
1611 LiteralValue::Pending => "[Pending]".to_string(),
1612 }
1613}
1614
1615fn literal_to_num_opt(v: &LiteralValue) -> Option<f64> {
1617 match v {
1618 LiteralValue::Number(n) => Some(*n),
1619 LiteralValue::Int(i) => Some(*i as f64),
1620 LiteralValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
1621 _ => None,
1622 }
1623}
1624
1625#[derive(Debug)]
1626pub struct GroupByFn;
1627
1628impl Function for GroupByFn {
1692 func_caps!(PURE);
1693 fn name(&self) -> &'static str {
1694 "GROUPBY"
1695 }
1696 fn min_args(&self) -> usize {
1697 3
1698 }
1699 fn variadic(&self) -> bool {
1700 true
1701 }
1702 fn arg_schema(&self) -> &'static [ArgSchema] {
1703 use once_cell::sync::Lazy;
1704 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1705 vec![
1706 ArgSchema {
1708 kinds: smallvec::smallvec![ArgKind::Range],
1709 required: true,
1710 by_ref: true,
1711 shape: ShapeKind::Range,
1712 coercion: CoercionPolicy::None,
1713 max: None,
1714 repeating: None,
1715 default: None,
1716 },
1717 ArgSchema {
1719 kinds: smallvec::smallvec![ArgKind::Range],
1720 required: true,
1721 by_ref: true,
1722 shape: ShapeKind::Range,
1723 coercion: CoercionPolicy::None,
1724 max: None,
1725 repeating: None,
1726 default: None,
1727 },
1728 ArgSchema {
1730 kinds: smallvec::smallvec![ArgKind::Any],
1731 required: true,
1732 by_ref: false,
1733 shape: ShapeKind::Scalar,
1734 coercion: CoercionPolicy::None,
1735 max: None,
1736 repeating: None,
1737 default: None,
1738 },
1739 ArgSchema {
1741 kinds: smallvec::smallvec![ArgKind::Number],
1742 required: false,
1743 by_ref: false,
1744 shape: ShapeKind::Scalar,
1745 coercion: CoercionPolicy::NumberLenientText,
1746 max: None,
1747 repeating: None,
1748 default: Some(LiteralValue::Int(1)),
1749 },
1750 ArgSchema {
1752 kinds: smallvec::smallvec![ArgKind::Number],
1753 required: false,
1754 by_ref: false,
1755 shape: ShapeKind::Scalar,
1756 coercion: CoercionPolicy::NumberLenientText,
1757 max: None,
1758 repeating: None,
1759 default: Some(LiteralValue::Int(0)),
1760 },
1761 ArgSchema {
1763 kinds: smallvec::smallvec![ArgKind::Number],
1764 required: false,
1765 by_ref: false,
1766 shape: ShapeKind::Scalar,
1767 coercion: CoercionPolicy::NumberLenientText,
1768 max: None,
1769 repeating: None,
1770 default: Some(LiteralValue::Int(0)),
1771 },
1772 ]
1773 });
1774 &SCHEMA
1775 }
1776
1777 fn eval<'a, 'b, 'c>(
1778 &self,
1779 args: &'c [ArgumentHandle<'a, 'b>],
1780 _ctx: &dyn FunctionContext<'b>,
1781 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1782 if args.len() < 3 {
1783 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1784 ExcelError::new(ExcelErrorKind::Value),
1785 )));
1786 }
1787
1788 let row_fields_view = match args[0].range_view() {
1790 Ok(v) => v,
1791 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1792 };
1793 let values_view = match args[1].range_view() {
1794 Ok(v) => v,
1795 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1796 };
1797
1798 let agg_val = args[2].value()?.into_literal();
1800 let aggregation = match GroupAggregation::from_literal(&agg_val) {
1801 Some(a) => a,
1802 None => {
1803 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1804 ExcelError::new(ExcelErrorKind::Value)
1805 .with_message("Invalid aggregation function"),
1806 )));
1807 }
1808 };
1809
1810 let field_headers = if args.len() >= 4 {
1812 match args[3].value()?.into_literal() {
1813 LiteralValue::Int(i) => i as i32,
1814 LiteralValue::Number(n) => n as i32,
1815 _ => 1,
1816 }
1817 } else {
1818 1
1819 };
1820
1821 let total_depth = if args.len() >= 5 {
1822 match args[4].value()?.into_literal() {
1823 LiteralValue::Int(i) => i as i32,
1824 LiteralValue::Number(n) => n as i32,
1825 _ => 0,
1826 }
1827 } else {
1828 0
1829 };
1830
1831 let sort_order = if args.len() >= 6 {
1832 match args[5].value()?.into_literal() {
1833 LiteralValue::Int(i) => i as i32,
1834 LiteralValue::Number(n) => n as i32,
1835 _ => 0,
1836 }
1837 } else {
1838 0
1839 };
1840
1841 let (rf_rows, rf_cols) = row_fields_view.dims();
1842 let (val_rows, val_cols) = values_view.dims();
1843
1844 let has_headers = field_headers == 1 || field_headers == 3;
1846 let data_start_row = if has_headers { 1 } else { 0 };
1847
1848 if rf_rows != val_rows {
1850 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1851 ExcelError::new(ExcelErrorKind::Value)
1852 .with_message("Row fields and values must have same number of rows"),
1853 )));
1854 }
1855
1856 if rf_rows <= data_start_row {
1857 return Ok(crate::traits::CalcValue::Range(
1859 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
1860 ));
1861 }
1862
1863 let mut groups: std::collections::HashMap<String, Vec<Vec<f64>>> = HashMap::new();
1866 let mut group_order: Vec<String> = Vec::new();
1867
1868 for r in data_start_row..rf_rows {
1869 let mut key_parts: Vec<String> = Vec::with_capacity(rf_cols);
1871 for c in 0..rf_cols {
1872 key_parts.push(literal_to_group_key(&row_fields_view.get_cell(r, c)));
1873 }
1874 let key = key_parts.join("\x00"); let mut row_values: Vec<Option<f64>> = Vec::with_capacity(val_cols);
1878 for c in 0..val_cols {
1879 row_values.push(literal_to_num_opt(&values_view.get_cell(r, c)));
1880 }
1881
1882 if !groups.contains_key(&key) {
1884 group_order.push(key.clone());
1885 groups.insert(key.clone(), vec![Vec::new(); val_cols]);
1886 }
1887
1888 let group_vals = groups.get_mut(&key).unwrap();
1889 for (c, val) in row_values.iter().enumerate() {
1890 if let Some(v) = val {
1891 group_vals[c].push(*v);
1892 }
1893 }
1894 }
1895
1896 if sort_order != 0 {
1898 group_order.sort_by(|a, b| if sort_order > 0 { a.cmp(b) } else { b.cmp(a) });
1899 }
1900
1901 let mut output: Vec<Vec<LiteralValue>> = Vec::new();
1903
1904 let generate_headers = field_headers == 2 || field_headers == 3;
1906 if generate_headers {
1907 let mut header_row: Vec<LiteralValue> = Vec::new();
1908 for c in 0..rf_cols {
1910 if has_headers {
1911 header_row.push(row_fields_view.get_cell(0, c));
1912 } else {
1913 header_row.push(LiteralValue::Text(format!("Field{}", c + 1)));
1914 }
1915 }
1916 for c in 0..val_cols {
1918 if has_headers {
1919 header_row.push(values_view.get_cell(0, c));
1920 } else {
1921 header_row.push(LiteralValue::Text(format!("Value{}", c + 1)));
1922 }
1923 }
1924 output.push(header_row);
1925 }
1926
1927 for key in &group_order {
1929 let mut row: Vec<LiteralValue> = Vec::new();
1930
1931 let key_parts: Vec<&str> = key.split('\x00').collect();
1933 for part in &key_parts {
1934 row.push(LiteralValue::Text(part.to_string()));
1935 }
1936
1937 let group_vals = groups.get(key).unwrap();
1939 for col_vals in group_vals {
1940 let result = aggregation.apply(col_vals);
1941 if result.is_nan() {
1942 row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
1943 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
1944 row.push(LiteralValue::Int(result as i64));
1945 } else {
1946 row.push(LiteralValue::Number(result));
1947 }
1948 }
1949 output.push(row);
1950 }
1951
1952 if total_depth >= 1 {
1954 let mut total_row: Vec<LiteralValue> = Vec::new();
1955 total_row.push(LiteralValue::Text("Grand Total".to_string()));
1957 for _ in 1..rf_cols {
1958 total_row.push(LiteralValue::Empty);
1959 }
1960
1961 for c in 0..val_cols {
1963 let mut all_vals: Vec<f64> = Vec::new();
1964 for group_vals in groups.values() {
1965 all_vals.extend(&group_vals[c]);
1966 }
1967 let result = aggregation.apply(&all_vals);
1968 if result.is_nan() {
1969 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
1970 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
1971 total_row.push(LiteralValue::Int(result as i64));
1972 } else {
1973 total_row.push(LiteralValue::Number(result));
1974 }
1975 }
1976 output.push(total_row);
1977 }
1978
1979 if output.is_empty() {
1980 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1981 ExcelError::new(ExcelErrorKind::Calc),
1982 )));
1983 }
1984
1985 Ok(collapse_if_scalar(output, _ctx.date_system()))
1986 }
1987}
1988
1989#[derive(Debug)]
1992pub struct PivotByFn;
1993
1994impl Function for PivotByFn {
2066 func_caps!(PURE);
2067 fn name(&self) -> &'static str {
2068 "PIVOTBY"
2069 }
2070 fn min_args(&self) -> usize {
2071 4
2072 }
2073 fn variadic(&self) -> bool {
2074 true
2075 }
2076 fn arg_schema(&self) -> &'static [ArgSchema] {
2077 use once_cell::sync::Lazy;
2078 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2079 vec![
2080 ArgSchema {
2082 kinds: smallvec::smallvec![ArgKind::Range],
2083 required: true,
2084 by_ref: true,
2085 shape: ShapeKind::Range,
2086 coercion: CoercionPolicy::None,
2087 max: None,
2088 repeating: None,
2089 default: None,
2090 },
2091 ArgSchema {
2093 kinds: smallvec::smallvec![ArgKind::Range],
2094 required: true,
2095 by_ref: true,
2096 shape: ShapeKind::Range,
2097 coercion: CoercionPolicy::None,
2098 max: None,
2099 repeating: None,
2100 default: None,
2101 },
2102 ArgSchema {
2104 kinds: smallvec::smallvec![ArgKind::Range],
2105 required: true,
2106 by_ref: true,
2107 shape: ShapeKind::Range,
2108 coercion: CoercionPolicy::None,
2109 max: None,
2110 repeating: None,
2111 default: None,
2112 },
2113 ArgSchema {
2115 kinds: smallvec::smallvec![ArgKind::Any],
2116 required: true,
2117 by_ref: false,
2118 shape: ShapeKind::Scalar,
2119 coercion: CoercionPolicy::None,
2120 max: None,
2121 repeating: None,
2122 default: None,
2123 },
2124 ArgSchema {
2126 kinds: smallvec::smallvec![ArgKind::Number],
2127 required: false,
2128 by_ref: false,
2129 shape: ShapeKind::Scalar,
2130 coercion: CoercionPolicy::NumberLenientText,
2131 max: None,
2132 repeating: None,
2133 default: Some(LiteralValue::Int(1)),
2134 },
2135 ArgSchema {
2137 kinds: smallvec::smallvec![ArgKind::Number],
2138 required: false,
2139 by_ref: false,
2140 shape: ShapeKind::Scalar,
2141 coercion: CoercionPolicy::NumberLenientText,
2142 max: None,
2143 repeating: None,
2144 default: Some(LiteralValue::Int(0)),
2145 },
2146 ArgSchema {
2148 kinds: smallvec::smallvec![ArgKind::Number],
2149 required: false,
2150 by_ref: false,
2151 shape: ShapeKind::Scalar,
2152 coercion: CoercionPolicy::NumberLenientText,
2153 max: None,
2154 repeating: None,
2155 default: Some(LiteralValue::Int(0)),
2156 },
2157 ArgSchema {
2159 kinds: smallvec::smallvec![ArgKind::Number],
2160 required: false,
2161 by_ref: false,
2162 shape: ShapeKind::Scalar,
2163 coercion: CoercionPolicy::NumberLenientText,
2164 max: None,
2165 repeating: None,
2166 default: Some(LiteralValue::Int(0)),
2167 },
2168 ArgSchema {
2170 kinds: smallvec::smallvec![ArgKind::Number],
2171 required: false,
2172 by_ref: false,
2173 shape: ShapeKind::Scalar,
2174 coercion: CoercionPolicy::NumberLenientText,
2175 max: None,
2176 repeating: None,
2177 default: Some(LiteralValue::Int(0)),
2178 },
2179 ]
2180 });
2181 &SCHEMA
2182 }
2183
2184 fn eval<'a, 'b, 'c>(
2185 &self,
2186 args: &'c [ArgumentHandle<'a, 'b>],
2187 _ctx: &dyn FunctionContext<'b>,
2188 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2189 if args.len() < 4 {
2190 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2191 ExcelError::new(ExcelErrorKind::Value),
2192 )));
2193 }
2194
2195 let row_fields_view = match args[0].range_view() {
2197 Ok(v) => v,
2198 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2199 };
2200 let col_fields_view = match args[1].range_view() {
2201 Ok(v) => v,
2202 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2203 };
2204 let values_view = match args[2].range_view() {
2205 Ok(v) => v,
2206 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2207 };
2208
2209 let agg_val = args[3].value()?.into_literal();
2211 let aggregation = match GroupAggregation::from_literal(&agg_val) {
2212 Some(a) => a,
2213 None => {
2214 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2215 ExcelError::new(ExcelErrorKind::Value)
2216 .with_message("Invalid aggregation function"),
2217 )));
2218 }
2219 };
2220
2221 let field_headers = if args.len() >= 5 {
2223 match args[4].value()?.into_literal() {
2224 LiteralValue::Int(i) => i as i32,
2225 LiteralValue::Number(n) => n as i32,
2226 _ => 1,
2227 }
2228 } else {
2229 1
2230 };
2231
2232 let row_total_depth = if args.len() >= 6 {
2233 match args[5].value()?.into_literal() {
2234 LiteralValue::Int(i) => i as i32,
2235 LiteralValue::Number(n) => n as i32,
2236 _ => 0,
2237 }
2238 } else {
2239 0
2240 };
2241
2242 let row_sort_order = if args.len() >= 7 {
2243 match args[6].value()?.into_literal() {
2244 LiteralValue::Int(i) => i as i32,
2245 LiteralValue::Number(n) => n as i32,
2246 _ => 0,
2247 }
2248 } else {
2249 0
2250 };
2251
2252 let col_total_depth = if args.len() >= 8 {
2253 match args[7].value()?.into_literal() {
2254 LiteralValue::Int(i) => i as i32,
2255 LiteralValue::Number(n) => n as i32,
2256 _ => 0,
2257 }
2258 } else {
2259 0
2260 };
2261
2262 let col_sort_order = if args.len() >= 9 {
2263 match args[8].value()?.into_literal() {
2264 LiteralValue::Int(i) => i as i32,
2265 LiteralValue::Number(n) => n as i32,
2266 _ => 0,
2267 }
2268 } else {
2269 0
2270 };
2271
2272 let (rf_rows, rf_cols) = row_fields_view.dims();
2273 let (cf_rows, _cf_cols) = col_fields_view.dims();
2274 let (val_rows, _val_cols) = values_view.dims();
2275
2276 if rf_rows != cf_rows || rf_rows != val_rows {
2278 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2279 ExcelError::new(ExcelErrorKind::Value)
2280 .with_message("All ranges must have same number of rows"),
2281 )));
2282 }
2283
2284 let has_headers = field_headers == 1 || field_headers == 3;
2285 let data_start_row = if has_headers { 1 } else { 0 };
2286
2287 if rf_rows <= data_start_row {
2288 return Ok(crate::traits::CalcValue::Range(
2289 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2290 ));
2291 }
2292
2293 let mut row_keys: Vec<String> = Vec::new();
2295 let mut col_keys: Vec<String> = Vec::new();
2296 let mut row_key_set: std::collections::HashSet<String> = std::collections::HashSet::new();
2297 let mut col_key_set: std::collections::HashSet<String> = std::collections::HashSet::new();
2298
2299 let mut pivot_data: HashMap<(String, String), Vec<f64>> = HashMap::new();
2301
2302 for r in data_start_row..rf_rows {
2303 let mut row_key_parts: Vec<String> = Vec::with_capacity(rf_cols);
2305 for c in 0..rf_cols {
2306 row_key_parts.push(literal_to_group_key(&row_fields_view.get_cell(r, c)));
2307 }
2308 let row_key = row_key_parts.join("\x00");
2309
2310 let col_key = literal_to_group_key(&col_fields_view.get_cell(r, 0));
2313
2314 let val = literal_to_num_opt(&values_view.get_cell(r, 0));
2316
2317 if !row_key_set.contains(&row_key) {
2319 row_key_set.insert(row_key.clone());
2320 row_keys.push(row_key.clone());
2321 }
2322 if !col_key_set.contains(&col_key) {
2323 col_key_set.insert(col_key.clone());
2324 col_keys.push(col_key.clone());
2325 }
2326
2327 let entry = pivot_data.entry((row_key, col_key)).or_default();
2329 if let Some(v) = val {
2330 entry.push(v);
2331 }
2332 }
2333
2334 if row_sort_order != 0 {
2336 row_keys.sort_by(|a, b| {
2337 if row_sort_order > 0 {
2338 a.cmp(b)
2339 } else {
2340 b.cmp(a)
2341 }
2342 });
2343 }
2344 if col_sort_order != 0 {
2345 col_keys.sort_by(|a, b| {
2346 if col_sort_order > 0 {
2347 a.cmp(b)
2348 } else {
2349 b.cmp(a)
2350 }
2351 });
2352 }
2353
2354 let mut output: Vec<Vec<LiteralValue>> = Vec::new();
2356
2357 let generate_headers = field_headers == 2 || field_headers == 3;
2359 if generate_headers || has_headers {
2360 let mut header_row: Vec<LiteralValue> = Vec::new();
2361 for _ in 0..rf_cols {
2363 header_row.push(LiteralValue::Empty);
2364 }
2365 for col_key in &col_keys {
2367 let parts: Vec<&str> = col_key.split('\x00').collect();
2369 header_row.push(LiteralValue::Text(parts.join(" ")));
2370 }
2371 if col_total_depth >= 1 {
2373 header_row.push(LiteralValue::Text("Total".to_string()));
2374 }
2375 output.push(header_row);
2376 }
2377
2378 for row_key in &row_keys {
2380 let mut row: Vec<LiteralValue> = Vec::new();
2381
2382 let row_parts: Vec<&str> = row_key.split('\x00').collect();
2384 for part in &row_parts {
2385 row.push(LiteralValue::Text(part.to_string()));
2386 }
2387
2388 let mut row_total_vals: Vec<f64> = Vec::new();
2390 for col_key in &col_keys {
2391 let key = (row_key.clone(), col_key.clone());
2392 let vals = pivot_data.get(&key).map(|v| v.as_slice()).unwrap_or(&[]);
2393 let result = aggregation.apply(vals);
2394
2395 row_total_vals.extend(vals);
2397
2398 if result.is_nan() || vals.is_empty() {
2399 row.push(LiteralValue::Empty);
2400 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2401 row.push(LiteralValue::Int(result as i64));
2402 } else {
2403 row.push(LiteralValue::Number(result));
2404 }
2405 }
2406
2407 if col_total_depth >= 1 {
2409 let result = aggregation.apply(&row_total_vals);
2410 if result.is_nan() {
2411 row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2412 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2413 row.push(LiteralValue::Int(result as i64));
2414 } else {
2415 row.push(LiteralValue::Number(result));
2416 }
2417 }
2418
2419 output.push(row);
2420 }
2421
2422 if row_total_depth >= 1 {
2424 let mut total_row: Vec<LiteralValue> = Vec::new();
2425 total_row.push(LiteralValue::Text("Total".to_string()));
2426 for _ in 1..rf_cols {
2427 total_row.push(LiteralValue::Empty);
2428 }
2429
2430 let mut grand_total_vals: Vec<f64> = Vec::new();
2431 for col_key in &col_keys {
2432 let mut col_vals: Vec<f64> = Vec::new();
2433 for row_key in &row_keys {
2434 let key = (row_key.clone(), col_key.clone());
2435 if let Some(vals) = pivot_data.get(&key) {
2436 col_vals.extend(vals);
2437 }
2438 }
2439 grand_total_vals.extend(&col_vals);
2440 let result = aggregation.apply(&col_vals);
2441 if result.is_nan() {
2442 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2443 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2444 total_row.push(LiteralValue::Int(result as i64));
2445 } else {
2446 total_row.push(LiteralValue::Number(result));
2447 }
2448 }
2449
2450 if col_total_depth >= 1 {
2452 let result = aggregation.apply(&grand_total_vals);
2453 if result.is_nan() {
2454 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2455 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2456 total_row.push(LiteralValue::Int(result as i64));
2457 } else {
2458 total_row.push(LiteralValue::Number(result));
2459 }
2460 }
2461
2462 output.push(total_row);
2463 }
2464
2465 if output.is_empty() {
2466 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2467 ExcelError::new(ExcelErrorKind::Calc),
2468 )));
2469 }
2470
2471 Ok(collapse_if_scalar(output, _ctx.date_system()))
2472 }
2473}
2474
2475#[derive(Debug)]
2478pub struct FilterFn;
2479impl Function for FilterFn {
2537 func_caps!(PURE);
2538 fn name(&self) -> &'static str {
2539 "FILTER"
2540 }
2541 fn min_args(&self) -> usize {
2542 2
2543 }
2544 fn variadic(&self) -> bool {
2545 true
2546 }
2547 fn arg_schema(&self) -> &'static [ArgSchema] {
2548 use once_cell::sync::Lazy;
2549 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2550 vec![
2551 ArgSchema {
2553 kinds: smallvec::smallvec![ArgKind::Range],
2554 required: true,
2555 by_ref: true,
2556 shape: ShapeKind::Range,
2557 coercion: CoercionPolicy::None,
2558 max: None,
2559 repeating: None,
2560 default: None,
2561 },
2562 ArgSchema {
2564 kinds: smallvec::smallvec![ArgKind::Range],
2565 required: true,
2566 by_ref: true,
2567 shape: ShapeKind::Range,
2568 coercion: CoercionPolicy::None,
2569 max: None,
2570 repeating: None,
2571 default: None,
2572 },
2573 ArgSchema {
2575 kinds: smallvec::smallvec![ArgKind::Any],
2576 required: false,
2577 by_ref: false,
2578 shape: ShapeKind::Scalar,
2579 coercion: CoercionPolicy::None,
2580 max: None,
2581 repeating: None,
2582 default: None,
2583 },
2584 ]
2585 });
2586 &SCHEMA
2587 }
2588 fn eval<'a, 'b, 'c>(
2589 &self,
2590 args: &'c [ArgumentHandle<'a, 'b>],
2591 _ctx: &dyn FunctionContext<'b>,
2592 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2593 if args.len() < 2 {
2594 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2595 ExcelError::new(ExcelErrorKind::Value),
2596 )));
2597 }
2598 let array_view = args[0].range_view()?;
2599 let include_view = args[1].range_view()?;
2600
2601 let (array_rows, array_cols) = array_view.dims();
2602 if array_rows == 0 || array_cols == 0 {
2603 return Ok(crate::traits::CalcValue::Range(
2604 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2605 ));
2606 }
2607
2608 let (include_rows, include_cols) = include_view.dims();
2609 if include_rows != array_rows && include_rows != 1 {
2610 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2611 ExcelError::new(ExcelErrorKind::Value),
2612 )));
2613 }
2614
2615 let mut result: Vec<Vec<LiteralValue>> = Vec::new();
2616 for r in 0..array_rows {
2617 let include_r = if include_rows == array_rows { r } else { 0 };
2618 let mut include = false;
2619 for c in 0..include_cols {
2620 if include_view.get_cell(include_r, c).is_truthy() {
2621 include = true;
2622 break;
2623 }
2624 }
2625
2626 if include {
2627 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(array_cols);
2628 for c in 0..array_cols {
2629 row_out.push(array_view.get_cell(r, c));
2630 }
2631 result.push(row_out);
2632 }
2633 }
2634
2635 if result.is_empty() {
2636 if args.len() >= 3 {
2637 return args[2].value();
2638 }
2639 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2640 ExcelError::new(ExcelErrorKind::Calc),
2641 )));
2642 }
2643
2644 Ok(crate::traits::CalcValue::Range(
2645 crate::engine::range_view::RangeView::from_owned_rows(result, _ctx.date_system()),
2646 ))
2647 }
2648}
2649
2650#[derive(Debug)]
2653pub struct UniqueFn;
2654impl Function for UniqueFn {
2710 func_caps!(PURE);
2711 fn name(&self) -> &'static str {
2712 "UNIQUE"
2713 }
2714 fn min_args(&self) -> usize {
2715 1
2716 }
2717 fn variadic(&self) -> bool {
2718 true
2719 }
2720 fn arg_schema(&self) -> &'static [ArgSchema] {
2721 use once_cell::sync::Lazy;
2722 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2723 vec![
2724 ArgSchema {
2725 kinds: smallvec::smallvec![ArgKind::Range],
2726 required: true,
2727 by_ref: true,
2728 shape: ShapeKind::Range,
2729 coercion: CoercionPolicy::None,
2730 max: None,
2731 repeating: None,
2732 default: None,
2733 },
2734 ArgSchema {
2735 kinds: smallvec::smallvec![ArgKind::Logical],
2736 required: false,
2737 by_ref: false,
2738 shape: ShapeKind::Scalar,
2739 coercion: CoercionPolicy::Logical,
2740 max: None,
2741 repeating: None,
2742 default: Some(LiteralValue::Boolean(false)),
2743 },
2744 ArgSchema {
2745 kinds: smallvec::smallvec![ArgKind::Logical],
2746 required: false,
2747 by_ref: false,
2748 shape: ShapeKind::Scalar,
2749 coercion: CoercionPolicy::Logical,
2750 max: None,
2751 repeating: None,
2752 default: Some(LiteralValue::Boolean(false)),
2753 },
2754 ]
2755 });
2756 &SCHEMA
2757 }
2758 fn eval<'a, 'b, 'c>(
2759 &self,
2760 args: &'c [ArgumentHandle<'a, 'b>],
2761 _ctx: &dyn FunctionContext<'b>,
2762 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2763 let view = match args[0].range_view() {
2764 Ok(v) => v,
2765 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2766 };
2767 let (rows, cols) = view.dims();
2768 if rows == 0 || cols == 0 {
2769 return Ok(crate::traits::CalcValue::Range(
2770 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2771 ));
2772 }
2773
2774 let by_col = if args.len() >= 2 {
2775 matches!(args[1].value()?.into_literal(), LiteralValue::Boolean(true))
2776 } else {
2777 false
2778 };
2779 let exactly_once = if args.len() >= 3 {
2780 matches!(args[2].value()?.into_literal(), LiteralValue::Boolean(true))
2781 } else {
2782 false
2783 };
2784
2785 if by_col {
2786 #[derive(Hash, Eq, PartialEq, Clone)]
2787 struct ColKey(Vec<LiteralValue>);
2788
2789 let mut order: Vec<ColKey> = Vec::new();
2790 let mut counts: HashMap<ColKey, usize> = HashMap::new();
2791
2792 for c in 0..cols {
2793 let mut col_vals: Vec<LiteralValue> = Vec::with_capacity(rows);
2794 for r in 0..rows {
2795 col_vals.push(view.get_cell(r, c));
2796 }
2797 let key = ColKey(col_vals);
2798 if !counts.contains_key(&key) {
2799 order.push(key.clone());
2800 }
2801 *counts.entry(key).or_insert(0) += 1;
2802 }
2803
2804 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
2805 for k in order {
2806 if !exactly_once || counts.get(&k) == Some(&1) {
2807 out.push(k.0);
2808 }
2809 }
2810 return Ok(collapse_if_scalar(out, _ctx.date_system()));
2811 }
2812
2813 #[derive(Hash, Eq, PartialEq, Clone)]
2814 struct RowKey(Vec<LiteralValue>);
2815
2816 let mut order: Vec<RowKey> = Vec::new();
2817 let mut counts: HashMap<RowKey, usize> = HashMap::new();
2818 for r in 0..rows {
2819 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
2820 for c in 0..cols {
2821 row_vals.push(view.get_cell(r, c));
2822 }
2823 let key = RowKey(row_vals);
2824 if !counts.contains_key(&key) {
2825 order.push(key.clone());
2826 }
2827 *counts.entry(key).or_insert(0) += 1;
2828 }
2829
2830 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
2831 for k in order {
2832 if !exactly_once || counts.get(&k) == Some(&1) {
2833 out.push(k.0);
2834 }
2835 }
2836 Ok(collapse_if_scalar(out, _ctx.date_system()))
2837 }
2838}
2839
2840#[derive(Debug)]
2843pub struct SequenceFn;
2844impl Function for SequenceFn {
2889 func_caps!(PURE);
2890 fn name(&self) -> &'static str {
2891 "SEQUENCE"
2892 }
2893 fn min_args(&self) -> usize {
2894 1
2895 }
2896 fn variadic(&self) -> bool {
2897 true
2898 }
2899 fn arg_schema(&self) -> &'static [ArgSchema] {
2900 use once_cell::sync::Lazy;
2901 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2902 vec![
2903 ArgSchema {
2905 kinds: smallvec::smallvec![ArgKind::Number],
2906 required: true,
2907 by_ref: false,
2908 shape: ShapeKind::Scalar,
2909 coercion: CoercionPolicy::NumberLenientText,
2910 max: None,
2911 repeating: None,
2912 default: None,
2913 },
2914 ArgSchema {
2916 kinds: smallvec::smallvec![ArgKind::Number],
2917 required: false,
2918 by_ref: false,
2919 shape: ShapeKind::Scalar,
2920 coercion: CoercionPolicy::NumberLenientText,
2921 max: None,
2922 repeating: None,
2923 default: Some(LiteralValue::Int(1)),
2924 },
2925 ArgSchema {
2927 kinds: smallvec::smallvec![ArgKind::Number],
2928 required: false,
2929 by_ref: false,
2930 shape: ShapeKind::Scalar,
2931 coercion: CoercionPolicy::NumberLenientText,
2932 max: None,
2933 repeating: None,
2934 default: Some(LiteralValue::Int(1)),
2935 },
2936 ArgSchema {
2938 kinds: smallvec::smallvec![ArgKind::Number],
2939 required: false,
2940 by_ref: false,
2941 shape: ShapeKind::Scalar,
2942 coercion: CoercionPolicy::NumberLenientText,
2943 max: None,
2944 repeating: None,
2945 default: Some(LiteralValue::Int(1)),
2946 },
2947 ]
2948 });
2949 &SCHEMA
2950 }
2951 fn eval<'a, 'b, 'c>(
2952 &self,
2953 args: &'c [ArgumentHandle<'a, 'b>],
2954 _ctx: &dyn FunctionContext<'b>,
2955 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2956 let num = |a: &ArgumentHandle| -> Result<f64, ExcelError> {
2958 Ok(match a.value()?.into_literal() {
2959 LiteralValue::Int(i) => i as f64,
2960 LiteralValue::Number(n) => n,
2961 _other => {
2962 return Err(ExcelError::new(ExcelErrorKind::Value));
2963 }
2964 })
2965 };
2966 let rows_f = num(&args[0])?;
2967 let rows = rows_f as i64;
2968 let cols = if args.len() >= 2 {
2969 num(&args[1])? as i64
2970 } else {
2971 1
2972 };
2973 let start = if args.len() >= 3 { num(&args[2])? } else { 1.0 };
2974 let step = if args.len() >= 4 { num(&args[3])? } else { 1.0 };
2975 if rows <= 0 || cols <= 0 {
2976 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2977 ExcelError::new(ExcelErrorKind::Value),
2978 )));
2979 }
2980 if let Some(e) = generated_array_too_large(rows, cols) {
2981 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2982 }
2983 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows as usize);
2984 let mut current = start;
2985 for _r in 0..rows {
2986 let mut row_vec: Vec<LiteralValue> = Vec::with_capacity(cols as usize);
2987 for _c in 0..cols {
2988 if (current.fract().abs() < 1e-12) && current.abs() < (i64::MAX as f64) {
2990 row_vec.push(LiteralValue::Int(current as i64));
2991 } else {
2992 row_vec.push(LiteralValue::Number(current));
2993 }
2994 current += step;
2995 }
2996 out.push(row_vec);
2997 }
2998
2999 Ok(collapse_if_scalar(out, _ctx.date_system()))
3000 }
3001}
3002
3003#[derive(Debug)]
3006pub struct TransposeFn;
3007impl Function for TransposeFn {
3060 func_caps!(PURE);
3061 fn name(&self) -> &'static str {
3062 "TRANSPOSE"
3063 }
3064 fn min_args(&self) -> usize {
3065 1
3066 }
3067 fn variadic(&self) -> bool {
3068 false
3069 }
3070 fn arg_schema(&self) -> &'static [ArgSchema] {
3071 use once_cell::sync::Lazy;
3072 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3073 vec![ArgSchema {
3074 kinds: smallvec::smallvec![ArgKind::Range],
3075 required: true,
3076 by_ref: true,
3077 shape: ShapeKind::Range,
3078 coercion: CoercionPolicy::None,
3079 max: None,
3080 repeating: None,
3081 default: None,
3082 }]
3083 });
3084 &SCHEMA
3085 }
3086 fn eval<'a, 'b, 'c>(
3087 &self,
3088 args: &'c [ArgumentHandle<'a, 'b>],
3089 _ctx: &dyn FunctionContext<'b>,
3090 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3091 let view = match args[0].range_view() {
3092 Ok(v) => v,
3093 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3094 };
3095 let (rows, cols) = view.dims();
3096 if rows == 0 || cols == 0 {
3097 return Ok(crate::traits::CalcValue::Range(
3098 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3099 ));
3100 }
3101
3102 let mut out: Vec<Vec<LiteralValue>> = vec![Vec::with_capacity(rows); cols];
3103 for (c, col) in out.iter_mut().enumerate().take(cols) {
3104 for r in 0..rows {
3105 col.push(view.get_cell(r, c));
3106 }
3107 }
3108 Ok(collapse_if_scalar(out, _ctx.date_system()))
3109 }
3110}
3111
3112#[derive(Debug)]
3115pub struct TakeFn;
3116impl Function for TakeFn {
3172 func_caps!(PURE);
3173 fn name(&self) -> &'static str {
3174 "TAKE"
3175 }
3176 fn min_args(&self) -> usize {
3177 2
3178 }
3179 fn variadic(&self) -> bool {
3180 true
3181 }
3182 fn arg_schema(&self) -> &'static [ArgSchema] {
3183 use once_cell::sync::Lazy;
3184 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3185 vec![
3186 ArgSchema {
3187 kinds: smallvec::smallvec![ArgKind::Range],
3188 required: true,
3189 by_ref: true,
3190 shape: ShapeKind::Range,
3191 coercion: CoercionPolicy::None,
3192 max: None,
3193 repeating: None,
3194 default: None,
3195 },
3196 ArgSchema {
3197 kinds: smallvec::smallvec![ArgKind::Number],
3198 required: true,
3199 by_ref: false,
3200 shape: ShapeKind::Scalar,
3201 coercion: CoercionPolicy::NumberLenientText,
3202 max: None,
3203 repeating: None,
3204 default: None,
3205 },
3206 ArgSchema {
3207 kinds: smallvec::smallvec![ArgKind::Number],
3208 required: false,
3209 by_ref: false,
3210 shape: ShapeKind::Scalar,
3211 coercion: CoercionPolicy::NumberLenientText,
3212 max: None,
3213 repeating: None,
3214 default: None,
3215 },
3216 ]
3217 });
3218 &SCHEMA
3219 }
3220 fn eval<'a, 'b, 'c>(
3221 &self,
3222 args: &'c [ArgumentHandle<'a, 'b>],
3223 _ctx: &dyn FunctionContext<'b>,
3224 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3225 let view = match args[0].range_view() {
3226 Ok(v) => v,
3227 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3228 };
3229 let (rows, cols) = view.dims();
3230 if rows == 0 || cols == 0 {
3231 return Ok(crate::traits::CalcValue::Range(
3232 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3233 ));
3234 }
3235
3236 let height = rows as i64;
3237 let width = cols as i64;
3238
3239 let num = |a: &ArgumentHandle| -> Result<i64, ExcelError> {
3240 Ok(match a.value()?.into_literal() {
3241 LiteralValue::Int(i) => i,
3242 LiteralValue::Number(n) => n as i64,
3243 _ => 0,
3244 })
3245 };
3246 let take_rows = num(&args[1])?;
3247 let take_cols = if args.len() >= 3 {
3248 Some(num(&args[2])?)
3249 } else {
3250 None
3251 };
3252
3253 if take_rows.abs() > height {
3254 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3255 ExcelError::new(ExcelErrorKind::Value),
3256 )));
3257 }
3258
3259 let (row_start, row_end) = if take_rows >= 0 {
3260 (0usize, take_rows as usize)
3261 } else {
3262 ((height + take_rows) as usize, height as usize)
3263 };
3264
3265 let (col_start, col_end) = if let Some(tc) = take_cols {
3266 if tc.abs() > width {
3267 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3268 ExcelError::new(ExcelErrorKind::Value),
3269 )));
3270 }
3271 if tc >= 0 {
3272 (0usize, tc as usize)
3273 } else {
3274 ((width + tc) as usize, width as usize)
3275 }
3276 } else {
3277 (0usize, width as usize)
3278 };
3279
3280 if row_start >= row_end || col_start >= col_end {
3281 return Ok(crate::traits::CalcValue::Range(
3282 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3283 ));
3284 }
3285
3286 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_end - row_start);
3287 for r in row_start..row_end {
3288 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(col_end - col_start);
3289 for c in col_start..col_end {
3290 row_out.push(view.get_cell(r, c));
3291 }
3292 out.push(row_out);
3293 }
3294
3295 Ok(collapse_if_scalar(out, _ctx.date_system()))
3296 }
3297}
3298
3299#[derive(Debug)]
3302pub struct DropFn;
3303impl Function for DropFn {
3356 func_caps!(PURE);
3357 fn name(&self) -> &'static str {
3358 "DROP"
3359 }
3360 fn min_args(&self) -> usize {
3361 2
3362 }
3363 fn variadic(&self) -> bool {
3364 true
3365 }
3366 fn arg_schema(&self) -> &'static [ArgSchema] {
3367 use once_cell::sync::Lazy;
3368 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3369 vec![
3370 ArgSchema {
3371 kinds: smallvec::smallvec![ArgKind::Range],
3372 required: true,
3373 by_ref: true,
3374 shape: ShapeKind::Range,
3375 coercion: CoercionPolicy::None,
3376 max: None,
3377 repeating: None,
3378 default: None,
3379 },
3380 ArgSchema {
3381 kinds: smallvec::smallvec![ArgKind::Number],
3382 required: true,
3383 by_ref: false,
3384 shape: ShapeKind::Scalar,
3385 coercion: CoercionPolicy::NumberLenientText,
3386 max: None,
3387 repeating: None,
3388 default: None,
3389 },
3390 ArgSchema {
3391 kinds: smallvec::smallvec![ArgKind::Number],
3392 required: false,
3393 by_ref: false,
3394 shape: ShapeKind::Scalar,
3395 coercion: CoercionPolicy::NumberLenientText,
3396 max: None,
3397 repeating: None,
3398 default: None,
3399 },
3400 ]
3401 });
3402 &SCHEMA
3403 }
3404 fn eval<'a, 'b, 'c>(
3405 &self,
3406 args: &'c [ArgumentHandle<'a, 'b>],
3407 _ctx: &dyn FunctionContext<'b>,
3408 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3409 let view = match args[0].range_view() {
3410 Ok(v) => v,
3411 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3412 };
3413 let (rows, cols) = view.dims();
3414 if rows == 0 || cols == 0 {
3415 return Ok(crate::traits::CalcValue::Range(
3416 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3417 ));
3418 }
3419
3420 let height = rows as i64;
3421 let width = cols as i64;
3422
3423 let num = |a: &ArgumentHandle| -> Result<i64, ExcelError> {
3424 Ok(match a.value()?.into_literal() {
3425 LiteralValue::Int(i) => i,
3426 LiteralValue::Number(n) => n as i64,
3427 _ => 0,
3428 })
3429 };
3430 let drop_rows = num(&args[1])?;
3431 let drop_cols = if args.len() >= 3 {
3432 Some(num(&args[2])?)
3433 } else {
3434 None
3435 };
3436
3437 let (row_start, row_end) = if drop_rows >= 0 {
3438 ((drop_rows as usize).min(height as usize), height as usize)
3439 } else {
3440 (0usize, (height + drop_rows).max(0) as usize)
3441 };
3442
3443 let (col_start, col_end) = if let Some(dc) = drop_cols {
3444 if dc >= 0 {
3445 ((dc as usize).min(width as usize), width as usize)
3446 } else {
3447 (0usize, (width + dc).max(0) as usize)
3448 }
3449 } else {
3450 (0usize, width as usize)
3451 };
3452
3453 if row_start >= row_end || col_start >= col_end {
3454 return Ok(crate::traits::CalcValue::Range(
3455 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3456 ));
3457 }
3458
3459 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_end - row_start);
3460 for r in row_start..row_end {
3461 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(col_end - col_start);
3462 for c in col_start..col_end {
3463 row_out.push(view.get_cell(r, c));
3464 }
3465 out.push(row_out);
3466 }
3467
3468 Ok(collapse_if_scalar(out, _ctx.date_system()))
3469 }
3470}
3471
3472pub fn register_builtins() {
3473 use crate::function_registry::register_function;
3474 use std::sync::Arc;
3475 register_function(Arc::new(XLookupFn));
3476 register_function(Arc::new(FilterFn));
3477 register_function(Arc::new(UniqueFn));
3478 register_function(Arc::new(SequenceFn));
3479 register_function(Arc::new(TransposeFn));
3480 register_function(Arc::new(TakeFn));
3481 register_function(Arc::new(DropFn));
3482 register_function(Arc::new(XMatchFn));
3483 register_function(Arc::new(SortFn));
3484 register_function(Arc::new(SortByFn));
3485 register_function(Arc::new(RandArrayFn));
3486 register_function(Arc::new(GroupByFn));
3487 register_function(Arc::new(PivotByFn));
3488}
3489
3490#[cfg(test)]
3493mod tests {
3494 use super::*;
3495 use crate::test_workbook::TestWorkbook;
3496 use crate::traits::ArgumentHandle;
3497 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
3498 use std::sync::Arc;
3499
3500 #[test]
3501 fn test_all_dynamic_functions_registered() {
3502 crate::builtins::load_builtins();
3504
3505 let functions = [
3506 "XLOOKUP",
3507 "FILTER",
3508 "UNIQUE",
3509 "SEQUENCE",
3510 "TRANSPOSE",
3511 "TAKE",
3512 "DROP",
3513 "XMATCH",
3514 "SORT",
3515 "SORTBY",
3516 "RANDARRAY",
3517 "GROUPBY",
3518 "PIVOTBY",
3519 ];
3520
3521 for name in &functions {
3522 let result = crate::function_registry::get("", name);
3523 assert!(result.is_some(), "Function {} should be registered", name);
3524 }
3525 }
3526
3527 fn lit(v: LiteralValue) -> ASTNode {
3528 ASTNode::new(ASTNodeType::Literal(v), None)
3529 }
3530
3531 fn range(r: &str, sr: u32, sc: u32, er: u32, ec: u32) -> ASTNode {
3532 ASTNode::new(
3533 ASTNodeType::Reference {
3534 original: r.into(),
3535 reference: ReferenceType::range(None, Some(sr), Some(sc), Some(er), Some(ec)),
3536 },
3537 None,
3538 )
3539 }
3540
3541 #[test]
3542 fn xlookup_basic_exact_and_if_not_found() {
3543 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3544 let wb = wb
3545 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("a".into()))
3546 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("b".into()))
3547 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3548 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20));
3549 let ctx = wb.interpreter();
3550 let lookup_range = range("A1:A2", 1, 1, 2, 1);
3551 let return_range = range("B1:B2", 1, 2, 2, 2);
3552 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3553 let key_b = lit(LiteralValue::Text("b".into()));
3554 let args = vec![
3555 ArgumentHandle::new(&key_b, &ctx),
3556 ArgumentHandle::new(&lookup_range, &ctx),
3557 ArgumentHandle::new(&return_range, &ctx),
3558 ];
3559 let v = f
3560 .dispatch(&args, &ctx.function_context(None))
3561 .unwrap()
3562 .into_literal();
3563 assert_eq!(v, LiteralValue::Number(20.0));
3564 let key_missing = lit(LiteralValue::Text("z".into()));
3565 let if_nf = lit(LiteralValue::Text("NF".into()));
3566 let args_nf = vec![
3567 ArgumentHandle::new(&key_missing, &ctx),
3568 ArgumentHandle::new(&lookup_range, &ctx),
3569 ArgumentHandle::new(&return_range, &ctx),
3570 ArgumentHandle::new(&if_nf, &ctx),
3571 ];
3572 let v_nf = f
3573 .dispatch(&args_nf, &ctx.function_context(None))
3574 .unwrap()
3575 .into_literal();
3576 assert_eq!(v_nf, LiteralValue::Text("NF".into()));
3577 }
3578
3579 #[test]
3580 fn xlookup_match_modes_next_smaller_larger() {
3581 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3582 let wb = wb
3583 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3584 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
3585 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
3586 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(1))
3587 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(2))
3588 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(3));
3589 let ctx = wb.interpreter();
3590 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3591 let return_range = range("B1:B3", 1, 2, 3, 2);
3592 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3593 let needle_25 = lit(LiteralValue::Int(25));
3594 let mm_next_smaller = lit(LiteralValue::Int(-1));
3595 let nf_text = lit(LiteralValue::Text("NF".into()));
3596 let args_smaller = vec![
3597 ArgumentHandle::new(&needle_25, &ctx),
3598 ArgumentHandle::new(&lookup_range, &ctx),
3599 ArgumentHandle::new(&return_range, &ctx),
3600 ArgumentHandle::new(&nf_text, &ctx),
3601 ArgumentHandle::new(&mm_next_smaller, &ctx),
3602 ];
3603 let v_smaller = f
3604 .dispatch(&args_smaller, &ctx.function_context(None))
3605 .unwrap()
3606 .into_literal();
3607 assert_eq!(v_smaller, LiteralValue::Number(2.0));
3608 let mm_next_larger = lit(LiteralValue::Int(1));
3609 let nf_text2 = lit(LiteralValue::Text("NF".into()));
3610 let args_larger = vec![
3611 ArgumentHandle::new(&needle_25, &ctx),
3612 ArgumentHandle::new(&lookup_range, &ctx),
3613 ArgumentHandle::new(&return_range, &ctx),
3614 ArgumentHandle::new(&nf_text2, &ctx),
3615 ArgumentHandle::new(&mm_next_larger, &ctx),
3616 ];
3617 let v_larger = f
3618 .dispatch(&args_larger, &ctx.function_context(None))
3619 .unwrap()
3620 .into_literal();
3621 assert_eq!(v_larger, LiteralValue::Number(3.0));
3622 }
3623
3624 #[test]
3625 fn xlookup_wildcard_and_not_found_default_na() {
3626 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3627 let wb = wb
3628 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Alpha".into()))
3629 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Beta".into()))
3630 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Gamma".into()))
3631 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
3632 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
3633 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(300));
3634 let ctx = wb.interpreter();
3635 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3636 let return_range = range("B1:B3", 1, 2, 3, 2);
3637 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3638 let pattern = lit(LiteralValue::Text("*et*".into()));
3640 let match_mode_wild = lit(LiteralValue::Int(2));
3641 let nf_binding = lit(LiteralValue::Text("NF".into()));
3642 let args_wild = vec![
3643 ArgumentHandle::new(&pattern, &ctx),
3644 ArgumentHandle::new(&lookup_range, &ctx),
3645 ArgumentHandle::new(&return_range, &ctx),
3646 ArgumentHandle::new(&nf_binding, &ctx),
3647 ArgumentHandle::new(&match_mode_wild, &ctx),
3648 ];
3649 let v_wild = f
3650 .dispatch(&args_wild, &ctx.function_context(None))
3651 .unwrap()
3652 .into_literal();
3653 assert_eq!(v_wild, LiteralValue::Number(200.0));
3654 let pattern_lit_star = lit(LiteralValue::Text("~*eta".into()));
3656 let args_lit = vec![
3657 ArgumentHandle::new(&pattern_lit_star, &ctx),
3658 ArgumentHandle::new(&lookup_range, &ctx),
3659 ArgumentHandle::new(&return_range, &ctx),
3660 ArgumentHandle::new(&nf_binding, &ctx),
3661 ArgumentHandle::new(&match_mode_wild, &ctx),
3662 ];
3663 let v_lit = f
3664 .dispatch(&args_lit, &ctx.function_context(None))
3665 .unwrap()
3666 .into_literal();
3667 match v_lit {
3668 LiteralValue::Text(s) => assert_eq!(s, "NF"),
3669 other => panic!("expected NF text got {other:?}"),
3670 }
3671 let missing = lit(LiteralValue::Text("Zeta".into()));
3673 let args_nf = vec![
3674 ArgumentHandle::new(&missing, &ctx),
3675 ArgumentHandle::new(&lookup_range, &ctx),
3676 ArgumentHandle::new(&return_range, &ctx),
3677 ];
3678 let v_nf = f
3679 .dispatch(&args_nf, &ctx.function_context(None))
3680 .unwrap()
3681 .into_literal();
3682 match v_nf {
3683 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Na),
3684 other => panic!("expected #N/A got {other:?}"),
3685 }
3686 }
3687
3688 #[test]
3689 fn xlookup_unicode_exact_and_wildcard_are_case_insensitive() {
3690 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3691 let wb = wb
3692 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("ИВАН".into()))
3693 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Петр".into()))
3694 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Иванов".into()))
3695 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
3696 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
3697 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(300));
3698 let ctx = wb.interpreter();
3699 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3700 let return_range = range("B1:B3", 1, 2, 3, 2);
3701 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3702 let nf = lit(LiteralValue::Text("NF".into()));
3703
3704 let exact = lit(LiteralValue::Text("иван".into()));
3705 let exact_args = vec![
3706 ArgumentHandle::new(&exact, &ctx),
3707 ArgumentHandle::new(&lookup_range, &ctx),
3708 ArgumentHandle::new(&return_range, &ctx),
3709 ArgumentHandle::new(&nf, &ctx),
3710 ];
3711 let exact_v = f
3712 .dispatch(&exact_args, &ctx.function_context(None))
3713 .unwrap()
3714 .into_literal();
3715 assert_eq!(exact_v, LiteralValue::Number(100.0));
3716
3717 let wildcard = lit(LiteralValue::Text("ив?н*".into()));
3718 let wildcard_mode = lit(LiteralValue::Int(2));
3719 let wildcard_args = vec![
3720 ArgumentHandle::new(&wildcard, &ctx),
3721 ArgumentHandle::new(&lookup_range, &ctx),
3722 ArgumentHandle::new(&return_range, &ctx),
3723 ArgumentHandle::new(&nf, &ctx),
3724 ArgumentHandle::new(&wildcard_mode, &ctx),
3725 ];
3726 let wildcard_v = f
3727 .dispatch(&wildcard_args, &ctx.function_context(None))
3728 .unwrap()
3729 .into_literal();
3730 assert_eq!(wildcard_v, LiteralValue::Number(100.0));
3731 }
3732
3733 #[test]
3734 fn xlookup_unicode_reverse_search_uses_prepared_matcher() {
3735 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3736 let wb = wb
3737 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("ИВАН".into()))
3738 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Петр".into()))
3739 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Иванов".into()))
3740 .with_cell_a1("Sheet1", "A4", LiteralValue::Text("ИВАН".into()))
3741 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
3742 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
3743 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(300))
3744 .with_cell_a1("Sheet1", "B4", LiteralValue::Int(400));
3745 let ctx = wb.interpreter();
3746 let lookup_range = range("A1:A4", 1, 1, 4, 1);
3747 let return_range = range("B1:B4", 1, 2, 4, 2);
3748 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3749 let nf = lit(LiteralValue::Text("NF".into()));
3750 let reverse = lit(LiteralValue::Int(-1));
3751 let exact_mode = lit(LiteralValue::Int(0));
3752
3753 let exact = lit(LiteralValue::Text("иван".into()));
3754 let exact_args = vec![
3755 ArgumentHandle::new(&exact, &ctx),
3756 ArgumentHandle::new(&lookup_range, &ctx),
3757 ArgumentHandle::new(&return_range, &ctx),
3758 ArgumentHandle::new(&nf, &ctx),
3759 ArgumentHandle::new(&exact_mode, &ctx),
3760 ArgumentHandle::new(&reverse, &ctx),
3761 ];
3762 let exact_v = f
3763 .dispatch(&exact_args, &ctx.function_context(None))
3764 .unwrap()
3765 .into_literal();
3766 assert_eq!(exact_v, LiteralValue::Number(400.0));
3767
3768 let wildcard = lit(LiteralValue::Text("ив?н*".into()));
3769 let wildcard_mode = lit(LiteralValue::Int(2));
3770 let wildcard_args = vec![
3771 ArgumentHandle::new(&wildcard, &ctx),
3772 ArgumentHandle::new(&lookup_range, &ctx),
3773 ArgumentHandle::new(&return_range, &ctx),
3774 ArgumentHandle::new(&nf, &ctx),
3775 ArgumentHandle::new(&wildcard_mode, &ctx),
3776 ArgumentHandle::new(&reverse, &ctx),
3777 ];
3778 let wildcard_v = f
3779 .dispatch(&wildcard_args, &ctx.function_context(None))
3780 .unwrap()
3781 .into_literal();
3782 assert_eq!(wildcard_v, LiteralValue::Number(400.0));
3783 }
3784
3785 #[test]
3786 fn xlookup_reverse_search_mode_picks_last() {
3787 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3788 let wb = wb
3789 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3790 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3791 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(1))
3792 .with_cell_a1("Sheet1", "B1", LiteralValue::Text("First".into()))
3793 .with_cell_a1("Sheet1", "B2", LiteralValue::Text("Mid".into()))
3794 .with_cell_a1("Sheet1", "B3", LiteralValue::Text("Last".into()));
3795 let ctx = wb.interpreter();
3796 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3797 let return_range = range("B1:B3", 1, 2, 3, 2);
3798 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3799 let needle_one = lit(LiteralValue::Int(1));
3800 let search_rev = lit(LiteralValue::Int(-1));
3801 let nf_binding2 = lit(LiteralValue::Text("NF".into()));
3802 let match_mode_zero = lit(LiteralValue::Int(0));
3803 let args_rev = vec![
3804 ArgumentHandle::new(&needle_one, &ctx),
3805 ArgumentHandle::new(&lookup_range, &ctx),
3806 ArgumentHandle::new(&return_range, &ctx),
3807 ArgumentHandle::new(&nf_binding2, &ctx),
3808 ArgumentHandle::new(&match_mode_zero, &ctx),
3809 ArgumentHandle::new(&search_rev, &ctx),
3810 ];
3811 let v_rev = f
3812 .dispatch(&args_rev, &ctx.function_context(None))
3813 .unwrap()
3814 .into_literal();
3815 assert_eq!(v_rev, LiteralValue::Text("Last".into()));
3816 }
3817
3818 #[test]
3819 fn xlookup_horizontal_returns_column_vector_for_matrix_return() {
3820 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3821 let wb = wb
3822 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3823 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(20))
3824 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(30))
3825 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(1))
3826 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(2))
3827 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(3))
3828 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(4))
3829 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(5))
3830 .with_cell_a1("Sheet1", "C3", LiteralValue::Int(6));
3831 let ctx = wb.interpreter();
3832 let lookup_range = range("A1:C1", 1, 1, 1, 3);
3833 let return_range = range("A2:C3", 2, 1, 3, 3);
3834 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3835 let needle = lit(LiteralValue::Int(20));
3836 let args = vec![
3837 ArgumentHandle::new(&needle, &ctx),
3838 ArgumentHandle::new(&lookup_range, &ctx),
3839 ArgumentHandle::new(&return_range, &ctx),
3840 ];
3841 let v = f
3842 .dispatch(&args, &ctx.function_context(None))
3843 .unwrap()
3844 .into_literal();
3845 match v {
3846 LiteralValue::Array(a) => {
3847 assert_eq!(
3848 a,
3849 vec![
3850 vec![LiteralValue::Number(2.0)],
3851 vec![LiteralValue::Number(5.0)]
3852 ]
3853 );
3854 }
3855 other => panic!("expected array got {other:?}"),
3856 }
3857 }
3858
3859 #[test]
3860 fn xlookup_vertical_returns_row_vector_for_matrix_return() {
3861 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3862 let wb = wb
3863 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3864 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
3865 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
3866 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(101))
3867 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(102))
3868 .with_cell_a1("Sheet1", "D1", LiteralValue::Int(103))
3869 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(201))
3870 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(202))
3871 .with_cell_a1("Sheet1", "D2", LiteralValue::Int(203))
3872 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(301))
3873 .with_cell_a1("Sheet1", "C3", LiteralValue::Int(302))
3874 .with_cell_a1("Sheet1", "D3", LiteralValue::Int(303));
3875 let ctx = wb.interpreter();
3876 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3877 let return_range = range("B1:D3", 1, 2, 3, 4);
3878 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3879 let needle = lit(LiteralValue::Int(20));
3880 let args = vec![
3881 ArgumentHandle::new(&needle, &ctx),
3882 ArgumentHandle::new(&lookup_range, &ctx),
3883 ArgumentHandle::new(&return_range, &ctx),
3884 ];
3885 let v = f
3886 .dispatch(&args, &ctx.function_context(None))
3887 .unwrap()
3888 .into_literal();
3889 match v {
3890 LiteralValue::Array(a) => {
3891 assert_eq!(
3892 a,
3893 vec![vec![
3894 LiteralValue::Number(201.0),
3895 LiteralValue::Number(202.0),
3896 LiteralValue::Number(203.0)
3897 ]]
3898 );
3899 }
3900 other => panic!("expected array got {other:?}"),
3901 }
3902 }
3903
3904 #[test]
3905 fn filter_basic_and_if_empty() {
3906 let wb = TestWorkbook::new().with_function(Arc::new(FilterFn));
3907 let wb = wb
3908 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3909 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3910 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3911 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
3912 .with_cell_a1("Sheet1", "C1", LiteralValue::Boolean(true))
3913 .with_cell_a1("Sheet1", "C2", LiteralValue::Boolean(false));
3914 let ctx = wb.interpreter();
3915 let array_range = range("A1:B2", 1, 1, 2, 2);
3916 let include_range = range("C1:C2", 1, 3, 2, 3);
3917 let f = ctx.context.get_function("", "FILTER").unwrap();
3918 let args = vec![
3919 ArgumentHandle::new(&array_range, &ctx),
3920 ArgumentHandle::new(&include_range, &ctx),
3921 ];
3922 let v = f
3923 .dispatch(&args, &ctx.function_context(None))
3924 .unwrap()
3925 .into_literal();
3926 match v {
3927 LiteralValue::Array(a) => {
3928 assert_eq!(a.len(), 1);
3929 assert_eq!(
3930 a[0],
3931 vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)]
3932 );
3933 }
3934 other => panic!("expected array got {other:?}"),
3935 }
3936 let wb2 = wb
3937 .with_cell_a1("Sheet1", "C1", LiteralValue::Boolean(false))
3938 .with_cell_a1("Sheet1", "C2", LiteralValue::Boolean(false));
3939 let ctx2 = wb2.interpreter();
3940 let f2 = ctx2.context.get_function("", "FILTER").unwrap();
3941 let empty_text = lit(LiteralValue::Text("EMPTY".into()));
3942 let args_empty = vec![
3943 ArgumentHandle::new(&array_range, &ctx2),
3944 ArgumentHandle::new(&include_range, &ctx2),
3945 ArgumentHandle::new(&empty_text, &ctx2),
3946 ];
3947 let v_empty = f2
3948 .dispatch(&args_empty, &ctx2.function_context(None))
3949 .unwrap()
3950 .into_literal();
3951 assert_eq!(v_empty, LiteralValue::Text("EMPTY".into()));
3952 }
3953
3954 #[test]
3955 fn unique_basic_and_exactly_once() {
3956 let wb = TestWorkbook::new().with_function(Arc::new(UniqueFn));
3957 let wb = wb
3958 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3959 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(1))
3960 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(2))
3961 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(3));
3962 let ctx = wb.interpreter();
3963 let range = range("A1:A4", 1, 1, 4, 1);
3964 let f = ctx.context.get_function("", "UNIQUE").unwrap();
3965 let args = vec![ArgumentHandle::new(&range, &ctx)];
3966 let v = f
3967 .dispatch(&args, &ctx.function_context(None))
3968 .unwrap()
3969 .into_literal();
3970 match v {
3971 LiteralValue::Array(a) => {
3972 assert_eq!(a.len(), 3);
3973 assert_eq!(a[0][0], LiteralValue::Number(1.0));
3974 }
3975 _ => panic!("expected array"),
3976 }
3977 }
3978
3979 #[test]
3980 fn generated_array_guard_boundaries() {
3981 assert!(generated_array_too_large(4096, 4096).is_none());
3983 assert!(generated_array_too_large(4096, 4097).is_some());
3985 assert!(generated_array_too_large(1_048_576, 1).is_none());
3987 assert!(generated_array_too_large(1_048_577, 1).is_some());
3988 assert!(generated_array_too_large(1, 16_384).is_none());
3989 assert!(generated_array_too_large(1, 16_385).is_some());
3990 assert!(generated_array_too_large(i64::MAX, i64::MAX).is_some());
3992 }
3993
3994 #[test]
3995 fn sequence_oversized_returns_num_error_quickly() {
3996 let wb = TestWorkbook::new().with_function(Arc::new(SequenceFn));
3997 let ctx = wb.interpreter();
3998 let f = ctx.context.get_function("", "SEQUENCE").unwrap();
3999 let rows = lit(LiteralValue::Number(1e6));
4001 let cols = lit(LiteralValue::Number(1e6));
4002 let args = vec![
4003 ArgumentHandle::new(&rows, &ctx),
4004 ArgumentHandle::new(&cols, &ctx),
4005 ];
4006 let started = std::time::Instant::now();
4007 let v = f
4008 .dispatch(&args, &ctx.function_context(None))
4009 .unwrap()
4010 .into_literal();
4011 let elapsed = started.elapsed();
4012 match v {
4013 LiteralValue::Error(e) => {
4014 assert_eq!(e.kind, formualizer_common::ExcelErrorKind::Num)
4015 }
4016 other => panic!("expected #NUM! got {other:?}"),
4017 }
4018 assert!(
4019 elapsed.as_millis() < 250,
4020 "oversized SEQUENCE must short-circuit, took {elapsed:?}"
4021 );
4022
4023 let rows = lit(LiteralValue::Int(1_048_576));
4025 let cols = lit(LiteralValue::Int(16_384));
4026 let args = vec![
4027 ArgumentHandle::new(&rows, &ctx),
4028 ArgumentHandle::new(&cols, &ctx),
4029 ];
4030 match f
4031 .dispatch(&args, &ctx.function_context(None))
4032 .unwrap()
4033 .into_literal()
4034 {
4035 LiteralValue::Error(e) => {
4036 assert_eq!(e.kind, formualizer_common::ExcelErrorKind::Num)
4037 }
4038 other => panic!("expected #NUM! got {other:?}"),
4039 }
4040 }
4041
4042 #[test]
4043 fn sequence_large_but_legal_succeeds() {
4044 let wb = TestWorkbook::new().with_function(Arc::new(SequenceFn));
4046 let ctx = wb.interpreter();
4047 let f = ctx.context.get_function("", "SEQUENCE").unwrap();
4048 let rows = lit(LiteralValue::Int(1_048_576));
4049 let args = vec![ArgumentHandle::new(&rows, &ctx)];
4050 match f
4051 .dispatch(&args, &ctx.function_context(None))
4052 .unwrap()
4053 .into_literal()
4054 {
4055 LiteralValue::Array(a) => {
4056 assert_eq!(a.len(), 1_048_576);
4057 assert_eq!(a[0][0], LiteralValue::Number(1.0));
4058 }
4059 other => panic!("expected array got {other:?}"),
4060 }
4061 }
4062
4063 #[test]
4064 fn sequence_negative_and_zero_dims_keep_value_error() {
4065 let wb = TestWorkbook::new().with_function(Arc::new(SequenceFn));
4066 let ctx = wb.interpreter();
4067 let f = ctx.context.get_function("", "SEQUENCE").unwrap();
4068 for (r, c) in [(0i64, 5i64), (-3, 5), (5, 0), (5, -1)] {
4069 let rows = lit(LiteralValue::Int(r));
4070 let cols = lit(LiteralValue::Int(c));
4071 let args = vec![
4072 ArgumentHandle::new(&rows, &ctx),
4073 ArgumentHandle::new(&cols, &ctx),
4074 ];
4075 match f
4076 .dispatch(&args, &ctx.function_context(None))
4077 .unwrap()
4078 .into_literal()
4079 {
4080 LiteralValue::Error(e) => {
4081 assert_eq!(
4082 e.kind,
4083 formualizer_common::ExcelErrorKind::Value,
4084 "SEQUENCE({r},{c})"
4085 )
4086 }
4087 other => panic!("expected #VALUE! for SEQUENCE({r},{c}), got {other:?}"),
4088 }
4089 }
4090 }
4091
4092 #[test]
4093 fn randarray_oversized_returns_num_error_quickly() {
4094 let wb = TestWorkbook::new().with_function(Arc::new(RandArrayFn));
4095 let ctx = wb.interpreter();
4096 let f = ctx.context.get_function("", "RANDARRAY").unwrap();
4097 let rows = lit(LiteralValue::Number(1e6));
4098 let cols = lit(LiteralValue::Number(1e6));
4099 let args = vec![
4100 ArgumentHandle::new(&rows, &ctx),
4101 ArgumentHandle::new(&cols, &ctx),
4102 ];
4103 let started = std::time::Instant::now();
4104 let v = f
4105 .dispatch(&args, &ctx.function_context(None))
4106 .unwrap()
4107 .into_literal();
4108 let elapsed = started.elapsed();
4109 match v {
4110 LiteralValue::Error(e) => {
4111 assert_eq!(e.kind, formualizer_common::ExcelErrorKind::Num)
4112 }
4113 other => panic!("expected #NUM! got {other:?}"),
4114 }
4115 assert!(
4116 elapsed.as_millis() < 250,
4117 "oversized RANDARRAY must short-circuit, took {elapsed:?}"
4118 );
4119 }
4120
4121 #[test]
4122 fn sequence_basic_rows_cols_step() {
4123 let wb = TestWorkbook::new().with_function(Arc::new(SequenceFn));
4124 let ctx = wb.interpreter();
4125 let f = ctx.context.get_function("", "SEQUENCE").unwrap();
4126 let rows = lit(LiteralValue::Int(2));
4127 let cols = lit(LiteralValue::Int(3));
4128 let start = lit(LiteralValue::Int(5));
4129 let step = lit(LiteralValue::Int(2));
4130 let args = vec![
4131 ArgumentHandle::new(&rows, &ctx),
4132 ArgumentHandle::new(&cols, &ctx),
4133 ArgumentHandle::new(&start, &ctx),
4134 ArgumentHandle::new(&step, &ctx),
4135 ];
4136 let v = f
4137 .dispatch(&args, &ctx.function_context(None))
4138 .unwrap()
4139 .into_literal();
4140 match v {
4141 LiteralValue::Array(a) => {
4142 assert_eq!(a.len(), 2);
4143 assert_eq!(a[0][0], LiteralValue::Number(5.0));
4144 }
4145 other => panic!("expected array got {other:?}"),
4146 }
4147 }
4148
4149 #[test]
4150 fn transpose_basic() {
4151 let wb = TestWorkbook::new().with_function(Arc::new(TransposeFn));
4152 let wb = wb
4153 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4154 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
4155 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
4156 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20));
4157 let ctx = wb.interpreter();
4158 let arr = range("A1:B2", 1, 1, 2, 2);
4159 let f = ctx.context.get_function("", "TRANSPOSE").unwrap();
4160 let args = vec![ArgumentHandle::new(&arr, &ctx)];
4161 let v = f
4162 .dispatch(&args, &ctx.function_context(None))
4163 .unwrap()
4164 .into_literal();
4165 match v {
4166 LiteralValue::Array(a) => {
4167 assert_eq!(a.len(), 2);
4168 assert_eq!(
4169 a[0],
4170 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)]
4171 );
4172 }
4173 other => panic!("expected array got {other:?}"),
4174 }
4175 }
4176
4177 #[test]
4178 fn take_basic() {
4179 let wb = TestWorkbook::new().with_function(Arc::new(TakeFn));
4180 let wb = wb
4181 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4182 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2));
4183 let ctx = wb.interpreter();
4184 let arr = range("A1:A2", 1, 1, 2, 1);
4185 let f = ctx.context.get_function("", "TAKE").unwrap();
4186 let one = lit(LiteralValue::Int(1));
4187 let args = vec![
4188 ArgumentHandle::new(&arr, &ctx),
4189 ArgumentHandle::new(&one, &ctx),
4190 ];
4191 let v = f
4192 .dispatch(&args, &ctx.function_context(None))
4193 .unwrap()
4194 .into_literal();
4195 assert_eq!(v, LiteralValue::Number(1.0));
4196 }
4197
4198 #[test]
4199 fn drop_basic() {
4200 let wb = TestWorkbook::new().with_function(Arc::new(DropFn));
4201 let wb = wb
4202 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4203 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2));
4204 let ctx = wb.interpreter();
4205 let arr = range("A1:A2", 1, 1, 2, 1);
4206 let f = ctx.context.get_function("", "DROP").unwrap();
4207 let one = lit(LiteralValue::Int(1));
4208 let args = vec![
4209 ArgumentHandle::new(&arr, &ctx),
4210 ArgumentHandle::new(&one, &ctx),
4211 ];
4212 let v = f
4213 .dispatch(&args, &ctx.function_context(None))
4214 .unwrap()
4215 .into_literal();
4216 assert_eq!(v, LiteralValue::Number(2.0));
4217 }
4218
4219 #[test]
4220 fn xmatch_exact_match_default() {
4221 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4222 let wb = wb
4223 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("apple".into()))
4224 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("banana".into()))
4225 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("cherry".into()));
4226 let ctx = wb.interpreter();
4227 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4228 let f = ctx.context.get_function("", "XMATCH").unwrap();
4229 let key = lit(LiteralValue::Text("banana".into()));
4230 let args = vec![
4231 ArgumentHandle::new(&key, &ctx),
4232 ArgumentHandle::new(&lookup_range, &ctx),
4233 ];
4234 let v = f
4235 .dispatch(&args, &ctx.function_context(None))
4236 .unwrap()
4237 .into_literal();
4238 assert_eq!(v, LiteralValue::Int(2));
4239 }
4240
4241 #[test]
4242 fn xmatch_exact_or_next_smaller() {
4243 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4244 let wb = wb
4245 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
4246 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
4247 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30));
4248 let ctx = wb.interpreter();
4249 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4250 let f = ctx.context.get_function("", "XMATCH").unwrap();
4251 let needle = lit(LiteralValue::Int(25));
4252 let match_mode = lit(LiteralValue::Int(-1)); let args = vec![
4254 ArgumentHandle::new(&needle, &ctx),
4255 ArgumentHandle::new(&lookup_range, &ctx),
4256 ArgumentHandle::new(&match_mode, &ctx),
4257 ];
4258 let v = f
4259 .dispatch(&args, &ctx.function_context(None))
4260 .unwrap()
4261 .into_literal();
4262 assert_eq!(v, LiteralValue::Int(2)); }
4264
4265 #[test]
4266 fn xmatch_exact_or_next_larger() {
4267 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4268 let wb = wb
4269 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
4270 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
4271 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30));
4272 let ctx = wb.interpreter();
4273 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4274 let f = ctx.context.get_function("", "XMATCH").unwrap();
4275 let needle = lit(LiteralValue::Int(25));
4276 let match_mode = lit(LiteralValue::Int(1)); let args = vec![
4278 ArgumentHandle::new(&needle, &ctx),
4279 ArgumentHandle::new(&lookup_range, &ctx),
4280 ArgumentHandle::new(&match_mode, &ctx),
4281 ];
4282 let v = f
4283 .dispatch(&args, &ctx.function_context(None))
4284 .unwrap()
4285 .into_literal();
4286 assert_eq!(v, LiteralValue::Int(3)); }
4288
4289 #[test]
4290 fn xmatch_wildcard() {
4291 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4292 let wb = wb
4293 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("alpha".into()))
4294 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("beta".into()))
4295 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("gamma".into()));
4296 let ctx = wb.interpreter();
4297 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4298 let f = ctx.context.get_function("", "XMATCH").unwrap();
4299 let pattern = lit(LiteralValue::Text("*eta".into()));
4300 let match_mode = lit(LiteralValue::Int(2)); let args = vec![
4302 ArgumentHandle::new(&pattern, &ctx),
4303 ArgumentHandle::new(&lookup_range, &ctx),
4304 ArgumentHandle::new(&match_mode, &ctx),
4305 ];
4306 let v = f
4307 .dispatch(&args, &ctx.function_context(None))
4308 .unwrap()
4309 .into_literal();
4310 assert_eq!(v, LiteralValue::Int(2)); }
4312
4313 #[test]
4314 fn xmatch_unicode_wildcard_is_case_insensitive() {
4315 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4316 let wb = wb
4317 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("ИВАН".into()))
4318 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Петр".into()))
4319 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Иванов".into()));
4320 let ctx = wb.interpreter();
4321 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4322 let f = ctx.context.get_function("", "XMATCH").unwrap();
4323 let pattern = lit(LiteralValue::Text("ив?н*".into()));
4324 let match_mode = lit(LiteralValue::Int(2));
4325 let args = vec![
4326 ArgumentHandle::new(&pattern, &ctx),
4327 ArgumentHandle::new(&lookup_range, &ctx),
4328 ArgumentHandle::new(&match_mode, &ctx),
4329 ];
4330 let v = f
4331 .dispatch(&args, &ctx.function_context(None))
4332 .unwrap()
4333 .into_literal();
4334 assert_eq!(v, LiteralValue::Int(1));
4335 }
4336
4337 #[test]
4338 fn xmatch_reverse_search() {
4339 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4340 let wb = wb
4341 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4342 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
4343 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(1)); let ctx = wb.interpreter();
4345 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4346 let f = ctx.context.get_function("", "XMATCH").unwrap();
4347 let needle = lit(LiteralValue::Int(1));
4348 let match_mode = lit(LiteralValue::Int(0));
4349 let search_mode = lit(LiteralValue::Int(-1)); let args = vec![
4351 ArgumentHandle::new(&needle, &ctx),
4352 ArgumentHandle::new(&lookup_range, &ctx),
4353 ArgumentHandle::new(&match_mode, &ctx),
4354 ArgumentHandle::new(&search_mode, &ctx),
4355 ];
4356 let v = f
4357 .dispatch(&args, &ctx.function_context(None))
4358 .unwrap()
4359 .into_literal();
4360 assert_eq!(v, LiteralValue::Int(3)); }
4362
4363 #[test]
4364 fn xmatch_not_found() {
4365 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4366 let wb = wb
4367 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4368 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
4369 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(3));
4370 let ctx = wb.interpreter();
4371 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4372 let f = ctx.context.get_function("", "XMATCH").unwrap();
4373 let needle = lit(LiteralValue::Int(5));
4374 let args = vec![
4375 ArgumentHandle::new(&needle, &ctx),
4376 ArgumentHandle::new(&lookup_range, &ctx),
4377 ];
4378 let v = f
4379 .dispatch(&args, &ctx.function_context(None))
4380 .unwrap()
4381 .into_literal();
4382 match v {
4383 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Na),
4384 other => panic!("expected #N/A got {other:?}"),
4385 }
4386 }
4387
4388 #[test]
4389 fn sort_basic_ascending() {
4390 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4391 let wb = wb
4392 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(30))
4393 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
4394 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20));
4395 let ctx = wb.interpreter();
4396 let arr = range("A1:A3", 1, 1, 3, 1);
4397 let f = ctx.context.get_function("", "SORT").unwrap();
4398 let args = vec![ArgumentHandle::new(&arr, &ctx)];
4399 let v = f
4400 .dispatch(&args, &ctx.function_context(None))
4401 .unwrap()
4402 .into_literal();
4403 match v {
4404 LiteralValue::Array(a) => {
4405 assert_eq!(a.len(), 3);
4406 assert_eq!(a[0][0], LiteralValue::Number(10.0));
4407 assert_eq!(a[1][0], LiteralValue::Number(20.0));
4408 assert_eq!(a[2][0], LiteralValue::Number(30.0));
4409 }
4410 other => panic!("expected array got {other:?}"),
4411 }
4412 }
4413
4414 #[test]
4415 fn sort_descending() {
4416 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4417 let wb = wb
4418 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(30))
4419 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
4420 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20));
4421 let ctx = wb.interpreter();
4422 let arr = range("A1:A3", 1, 1, 3, 1);
4423 let f = ctx.context.get_function("", "SORT").unwrap();
4424 let sort_index = lit(LiteralValue::Int(1));
4425 let sort_order = lit(LiteralValue::Int(-1)); let args = vec![
4427 ArgumentHandle::new(&arr, &ctx),
4428 ArgumentHandle::new(&sort_index, &ctx),
4429 ArgumentHandle::new(&sort_order, &ctx),
4430 ];
4431 let v = f
4432 .dispatch(&args, &ctx.function_context(None))
4433 .unwrap()
4434 .into_literal();
4435 match v {
4436 LiteralValue::Array(a) => {
4437 assert_eq!(a.len(), 3);
4438 assert_eq!(a[0][0], LiteralValue::Number(30.0));
4439 assert_eq!(a[1][0], LiteralValue::Number(20.0));
4440 assert_eq!(a[2][0], LiteralValue::Number(10.0));
4441 }
4442 other => panic!("expected array got {other:?}"),
4443 }
4444 }
4445
4446 #[test]
4447 fn sort_by_column() {
4448 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4449 let wb = wb
4450 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4451 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(30))
4452 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4453 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(10))
4454 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4455 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(20));
4456 let ctx = wb.interpreter();
4457 let arr = range("A1:B3", 1, 1, 3, 2);
4458 let f = ctx.context.get_function("", "SORT").unwrap();
4459 let sort_index = lit(LiteralValue::Int(2)); let args = vec![
4461 ArgumentHandle::new(&arr, &ctx),
4462 ArgumentHandle::new(&sort_index, &ctx),
4463 ];
4464 let v = f
4465 .dispatch(&args, &ctx.function_context(None))
4466 .unwrap()
4467 .into_literal();
4468 match v {
4469 LiteralValue::Array(a) => {
4470 assert_eq!(a.len(), 3);
4471 assert_eq!(a[0][0], LiteralValue::Text("Alice".into()));
4473 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4474 assert_eq!(a[2][0], LiteralValue::Text("Charlie".into()));
4475 }
4476 other => panic!("expected array got {other:?}"),
4477 }
4478 }
4479
4480 #[test]
4481 fn sortby_basic() {
4482 let wb = TestWorkbook::new().with_function(Arc::new(SortByFn));
4483 let wb = wb
4484 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4485 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4486 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4487 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(3))
4488 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(1))
4489 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(2));
4490 let ctx = wb.interpreter();
4491 let arr = range("A1:A3", 1, 1, 3, 1);
4492 let by_arr = range("B1:B3", 1, 2, 3, 2);
4493 let f = ctx.context.get_function("", "SORTBY").unwrap();
4494 let args = vec![
4495 ArgumentHandle::new(&arr, &ctx),
4496 ArgumentHandle::new(&by_arr, &ctx),
4497 ];
4498 let v = f
4499 .dispatch(&args, &ctx.function_context(None))
4500 .unwrap()
4501 .into_literal();
4502 match v {
4503 LiteralValue::Array(a) => {
4504 assert_eq!(a.len(), 3);
4505 assert_eq!(a[0][0], LiteralValue::Text("Alice".into()));
4507 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4508 assert_eq!(a[2][0], LiteralValue::Text("Charlie".into()));
4509 }
4510 other => panic!("expected array got {other:?}"),
4511 }
4512 }
4513
4514 #[test]
4515 fn sortby_descending() {
4516 let wb = TestWorkbook::new().with_function(Arc::new(SortByFn));
4517 let wb = wb
4518 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4519 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4520 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4521 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(3))
4522 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(1))
4523 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(2));
4524 let ctx = wb.interpreter();
4525 let arr = range("A1:A3", 1, 1, 3, 1);
4526 let by_arr = range("B1:B3", 1, 2, 3, 2);
4527 let sort_order = lit(LiteralValue::Int(-1)); let f = ctx.context.get_function("", "SORTBY").unwrap();
4529 let args = vec![
4530 ArgumentHandle::new(&arr, &ctx),
4531 ArgumentHandle::new(&by_arr, &ctx),
4532 ArgumentHandle::new(&sort_order, &ctx),
4533 ];
4534 let v = f
4535 .dispatch(&args, &ctx.function_context(None))
4536 .unwrap()
4537 .into_literal();
4538 match v {
4539 LiteralValue::Array(a) => {
4540 assert_eq!(a.len(), 3);
4541 assert_eq!(a[0][0], LiteralValue::Text("Charlie".into()));
4543 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4544 assert_eq!(a[2][0], LiteralValue::Text("Alice".into()));
4545 }
4546 other => panic!("expected array got {other:?}"),
4547 }
4548 }
4549
4550 #[test]
4551 fn randarray_basic() {
4552 let wb = TestWorkbook::new().with_function(Arc::new(RandArrayFn));
4553 let ctx = wb.interpreter();
4554 let f = ctx.context.get_function("", "RANDARRAY").unwrap();
4555
4556 let rows = lit(LiteralValue::Int(2));
4558 let cols = lit(LiteralValue::Int(3));
4559 let args = vec![
4560 ArgumentHandle::new(&rows, &ctx),
4561 ArgumentHandle::new(&cols, &ctx),
4562 ];
4563 let v = f
4564 .dispatch(&args, &ctx.function_context(None))
4565 .unwrap()
4566 .into_literal();
4567 match v {
4568 LiteralValue::Array(a) => {
4569 assert_eq!(a.len(), 2);
4570 assert_eq!(a[0].len(), 3);
4571 for row in &a {
4573 for cell in row {
4574 match cell {
4575 LiteralValue::Number(n) => {
4576 assert!(*n >= 0.0 && *n < 1.0, "Value {n} not in [0, 1)");
4577 }
4578 other => panic!("expected Number got {other:?}"),
4579 }
4580 }
4581 }
4582 }
4583 other => panic!("expected array got {other:?}"),
4584 }
4585 }
4586
4587 #[test]
4588 fn randarray_whole_numbers() {
4589 let wb = TestWorkbook::new().with_function(Arc::new(RandArrayFn));
4590 let ctx = wb.interpreter();
4591 let f = ctx.context.get_function("", "RANDARRAY").unwrap();
4592
4593 let rows = lit(LiteralValue::Int(3));
4595 let cols = lit(LiteralValue::Int(2));
4596 let min = lit(LiteralValue::Int(1));
4597 let max = lit(LiteralValue::Int(10));
4598 let whole = lit(LiteralValue::Boolean(true));
4599 let args = vec![
4600 ArgumentHandle::new(&rows, &ctx),
4601 ArgumentHandle::new(&cols, &ctx),
4602 ArgumentHandle::new(&min, &ctx),
4603 ArgumentHandle::new(&max, &ctx),
4604 ArgumentHandle::new(&whole, &ctx),
4605 ];
4606 let v = f
4607 .dispatch(&args, &ctx.function_context(None))
4608 .unwrap()
4609 .into_literal();
4610 match v {
4611 LiteralValue::Array(a) => {
4612 assert_eq!(a.len(), 3);
4613 assert_eq!(a[0].len(), 2);
4614 for row in &a {
4616 for cell in row {
4617 let n = match cell {
4618 LiteralValue::Int(n) => *n as f64,
4619 LiteralValue::Number(n) => *n,
4620 other => panic!("expected Int or Number got {other:?}"),
4621 };
4622 assert!((1.0..=10.0).contains(&n), "Value {n} not in [1, 10]");
4623 assert!(n.fract() == 0.0, "Value {n} is not a whole number");
4625 }
4626 }
4627 }
4628 other => panic!("expected array got {other:?}"),
4629 }
4630 }
4631}