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::function::Function; use crate::traits::{ArgumentHandle, FunctionContext};
27use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
28use formualizer_macros::func_caps;
29use std::collections::HashMap;
30
31pub fn super_wildcard_match(pattern: &str, text: &str) -> bool {
34 super::lookup_utils::wildcard_pattern_match(pattern, text)
35}
36
37#[derive(Debug)]
40pub struct XLookupFn;
41
42impl Function for XLookupFn {
105 func_caps!(PURE, LOOKUP);
106 fn name(&self) -> &'static str {
107 "XLOOKUP"
108 }
109 fn min_args(&self) -> usize {
110 3
111 }
112 fn variadic(&self) -> bool {
113 true
114 }
115 fn arg_schema(&self) -> &'static [ArgSchema] {
116 use once_cell::sync::Lazy;
117 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
118 vec![
119 ArgSchema {
121 kinds: smallvec::smallvec![ArgKind::Any],
122 required: true,
123 by_ref: false,
124 shape: ShapeKind::Scalar,
125 coercion: CoercionPolicy::None,
126 max: None,
127 repeating: None,
128 default: None,
129 },
130 ArgSchema {
132 kinds: smallvec::smallvec![ArgKind::Range],
133 required: true,
134 by_ref: true,
135 shape: ShapeKind::Range,
136 coercion: CoercionPolicy::None,
137 max: None,
138 repeating: None,
139 default: None,
140 },
141 ArgSchema {
143 kinds: smallvec::smallvec![ArgKind::Range],
144 required: true,
145 by_ref: true,
146 shape: ShapeKind::Range,
147 coercion: CoercionPolicy::None,
148 max: None,
149 repeating: None,
150 default: None,
151 },
152 ArgSchema {
154 kinds: smallvec::smallvec![ArgKind::Any],
155 required: false,
156 by_ref: false,
157 shape: ShapeKind::Scalar,
158 coercion: CoercionPolicy::None,
159 max: None,
160 repeating: None,
161 default: None,
162 },
163 ArgSchema {
165 kinds: smallvec::smallvec![ArgKind::Number],
166 required: false,
167 by_ref: false,
168 shape: ShapeKind::Scalar,
169 coercion: CoercionPolicy::NumberLenientText,
170 max: None,
171 repeating: None,
172 default: Some(LiteralValue::Int(0)),
173 },
174 ArgSchema {
176 kinds: smallvec::smallvec![ArgKind::Number],
177 required: false,
178 by_ref: false,
179 shape: ShapeKind::Scalar,
180 coercion: CoercionPolicy::NumberLenientText,
181 max: None,
182 repeating: None,
183 default: Some(LiteralValue::Int(1)),
184 },
185 ]
186 });
187 &SCHEMA
188 }
189 fn eval<'a, 'b, 'c>(
190 &self,
191 args: &'c [ArgumentHandle<'a, 'b>],
192 _ctx: &dyn FunctionContext<'b>,
193 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
194 if args.len() < 3 {
195 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
196 ExcelError::new(ExcelErrorKind::Value),
197 )));
198 }
199 let lookup_value = args[0].value()?.into_literal();
200 if let LiteralValue::Error(ref e) = lookup_value {
201 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
202 e.clone(),
203 )));
204 }
205 let lookup_view = match args[1].range_view() {
206 Ok(v) => v,
207 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
208 };
209 let ret_view = match args[2].range_view() {
210 Ok(v) => v,
211 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
212 };
213
214 let (lookup_rows, lookup_cols) = lookup_view.dims();
215 let (ret_rows, ret_cols) = ret_view.dims();
216
217 let vertical = if lookup_cols == 1 {
222 true
223 } else if lookup_rows == 1 {
224 false
225 } else if lookup_rows == 0 && lookup_cols == 0 {
226 if ret_cols == 1 {
227 true
228 } else if ret_rows == 1 {
229 false
230 } else {
231 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
232 ExcelError::new(ExcelErrorKind::Value),
233 )));
234 }
235 } else {
236 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
237 ExcelError::new(ExcelErrorKind::Value),
238 )));
239 };
240
241 let lookup_len = {
242 let raw = if vertical { lookup_rows } else { lookup_cols };
243 if raw == 0 {
244 if vertical { ret_rows } else { ret_cols }
245 } else {
246 raw
247 }
248 };
249
250 if lookup_len == 0 {
251 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
252 ExcelError::new(ExcelErrorKind::Na),
253 )));
254 }
255
256 let match_mode = if args.len() >= 5 {
257 match args[4].value()?.into_literal() {
258 LiteralValue::Int(i) => i,
259 LiteralValue::Number(n) => n as i64,
260 _ => 0,
261 }
262 } else {
263 0
264 };
265 let search_mode = if args.len() >= 6 {
266 match args[5].value()?.into_literal() {
267 LiteralValue::Int(i) => i,
268 LiteralValue::Number(n) => n as i64,
269 _ => 1,
270 }
271 } else {
272 1
273 };
274
275 let wildcard = match_mode == 2;
276
277 let mut found: Option<usize> = None;
278 let needle = lookup_value;
279 let prepared_matcher = PreparedLookupMatcher::new(&needle, wildcard);
280 if match_mode == 0 || wildcard {
281 if search_mode == 1 && lookup_rows > 0 && lookup_cols > 0 {
282 found =
283 super::lookup_utils::find_exact_index_in_view(&lookup_view, &needle, wildcard)?;
284 } else if search_mode == -1 {
285 for i in (0..lookup_len).rev() {
286 let cand = if vertical {
287 lookup_view.get_cell(i, 0)
288 } else {
289 lookup_view.get_cell(0, i)
290 };
291 if prepared_matcher.matches(&cand) {
292 found = Some(i);
293 break;
294 }
295 }
296 } else {
297 for i in 0..lookup_len {
300 let cand = if vertical {
301 lookup_view.get_cell(i, 0)
302 } else {
303 lookup_view.get_cell(0, i)
304 };
305 if prepared_matcher.matches(&cand) {
306 found = Some(i);
307 break;
308 }
309 }
310 }
311 } else if match_mode == -1 || match_mode == 1 {
312 let needle_num = value_to_f64_lenient(&needle);
313 let mut best_idx: Option<usize> = None;
314 let mut best_val: f64 = if match_mode == -1 {
315 f64::NEG_INFINITY
316 } else {
317 f64::INFINITY
318 };
319
320 let mut prev: Option<LiteralValue> = None;
321 for i in 0..lookup_len {
322 let cand = if vertical {
323 lookup_view.get_cell(i, 0)
324 } else {
325 lookup_view.get_cell(0, i)
326 };
327
328 if let Some(p) = prev.as_ref() {
329 let sorted_ok = cmp_for_lookup(p, &cand).is_some_and(|o| o <= 0);
330 if !sorted_ok {
331 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
332 ExcelError::new(ExcelErrorKind::Na),
333 )));
334 }
335 }
336 prev = Some(cand.clone());
337
338 if cmp_for_lookup(&cand, &needle).is_some_and(|o| o == 0) {
339 found = Some(i);
340 break;
341 }
342
343 if let (Some(nn), Some(vv)) = (needle_num, value_to_f64_lenient(&cand)) {
344 if match_mode == -1 {
345 if vv <= nn && vv > best_val {
346 best_val = vv;
347 best_idx = Some(i);
348 }
349 } else if vv >= nn && vv < best_val {
350 best_val = vv;
351 best_idx = Some(i);
352 }
353 }
354 }
355
356 if found.is_none() {
357 found = best_idx;
358 }
359 } else {
360 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
361 ExcelError::new(ExcelErrorKind::Value),
362 )));
363 }
364
365 if let Some(idx) = found {
366 let (ret_rows, ret_cols) = ret_view.dims();
367 if ret_rows == 0 || ret_cols == 0 {
368 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Empty));
369 }
370
371 if vertical {
372 if ret_cols == 1 {
373 return Ok(crate::traits::CalcValue::Scalar(ret_view.get_cell(idx, 0)));
374 }
375 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(ret_cols);
376 for c in 0..ret_cols {
377 row_out.push(ret_view.get_cell(idx, c));
378 }
379 return Ok(crate::traits::CalcValue::Range(
380 crate::engine::range_view::RangeView::from_owned_rows(
381 vec![row_out],
382 _ctx.date_system(),
383 ),
384 ));
385 }
386
387 if ret_rows == 1 {
389 return Ok(crate::traits::CalcValue::Scalar(ret_view.get_cell(0, idx)));
390 }
391
392 let mut col_out: Vec<Vec<LiteralValue>> = Vec::with_capacity(ret_rows);
393 for r in 0..ret_rows {
394 col_out.push(vec![ret_view.get_cell(r, idx)]);
395 }
396 return Ok(crate::traits::CalcValue::Range(
397 crate::engine::range_view::RangeView::from_owned_rows(col_out, _ctx.date_system()),
398 ));
399 }
400
401 if args.len() >= 4 {
402 return args[3].value();
403 }
404 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
405 ExcelError::new(ExcelErrorKind::Na),
406 )))
407 }
408}
409
410#[derive(Debug)]
413pub struct XMatchFn;
414impl Function for XMatchFn {
468 func_caps!(PURE, LOOKUP);
469 fn name(&self) -> &'static str {
470 "XMATCH"
471 }
472 fn min_args(&self) -> usize {
473 2
474 }
475 fn variadic(&self) -> bool {
476 true
477 }
478 fn arg_schema(&self) -> &'static [ArgSchema] {
479 use once_cell::sync::Lazy;
480 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
481 vec![
482 ArgSchema {
484 kinds: smallvec::smallvec![ArgKind::Any],
485 required: true,
486 by_ref: false,
487 shape: ShapeKind::Scalar,
488 coercion: CoercionPolicy::None,
489 max: None,
490 repeating: None,
491 default: None,
492 },
493 ArgSchema {
495 kinds: smallvec::smallvec![ArgKind::Range],
496 required: true,
497 by_ref: true,
498 shape: ShapeKind::Range,
499 coercion: CoercionPolicy::None,
500 max: None,
501 repeating: None,
502 default: None,
503 },
504 ArgSchema {
507 kinds: smallvec::smallvec![ArgKind::Number],
508 required: false,
509 by_ref: false,
510 shape: ShapeKind::Scalar,
511 coercion: CoercionPolicy::NumberLenientText,
512 max: None,
513 repeating: None,
514 default: Some(LiteralValue::Int(0)),
515 },
516 ArgSchema {
519 kinds: smallvec::smallvec![ArgKind::Number],
520 required: false,
521 by_ref: false,
522 shape: ShapeKind::Scalar,
523 coercion: CoercionPolicy::NumberLenientText,
524 max: None,
525 repeating: None,
526 default: Some(LiteralValue::Int(1)),
527 },
528 ]
529 });
530 &SCHEMA
531 }
532 fn eval<'a, 'b, 'c>(
533 &self,
534 args: &'c [ArgumentHandle<'a, 'b>],
535 _ctx: &dyn FunctionContext<'b>,
536 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
537 if args.len() < 2 {
538 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
539 ExcelError::new(ExcelErrorKind::Value),
540 )));
541 }
542 let lookup_value = args[0].value()?.into_literal();
543 if let LiteralValue::Error(ref e) = lookup_value {
544 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
545 e.clone(),
546 )));
547 }
548 let lookup_view = match args[1].range_view() {
549 Ok(v) => v,
550 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
551 };
552
553 let (lookup_rows, lookup_cols) = lookup_view.dims();
554
555 let vertical = if lookup_cols == 1 {
557 true
558 } else if lookup_rows == 1 {
559 false
560 } else if lookup_rows == 0 || lookup_cols == 0 {
561 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
562 ExcelError::new(ExcelErrorKind::Na),
563 )));
564 } else {
565 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
566 ExcelError::new(ExcelErrorKind::Value),
567 )));
568 };
569
570 let lookup_len = if vertical { lookup_rows } else { lookup_cols };
571
572 if lookup_len == 0 {
573 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
574 ExcelError::new(ExcelErrorKind::Na),
575 )));
576 }
577
578 let match_mode = if args.len() >= 3 {
579 match args[2].value()?.into_literal() {
580 LiteralValue::Int(i) => i,
581 LiteralValue::Number(n) => n as i64,
582 _ => 0,
583 }
584 } else {
585 0
586 };
587 let search_mode = if args.len() >= 4 {
588 match args[3].value()?.into_literal() {
589 LiteralValue::Int(i) => i,
590 LiteralValue::Number(n) => n as i64,
591 _ => 1,
592 }
593 } else {
594 1
595 };
596
597 let wildcard = match_mode == 2;
598 let needle = lookup_value;
599 let prepared_matcher = PreparedLookupMatcher::new(&needle, wildcard);
600
601 let mut found: Option<usize> = None;
602
603 if match_mode == 0 || wildcard {
604 if search_mode == 1 || search_mode == 2 {
606 if lookup_rows > 0 && lookup_cols > 0 {
608 found = super::lookup_utils::find_exact_index_in_view(
609 &lookup_view,
610 &needle,
611 wildcard,
612 )?;
613 }
614 } else if search_mode == -1 || search_mode == -2 {
615 for i in (0..lookup_len).rev() {
617 let cand = if vertical {
618 lookup_view.get_cell(i, 0)
619 } else {
620 lookup_view.get_cell(0, i)
621 };
622 if prepared_matcher.matches(&cand) {
623 found = Some(i);
624 break;
625 }
626 }
627 } else {
628 for i in 0..lookup_len {
630 let cand = if vertical {
631 lookup_view.get_cell(i, 0)
632 } else {
633 lookup_view.get_cell(0, i)
634 };
635 if prepared_matcher.matches(&cand) {
636 found = Some(i);
637 break;
638 }
639 }
640 }
641 } else if match_mode == -1 || match_mode == 1 {
642 let needle_num = value_to_f64_lenient(&needle);
644 let mut best_idx: Option<usize> = None;
645 let mut best_val: f64 = if match_mode == -1 {
646 f64::NEG_INFINITY
647 } else {
648 f64::INFINITY
649 };
650
651 let use_reverse = search_mode == -1 || search_mode == -2;
653 let indices: Box<dyn Iterator<Item = usize>> = if use_reverse {
654 Box::new((0..lookup_len).rev())
655 } else {
656 Box::new(0..lookup_len)
657 };
658
659 if (search_mode == 2 || search_mode == -2) && match_mode != 0 {
662 let ascending = search_mode == 2;
663 let mut prev: Option<LiteralValue> = None;
664 for i in 0..lookup_len {
665 let cand = if vertical {
666 lookup_view.get_cell(i, 0)
667 } else {
668 lookup_view.get_cell(0, i)
669 };
670 if let Some(p) = prev.as_ref() {
671 let sorted_ok = if ascending {
672 cmp_for_lookup(p, &cand).is_some_and(|o| o <= 0)
673 } else {
674 cmp_for_lookup(p, &cand).is_some_and(|o| o >= 0)
675 };
676 if !sorted_ok {
677 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
678 ExcelError::new(ExcelErrorKind::Na),
679 )));
680 }
681 }
682 prev = Some(cand);
683 }
684 }
685
686 for i in indices {
687 let cand = if vertical {
688 lookup_view.get_cell(i, 0)
689 } else {
690 lookup_view.get_cell(0, i)
691 };
692
693 if cmp_for_lookup(&cand, &needle).is_some_and(|o| o == 0) {
694 found = Some(i);
695 break;
696 }
697
698 if let (Some(nn), Some(vv)) = (needle_num, value_to_f64_lenient(&cand)) {
699 if match_mode == -1 {
700 if vv <= nn && vv > best_val {
702 best_val = vv;
703 best_idx = Some(i);
704 }
705 } else {
706 if vv >= nn && vv < best_val {
708 best_val = vv;
709 best_idx = Some(i);
710 }
711 }
712 }
713 }
714
715 if found.is_none() {
716 found = best_idx;
717 }
718 } else {
719 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
720 ExcelError::new(ExcelErrorKind::Value),
721 )));
722 }
723
724 match found {
725 Some(idx) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
726 (idx + 1) as i64,
727 ))),
728 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
729 ExcelError::new(ExcelErrorKind::Na),
730 ))),
731 }
732 }
733}
734
735#[derive(Debug)]
738pub struct SortFn;
739impl Function for SortFn {
799 func_caps!(PURE);
800 fn name(&self) -> &'static str {
801 "SORT"
802 }
803 fn min_args(&self) -> usize {
804 1
805 }
806 fn variadic(&self) -> bool {
807 true
808 }
809 fn arg_schema(&self) -> &'static [ArgSchema] {
810 use once_cell::sync::Lazy;
811 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
812 vec![
813 ArgSchema {
815 kinds: smallvec::smallvec![ArgKind::Range],
816 required: true,
817 by_ref: true,
818 shape: ShapeKind::Range,
819 coercion: CoercionPolicy::None,
820 max: None,
821 repeating: None,
822 default: None,
823 },
824 ArgSchema {
826 kinds: smallvec::smallvec![ArgKind::Number],
827 required: false,
828 by_ref: false,
829 shape: ShapeKind::Scalar,
830 coercion: CoercionPolicy::NumberLenientText,
831 max: None,
832 repeating: None,
833 default: Some(LiteralValue::Int(1)),
834 },
835 ArgSchema {
837 kinds: smallvec::smallvec![ArgKind::Number],
838 required: false,
839 by_ref: false,
840 shape: ShapeKind::Scalar,
841 coercion: CoercionPolicy::NumberLenientText,
842 max: None,
843 repeating: None,
844 default: Some(LiteralValue::Int(1)),
845 },
846 ArgSchema {
848 kinds: smallvec::smallvec![ArgKind::Logical],
849 required: false,
850 by_ref: false,
851 shape: ShapeKind::Scalar,
852 coercion: CoercionPolicy::Logical,
853 max: None,
854 repeating: None,
855 default: Some(LiteralValue::Boolean(false)),
856 },
857 ]
858 });
859 &SCHEMA
860 }
861 fn eval<'a, 'b, 'c>(
862 &self,
863 args: &'c [ArgumentHandle<'a, 'b>],
864 _ctx: &dyn FunctionContext<'b>,
865 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
866 let view = match args[0].range_view() {
867 Ok(v) => v,
868 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
869 };
870 let (rows, cols) = view.dims();
871 if rows == 0 || cols == 0 {
872 return Ok(crate::traits::CalcValue::Range(
873 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
874 ));
875 }
876
877 let sort_index = if args.len() >= 2 {
878 match args[1].value()?.into_literal() {
879 LiteralValue::Int(i) => i,
880 LiteralValue::Number(n) => n as i64,
881 _ => 1,
882 }
883 } else {
884 1
885 };
886
887 let sort_order = if args.len() >= 3 {
888 match args[2].value()?.into_literal() {
889 LiteralValue::Int(i) => i,
890 LiteralValue::Number(n) => n as i64,
891 _ => 1,
892 }
893 } else {
894 1
895 };
896
897 let by_col = if args.len() >= 4 {
898 matches!(args[3].value()?.into_literal(), LiteralValue::Boolean(true))
899 } else {
900 false
901 };
902
903 let ascending = sort_order >= 0;
904
905 if by_col {
906 let sort_row_idx = (sort_index - 1).max(0) as usize;
908 if sort_row_idx >= rows {
909 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
910 ExcelError::new(ExcelErrorKind::Value),
911 )));
912 }
913
914 let mut columns: Vec<(usize, Vec<LiteralValue>)> = Vec::with_capacity(cols);
916 for c in 0..cols {
917 let mut col_vals: Vec<LiteralValue> = Vec::with_capacity(rows);
918 for r in 0..rows {
919 col_vals.push(view.get_cell(r, c));
920 }
921 columns.push((c, col_vals));
922 }
923
924 columns.sort_by(|a, b| {
926 let val_a = &a.1[sort_row_idx];
927 let val_b = &b.1[sort_row_idx];
928 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
929 if ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) }
930 });
931
932 let mut out: Vec<Vec<LiteralValue>> = vec![Vec::with_capacity(cols); rows];
934 for (_orig_idx, col_vals) in columns {
935 for (r, val) in col_vals.into_iter().enumerate() {
936 out[r].push(val);
937 }
938 }
939
940 Ok(collapse_if_scalar(out, _ctx.date_system()))
941 } else {
942 let sort_col_idx = (sort_index - 1).max(0) as usize;
944 if sort_col_idx >= cols {
945 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
946 ExcelError::new(ExcelErrorKind::Value),
947 )));
948 }
949
950 let mut row_data: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
952 for r in 0..rows {
953 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
954 for c in 0..cols {
955 row_vals.push(view.get_cell(r, c));
956 }
957 row_data.push(row_vals);
958 }
959
960 row_data.sort_by(|a, b| {
962 let val_a = &a[sort_col_idx];
963 let val_b = &b[sort_col_idx];
964 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
965 if ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) }
966 });
967
968 Ok(collapse_if_scalar(row_data, _ctx.date_system()))
969 }
970 }
971}
972
973#[derive(Debug)]
976pub struct SortByFn;
977impl Function for SortByFn {
1037 func_caps!(PURE);
1038 fn name(&self) -> &'static str {
1039 "SORTBY"
1040 }
1041 fn min_args(&self) -> usize {
1042 2
1043 }
1044 fn variadic(&self) -> bool {
1045 true
1046 }
1047 fn arg_schema(&self) -> &'static [ArgSchema] {
1048 use once_cell::sync::Lazy;
1049 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1050 vec![
1051 ArgSchema {
1053 kinds: smallvec::smallvec![ArgKind::Range],
1054 required: true,
1055 by_ref: true,
1056 shape: ShapeKind::Range,
1057 coercion: CoercionPolicy::None,
1058 max: None,
1059 repeating: None,
1060 default: None,
1061 },
1062 ArgSchema {
1064 kinds: smallvec::smallvec![ArgKind::Range],
1065 required: true,
1066 by_ref: true,
1067 shape: ShapeKind::Range,
1068 coercion: CoercionPolicy::None,
1069 max: None,
1070 repeating: None,
1071 default: None,
1072 },
1073 ArgSchema {
1075 kinds: smallvec::smallvec![ArgKind::Number],
1076 required: false,
1077 by_ref: false,
1078 shape: ShapeKind::Scalar,
1079 coercion: CoercionPolicy::NumberLenientText,
1080 max: None,
1081 repeating: None,
1082 default: Some(LiteralValue::Int(1)),
1083 },
1084 ]
1086 });
1087 &SCHEMA
1088 }
1089 fn eval<'a, 'b, 'c>(
1090 &self,
1091 args: &'c [ArgumentHandle<'a, 'b>],
1092 _ctx: &dyn FunctionContext<'b>,
1093 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1094 if args.len() < 2 {
1095 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1096 ExcelError::new(ExcelErrorKind::Value),
1097 )));
1098 }
1099
1100 let view = match args[0].range_view() {
1101 Ok(v) => v,
1102 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1103 };
1104 let (rows, cols) = view.dims();
1105 if rows == 0 || cols == 0 {
1106 return Ok(crate::traits::CalcValue::Range(
1107 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
1108 ));
1109 }
1110
1111 let mut sort_criteria: Vec<(Vec<LiteralValue>, bool)> = Vec::new();
1114 let mut arg_idx = 1;
1115
1116 while arg_idx < args.len() {
1117 let by_view = match args[arg_idx].range_view() {
1119 Ok(v) => v,
1120 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1121 };
1122 let (by_rows, by_cols) = by_view.dims();
1123
1124 let by_values: Vec<LiteralValue> = if by_cols == 1 {
1126 if by_rows != rows {
1127 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1128 ExcelError::new(ExcelErrorKind::Value),
1129 )));
1130 }
1131 (0..by_rows).map(|r| by_view.get_cell(r, 0)).collect()
1132 } else if by_rows == 1 {
1133 if by_cols != rows {
1134 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1135 ExcelError::new(ExcelErrorKind::Value),
1136 )));
1137 }
1138 (0..by_cols).map(|c| by_view.get_cell(0, c)).collect()
1139 } else {
1140 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1141 ExcelError::new(ExcelErrorKind::Value),
1142 )));
1143 };
1144
1145 arg_idx += 1;
1146
1147 let ascending = if arg_idx < args.len() {
1149 match args[arg_idx].value() {
1152 Ok(v) => {
1153 let lit = v.into_literal();
1154 match lit {
1155 LiteralValue::Int(i) => {
1156 arg_idx += 1;
1157 i >= 0
1158 }
1159 LiteralValue::Number(n) => {
1160 arg_idx += 1;
1161 n >= 0.0
1162 }
1163 _ => true, }
1165 }
1166 Err(_) => true,
1167 }
1168 } else {
1169 true
1170 };
1171
1172 sort_criteria.push((by_values, ascending));
1173 }
1174
1175 if sort_criteria.is_empty() {
1176 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1177 ExcelError::new(ExcelErrorKind::Value),
1178 )));
1179 }
1180
1181 let mut indexed_rows: Vec<(usize, Vec<LiteralValue>)> = Vec::with_capacity(rows);
1183 for r in 0..rows {
1184 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
1185 for c in 0..cols {
1186 row_vals.push(view.get_cell(r, c));
1187 }
1188 indexed_rows.push((r, row_vals));
1189 }
1190
1191 indexed_rows.sort_by(|a, b| {
1193 for (by_values, ascending) in &sort_criteria {
1194 let val_a = &by_values[a.0];
1195 let val_b = &by_values[b.0];
1196 let cmp = cmp_for_lookup(val_a, val_b).unwrap_or(0);
1197 if cmp != 0 {
1198 return if *ascending { cmp.cmp(&0) } else { 0.cmp(&cmp) };
1199 }
1200 }
1201 std::cmp::Ordering::Equal
1202 });
1203
1204 let out: Vec<Vec<LiteralValue>> = indexed_rows.into_iter().map(|(_, row)| row).collect();
1206
1207 Ok(collapse_if_scalar(out, _ctx.date_system()))
1208 }
1209}
1210
1211#[derive(Debug)]
1214pub struct RandArrayFn;
1215impl Function for RandArrayFn {
1260 fn caps(&self) -> crate::function::FnCaps {
1262 crate::function::FnCaps::empty()
1263 }
1264 fn name(&self) -> &'static str {
1265 "RANDARRAY"
1266 }
1267 fn min_args(&self) -> usize {
1268 0
1269 }
1270 fn variadic(&self) -> bool {
1271 true
1272 }
1273 fn arg_schema(&self) -> &'static [ArgSchema] {
1274 use once_cell::sync::Lazy;
1275 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1276 vec![
1277 ArgSchema {
1279 kinds: smallvec::smallvec![ArgKind::Number],
1280 required: false,
1281 by_ref: false,
1282 shape: ShapeKind::Scalar,
1283 coercion: CoercionPolicy::NumberLenientText,
1284 max: None,
1285 repeating: None,
1286 default: Some(LiteralValue::Int(1)),
1287 },
1288 ArgSchema {
1290 kinds: smallvec::smallvec![ArgKind::Number],
1291 required: false,
1292 by_ref: false,
1293 shape: ShapeKind::Scalar,
1294 coercion: CoercionPolicy::NumberLenientText,
1295 max: None,
1296 repeating: None,
1297 default: Some(LiteralValue::Int(1)),
1298 },
1299 ArgSchema {
1301 kinds: smallvec::smallvec![ArgKind::Number],
1302 required: false,
1303 by_ref: false,
1304 shape: ShapeKind::Scalar,
1305 coercion: CoercionPolicy::NumberLenientText,
1306 max: None,
1307 repeating: None,
1308 default: Some(LiteralValue::Int(0)),
1309 },
1310 ArgSchema {
1312 kinds: smallvec::smallvec![ArgKind::Number],
1313 required: false,
1314 by_ref: false,
1315 shape: ShapeKind::Scalar,
1316 coercion: CoercionPolicy::NumberLenientText,
1317 max: None,
1318 repeating: None,
1319 default: Some(LiteralValue::Int(1)),
1320 },
1321 ArgSchema {
1323 kinds: smallvec::smallvec![ArgKind::Logical],
1324 required: false,
1325 by_ref: false,
1326 shape: ShapeKind::Scalar,
1327 coercion: CoercionPolicy::Logical,
1328 max: None,
1329 repeating: None,
1330 default: Some(LiteralValue::Boolean(false)),
1331 },
1332 ]
1333 });
1334 &SCHEMA
1335 }
1336 fn eval<'a, 'b, 'c>(
1337 &self,
1338 args: &'c [ArgumentHandle<'a, 'b>],
1339 ctx: &dyn FunctionContext<'b>,
1340 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1341 use rand::Rng;
1342
1343 let num = |a: &ArgumentHandle| -> Result<f64, ExcelError> {
1345 Ok(match a.value()?.into_literal() {
1346 LiteralValue::Int(i) => i as f64,
1347 LiteralValue::Number(n) => n,
1348 LiteralValue::Error(e) => return Err(e),
1349 _other => {
1350 return Err(ExcelError::new(ExcelErrorKind::Value));
1351 }
1352 })
1353 };
1354
1355 let rows = if !args.is_empty() {
1356 num(&args[0])? as i64
1357 } else {
1358 1
1359 };
1360 let cols = if args.len() >= 2 {
1361 num(&args[1])? as i64
1362 } else {
1363 1
1364 };
1365 let min_val = if args.len() >= 3 { num(&args[2])? } else { 0.0 };
1366 let max_val = if args.len() >= 4 { num(&args[3])? } else { 1.0 };
1367 let whole_number = if args.len() >= 5 {
1368 matches!(args[4].value()?.into_literal(), LiteralValue::Boolean(true))
1369 } else {
1370 false
1371 };
1372
1373 if rows <= 0 || cols <= 0 {
1375 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1376 ExcelError::new(ExcelErrorKind::Value),
1377 )));
1378 }
1379
1380 if whole_number && min_val > max_val {
1382 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1383 ExcelError::new(ExcelErrorKind::Value),
1384 )));
1385 }
1386
1387 let mut rng = ctx.rng_for_current(self.function_salt());
1388 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows as usize);
1389
1390 for _r in 0..rows {
1391 let mut row_vec: Vec<LiteralValue> = Vec::with_capacity(cols as usize);
1392 for _c in 0..cols {
1393 let value = if whole_number {
1394 let min_int = min_val.ceil() as i64;
1396 let max_int = max_val.floor() as i64;
1397 if min_int > max_int {
1398 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1399 ExcelError::new(ExcelErrorKind::Value),
1400 )));
1401 }
1402 let rand_int = rng.gen_range(min_int..=max_int);
1403 LiteralValue::Int(rand_int)
1404 } else {
1405 let rand_float = rng.r#gen::<f64>() * (max_val - min_val) + min_val;
1407 LiteralValue::Number(rand_float)
1408 };
1409 row_vec.push(value);
1410 }
1411 out.push(row_vec);
1412 }
1413
1414 Ok(collapse_if_scalar(out, ctx.date_system()))
1415 }
1416}
1417
1418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1422enum GroupAggregation {
1423 Sum,
1424 Average,
1425 Count,
1426 CountA,
1427 Max,
1428 Min,
1429 Product,
1430 StDev,
1431 StDevP,
1432 Var,
1433 VarP,
1434 Median,
1435}
1436
1437impl GroupAggregation {
1438 fn from_literal(val: &LiteralValue) -> Option<Self> {
1439 match val {
1440 LiteralValue::Text(s) => Self::from_str(s),
1441 LiteralValue::Int(n) => Self::from_num(*n as i32),
1442 LiteralValue::Number(n) => Self::from_num(*n as i32),
1443 _ => None,
1444 }
1445 }
1446
1447 fn from_str(s: &str) -> Option<Self> {
1448 let upper = s.to_ascii_uppercase();
1449 match upper.as_str() {
1450 "SUM" => Some(Self::Sum),
1451 "AVERAGE" | "AVG" => Some(Self::Average),
1452 "COUNT" => Some(Self::Count),
1453 "COUNTA" => Some(Self::CountA),
1454 "MAX" => Some(Self::Max),
1455 "MIN" => Some(Self::Min),
1456 "PRODUCT" => Some(Self::Product),
1457 "STDEV" | "STDEV.S" => Some(Self::StDev),
1458 "STDEVP" | "STDEV.P" => Some(Self::StDevP),
1459 "VAR" | "VAR.S" => Some(Self::Var),
1460 "VARP" | "VAR.P" => Some(Self::VarP),
1461 "MEDIAN" => Some(Self::Median),
1462 _ => None,
1463 }
1464 }
1465
1466 fn from_num(n: i32) -> Option<Self> {
1467 match n {
1469 1 => Some(Self::Average),
1470 2 => Some(Self::Count),
1471 3 => Some(Self::CountA),
1472 4 => Some(Self::Max),
1473 5 => Some(Self::Min),
1474 6 => Some(Self::Product),
1475 7 => Some(Self::StDev),
1476 8 => Some(Self::StDevP),
1477 9 => Some(Self::Sum),
1478 10 => Some(Self::Var),
1479 11 => Some(Self::VarP),
1480 12 => Some(Self::Median),
1481 _ => None,
1482 }
1483 }
1484
1485 fn apply(&self, values: &[f64]) -> f64 {
1486 if values.is_empty() {
1487 return match self {
1488 Self::Count | Self::CountA => 0.0,
1489 Self::Sum | Self::Product => 0.0,
1490 _ => f64::NAN,
1491 };
1492 }
1493
1494 match self {
1495 Self::Sum => values.iter().sum(),
1496 Self::Average => values.iter().sum::<f64>() / values.len() as f64,
1497 Self::Count | Self::CountA => values.len() as f64,
1498 Self::Max => values.iter().copied().fold(f64::NEG_INFINITY, f64::max),
1499 Self::Min => values.iter().copied().fold(f64::INFINITY, f64::min),
1500 Self::Product => values.iter().product(),
1501 Self::StDev => {
1502 if values.len() < 2 {
1503 return f64::NAN;
1504 }
1505 let mean = values.iter().sum::<f64>() / values.len() as f64;
1506 let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>()
1507 / (values.len() - 1) as f64;
1508 variance.sqrt()
1509 }
1510 Self::StDevP => {
1511 let mean = values.iter().sum::<f64>() / values.len() as f64;
1512 let variance =
1513 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
1514 variance.sqrt()
1515 }
1516 Self::Var => {
1517 if values.len() < 2 {
1518 return f64::NAN;
1519 }
1520 let mean = values.iter().sum::<f64>() / values.len() as f64;
1521 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (values.len() - 1) as f64
1522 }
1523 Self::VarP => {
1524 let mean = values.iter().sum::<f64>() / values.len() as f64;
1525 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64
1526 }
1527 Self::Median => {
1528 let mut sorted = values.to_vec();
1529 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1530 let mid = sorted.len() / 2;
1531 if sorted.len().is_multiple_of(2) {
1532 (sorted[mid - 1] + sorted[mid]) / 2.0
1533 } else {
1534 sorted[mid]
1535 }
1536 }
1537 }
1538 }
1539}
1540
1541fn literal_to_group_key(v: &LiteralValue) -> String {
1543 match v {
1544 LiteralValue::Text(s) => s.clone(),
1545 LiteralValue::Int(i) => i.to_string(),
1546 LiteralValue::Number(n) => format!("{:.10}", n),
1547 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
1548 LiteralValue::Empty => String::new(),
1549 LiteralValue::Error(e) => format!("#{:?}", e.kind),
1550 LiteralValue::Array(_) => "[Array]".to_string(),
1551 LiteralValue::Date(d) => d.to_string(),
1552 LiteralValue::DateTime(dt) => dt.to_string(),
1553 LiteralValue::Time(t) => t.to_string(),
1554 LiteralValue::Duration(d) => format!("{:?}", d),
1555 LiteralValue::Pending => "[Pending]".to_string(),
1556 }
1557}
1558
1559fn literal_to_num_opt(v: &LiteralValue) -> Option<f64> {
1561 match v {
1562 LiteralValue::Number(n) => Some(*n),
1563 LiteralValue::Int(i) => Some(*i as f64),
1564 LiteralValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
1565 _ => None,
1566 }
1567}
1568
1569#[derive(Debug)]
1570pub struct GroupByFn;
1571
1572impl Function for GroupByFn {
1636 func_caps!(PURE);
1637 fn name(&self) -> &'static str {
1638 "GROUPBY"
1639 }
1640 fn min_args(&self) -> usize {
1641 3
1642 }
1643 fn variadic(&self) -> bool {
1644 true
1645 }
1646 fn arg_schema(&self) -> &'static [ArgSchema] {
1647 use once_cell::sync::Lazy;
1648 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
1649 vec![
1650 ArgSchema {
1652 kinds: smallvec::smallvec![ArgKind::Range],
1653 required: true,
1654 by_ref: true,
1655 shape: ShapeKind::Range,
1656 coercion: CoercionPolicy::None,
1657 max: None,
1658 repeating: None,
1659 default: None,
1660 },
1661 ArgSchema {
1663 kinds: smallvec::smallvec![ArgKind::Range],
1664 required: true,
1665 by_ref: true,
1666 shape: ShapeKind::Range,
1667 coercion: CoercionPolicy::None,
1668 max: None,
1669 repeating: None,
1670 default: None,
1671 },
1672 ArgSchema {
1674 kinds: smallvec::smallvec![ArgKind::Any],
1675 required: true,
1676 by_ref: false,
1677 shape: ShapeKind::Scalar,
1678 coercion: CoercionPolicy::None,
1679 max: None,
1680 repeating: None,
1681 default: None,
1682 },
1683 ArgSchema {
1685 kinds: smallvec::smallvec![ArgKind::Number],
1686 required: false,
1687 by_ref: false,
1688 shape: ShapeKind::Scalar,
1689 coercion: CoercionPolicy::NumberLenientText,
1690 max: None,
1691 repeating: None,
1692 default: Some(LiteralValue::Int(1)),
1693 },
1694 ArgSchema {
1696 kinds: smallvec::smallvec![ArgKind::Number],
1697 required: false,
1698 by_ref: false,
1699 shape: ShapeKind::Scalar,
1700 coercion: CoercionPolicy::NumberLenientText,
1701 max: None,
1702 repeating: None,
1703 default: Some(LiteralValue::Int(0)),
1704 },
1705 ArgSchema {
1707 kinds: smallvec::smallvec![ArgKind::Number],
1708 required: false,
1709 by_ref: false,
1710 shape: ShapeKind::Scalar,
1711 coercion: CoercionPolicy::NumberLenientText,
1712 max: None,
1713 repeating: None,
1714 default: Some(LiteralValue::Int(0)),
1715 },
1716 ]
1717 });
1718 &SCHEMA
1719 }
1720
1721 fn eval<'a, 'b, 'c>(
1722 &self,
1723 args: &'c [ArgumentHandle<'a, 'b>],
1724 _ctx: &dyn FunctionContext<'b>,
1725 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1726 if args.len() < 3 {
1727 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1728 ExcelError::new(ExcelErrorKind::Value),
1729 )));
1730 }
1731
1732 let row_fields_view = match args[0].range_view() {
1734 Ok(v) => v,
1735 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1736 };
1737 let values_view = match args[1].range_view() {
1738 Ok(v) => v,
1739 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1740 };
1741
1742 let agg_val = args[2].value()?.into_literal();
1744 let aggregation = match GroupAggregation::from_literal(&agg_val) {
1745 Some(a) => a,
1746 None => {
1747 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1748 ExcelError::new(ExcelErrorKind::Value)
1749 .with_message("Invalid aggregation function"),
1750 )));
1751 }
1752 };
1753
1754 let field_headers = if args.len() >= 4 {
1756 match args[3].value()?.into_literal() {
1757 LiteralValue::Int(i) => i as i32,
1758 LiteralValue::Number(n) => n as i32,
1759 _ => 1,
1760 }
1761 } else {
1762 1
1763 };
1764
1765 let total_depth = if args.len() >= 5 {
1766 match args[4].value()?.into_literal() {
1767 LiteralValue::Int(i) => i as i32,
1768 LiteralValue::Number(n) => n as i32,
1769 _ => 0,
1770 }
1771 } else {
1772 0
1773 };
1774
1775 let sort_order = if args.len() >= 6 {
1776 match args[5].value()?.into_literal() {
1777 LiteralValue::Int(i) => i as i32,
1778 LiteralValue::Number(n) => n as i32,
1779 _ => 0,
1780 }
1781 } else {
1782 0
1783 };
1784
1785 let (rf_rows, rf_cols) = row_fields_view.dims();
1786 let (val_rows, val_cols) = values_view.dims();
1787
1788 let has_headers = field_headers == 1 || field_headers == 3;
1790 let data_start_row = if has_headers { 1 } else { 0 };
1791
1792 if rf_rows != val_rows {
1794 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1795 ExcelError::new(ExcelErrorKind::Value)
1796 .with_message("Row fields and values must have same number of rows"),
1797 )));
1798 }
1799
1800 if rf_rows <= data_start_row {
1801 return Ok(crate::traits::CalcValue::Range(
1803 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
1804 ));
1805 }
1806
1807 let mut groups: std::collections::HashMap<String, Vec<Vec<f64>>> = HashMap::new();
1810 let mut group_order: Vec<String> = Vec::new();
1811
1812 for r in data_start_row..rf_rows {
1813 let mut key_parts: Vec<String> = Vec::with_capacity(rf_cols);
1815 for c in 0..rf_cols {
1816 key_parts.push(literal_to_group_key(&row_fields_view.get_cell(r, c)));
1817 }
1818 let key = key_parts.join("\x00"); let mut row_values: Vec<Option<f64>> = Vec::with_capacity(val_cols);
1822 for c in 0..val_cols {
1823 row_values.push(literal_to_num_opt(&values_view.get_cell(r, c)));
1824 }
1825
1826 if !groups.contains_key(&key) {
1828 group_order.push(key.clone());
1829 groups.insert(key.clone(), vec![Vec::new(); val_cols]);
1830 }
1831
1832 let group_vals = groups.get_mut(&key).unwrap();
1833 for (c, val) in row_values.iter().enumerate() {
1834 if let Some(v) = val {
1835 group_vals[c].push(*v);
1836 }
1837 }
1838 }
1839
1840 if sort_order != 0 {
1842 group_order.sort_by(|a, b| if sort_order > 0 { a.cmp(b) } else { b.cmp(a) });
1843 }
1844
1845 let mut output: Vec<Vec<LiteralValue>> = Vec::new();
1847
1848 let generate_headers = field_headers == 2 || field_headers == 3;
1850 if generate_headers {
1851 let mut header_row: Vec<LiteralValue> = Vec::new();
1852 for c in 0..rf_cols {
1854 if has_headers {
1855 header_row.push(row_fields_view.get_cell(0, c));
1856 } else {
1857 header_row.push(LiteralValue::Text(format!("Field{}", c + 1)));
1858 }
1859 }
1860 for c in 0..val_cols {
1862 if has_headers {
1863 header_row.push(values_view.get_cell(0, c));
1864 } else {
1865 header_row.push(LiteralValue::Text(format!("Value{}", c + 1)));
1866 }
1867 }
1868 output.push(header_row);
1869 }
1870
1871 for key in &group_order {
1873 let mut row: Vec<LiteralValue> = Vec::new();
1874
1875 let key_parts: Vec<&str> = key.split('\x00').collect();
1877 for part in &key_parts {
1878 row.push(LiteralValue::Text(part.to_string()));
1879 }
1880
1881 let group_vals = groups.get(key).unwrap();
1883 for col_vals in group_vals {
1884 let result = aggregation.apply(col_vals);
1885 if result.is_nan() {
1886 row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
1887 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
1888 row.push(LiteralValue::Int(result as i64));
1889 } else {
1890 row.push(LiteralValue::Number(result));
1891 }
1892 }
1893 output.push(row);
1894 }
1895
1896 if total_depth >= 1 {
1898 let mut total_row: Vec<LiteralValue> = Vec::new();
1899 total_row.push(LiteralValue::Text("Grand Total".to_string()));
1901 for _ in 1..rf_cols {
1902 total_row.push(LiteralValue::Empty);
1903 }
1904
1905 for c in 0..val_cols {
1907 let mut all_vals: Vec<f64> = Vec::new();
1908 for group_vals in groups.values() {
1909 all_vals.extend(&group_vals[c]);
1910 }
1911 let result = aggregation.apply(&all_vals);
1912 if result.is_nan() {
1913 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
1914 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
1915 total_row.push(LiteralValue::Int(result as i64));
1916 } else {
1917 total_row.push(LiteralValue::Number(result));
1918 }
1919 }
1920 output.push(total_row);
1921 }
1922
1923 if output.is_empty() {
1924 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1925 ExcelError::new(ExcelErrorKind::Calc),
1926 )));
1927 }
1928
1929 Ok(collapse_if_scalar(output, _ctx.date_system()))
1930 }
1931}
1932
1933#[derive(Debug)]
1936pub struct PivotByFn;
1937
1938impl Function for PivotByFn {
2010 func_caps!(PURE);
2011 fn name(&self) -> &'static str {
2012 "PIVOTBY"
2013 }
2014 fn min_args(&self) -> usize {
2015 4
2016 }
2017 fn variadic(&self) -> bool {
2018 true
2019 }
2020 fn arg_schema(&self) -> &'static [ArgSchema] {
2021 use once_cell::sync::Lazy;
2022 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2023 vec![
2024 ArgSchema {
2026 kinds: smallvec::smallvec![ArgKind::Range],
2027 required: true,
2028 by_ref: true,
2029 shape: ShapeKind::Range,
2030 coercion: CoercionPolicy::None,
2031 max: None,
2032 repeating: None,
2033 default: None,
2034 },
2035 ArgSchema {
2037 kinds: smallvec::smallvec![ArgKind::Range],
2038 required: true,
2039 by_ref: true,
2040 shape: ShapeKind::Range,
2041 coercion: CoercionPolicy::None,
2042 max: None,
2043 repeating: None,
2044 default: None,
2045 },
2046 ArgSchema {
2048 kinds: smallvec::smallvec![ArgKind::Range],
2049 required: true,
2050 by_ref: true,
2051 shape: ShapeKind::Range,
2052 coercion: CoercionPolicy::None,
2053 max: None,
2054 repeating: None,
2055 default: None,
2056 },
2057 ArgSchema {
2059 kinds: smallvec::smallvec![ArgKind::Any],
2060 required: true,
2061 by_ref: false,
2062 shape: ShapeKind::Scalar,
2063 coercion: CoercionPolicy::None,
2064 max: None,
2065 repeating: None,
2066 default: None,
2067 },
2068 ArgSchema {
2070 kinds: smallvec::smallvec![ArgKind::Number],
2071 required: false,
2072 by_ref: false,
2073 shape: ShapeKind::Scalar,
2074 coercion: CoercionPolicy::NumberLenientText,
2075 max: None,
2076 repeating: None,
2077 default: Some(LiteralValue::Int(1)),
2078 },
2079 ArgSchema {
2081 kinds: smallvec::smallvec![ArgKind::Number],
2082 required: false,
2083 by_ref: false,
2084 shape: ShapeKind::Scalar,
2085 coercion: CoercionPolicy::NumberLenientText,
2086 max: None,
2087 repeating: None,
2088 default: Some(LiteralValue::Int(0)),
2089 },
2090 ArgSchema {
2092 kinds: smallvec::smallvec![ArgKind::Number],
2093 required: false,
2094 by_ref: false,
2095 shape: ShapeKind::Scalar,
2096 coercion: CoercionPolicy::NumberLenientText,
2097 max: None,
2098 repeating: None,
2099 default: Some(LiteralValue::Int(0)),
2100 },
2101 ArgSchema {
2103 kinds: smallvec::smallvec![ArgKind::Number],
2104 required: false,
2105 by_ref: false,
2106 shape: ShapeKind::Scalar,
2107 coercion: CoercionPolicy::NumberLenientText,
2108 max: None,
2109 repeating: None,
2110 default: Some(LiteralValue::Int(0)),
2111 },
2112 ArgSchema {
2114 kinds: smallvec::smallvec![ArgKind::Number],
2115 required: false,
2116 by_ref: false,
2117 shape: ShapeKind::Scalar,
2118 coercion: CoercionPolicy::NumberLenientText,
2119 max: None,
2120 repeating: None,
2121 default: Some(LiteralValue::Int(0)),
2122 },
2123 ]
2124 });
2125 &SCHEMA
2126 }
2127
2128 fn eval<'a, 'b, 'c>(
2129 &self,
2130 args: &'c [ArgumentHandle<'a, 'b>],
2131 _ctx: &dyn FunctionContext<'b>,
2132 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2133 if args.len() < 4 {
2134 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2135 ExcelError::new(ExcelErrorKind::Value),
2136 )));
2137 }
2138
2139 let row_fields_view = match args[0].range_view() {
2141 Ok(v) => v,
2142 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2143 };
2144 let col_fields_view = match args[1].range_view() {
2145 Ok(v) => v,
2146 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2147 };
2148 let values_view = match args[2].range_view() {
2149 Ok(v) => v,
2150 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2151 };
2152
2153 let agg_val = args[3].value()?.into_literal();
2155 let aggregation = match GroupAggregation::from_literal(&agg_val) {
2156 Some(a) => a,
2157 None => {
2158 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2159 ExcelError::new(ExcelErrorKind::Value)
2160 .with_message("Invalid aggregation function"),
2161 )));
2162 }
2163 };
2164
2165 let field_headers = if args.len() >= 5 {
2167 match args[4].value()?.into_literal() {
2168 LiteralValue::Int(i) => i as i32,
2169 LiteralValue::Number(n) => n as i32,
2170 _ => 1,
2171 }
2172 } else {
2173 1
2174 };
2175
2176 let row_total_depth = if args.len() >= 6 {
2177 match args[5].value()?.into_literal() {
2178 LiteralValue::Int(i) => i as i32,
2179 LiteralValue::Number(n) => n as i32,
2180 _ => 0,
2181 }
2182 } else {
2183 0
2184 };
2185
2186 let row_sort_order = if args.len() >= 7 {
2187 match args[6].value()?.into_literal() {
2188 LiteralValue::Int(i) => i as i32,
2189 LiteralValue::Number(n) => n as i32,
2190 _ => 0,
2191 }
2192 } else {
2193 0
2194 };
2195
2196 let col_total_depth = if args.len() >= 8 {
2197 match args[7].value()?.into_literal() {
2198 LiteralValue::Int(i) => i as i32,
2199 LiteralValue::Number(n) => n as i32,
2200 _ => 0,
2201 }
2202 } else {
2203 0
2204 };
2205
2206 let col_sort_order = if args.len() >= 9 {
2207 match args[8].value()?.into_literal() {
2208 LiteralValue::Int(i) => i as i32,
2209 LiteralValue::Number(n) => n as i32,
2210 _ => 0,
2211 }
2212 } else {
2213 0
2214 };
2215
2216 let (rf_rows, rf_cols) = row_fields_view.dims();
2217 let (cf_rows, _cf_cols) = col_fields_view.dims();
2218 let (val_rows, _val_cols) = values_view.dims();
2219
2220 if rf_rows != cf_rows || rf_rows != val_rows {
2222 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2223 ExcelError::new(ExcelErrorKind::Value)
2224 .with_message("All ranges must have same number of rows"),
2225 )));
2226 }
2227
2228 let has_headers = field_headers == 1 || field_headers == 3;
2229 let data_start_row = if has_headers { 1 } else { 0 };
2230
2231 if rf_rows <= data_start_row {
2232 return Ok(crate::traits::CalcValue::Range(
2233 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2234 ));
2235 }
2236
2237 let mut row_keys: Vec<String> = Vec::new();
2239 let mut col_keys: Vec<String> = Vec::new();
2240 let mut row_key_set: std::collections::HashSet<String> = std::collections::HashSet::new();
2241 let mut col_key_set: std::collections::HashSet<String> = std::collections::HashSet::new();
2242
2243 let mut pivot_data: HashMap<(String, String), Vec<f64>> = HashMap::new();
2245
2246 for r in data_start_row..rf_rows {
2247 let mut row_key_parts: Vec<String> = Vec::with_capacity(rf_cols);
2249 for c in 0..rf_cols {
2250 row_key_parts.push(literal_to_group_key(&row_fields_view.get_cell(r, c)));
2251 }
2252 let row_key = row_key_parts.join("\x00");
2253
2254 let col_key = literal_to_group_key(&col_fields_view.get_cell(r, 0));
2257
2258 let val = literal_to_num_opt(&values_view.get_cell(r, 0));
2260
2261 if !row_key_set.contains(&row_key) {
2263 row_key_set.insert(row_key.clone());
2264 row_keys.push(row_key.clone());
2265 }
2266 if !col_key_set.contains(&col_key) {
2267 col_key_set.insert(col_key.clone());
2268 col_keys.push(col_key.clone());
2269 }
2270
2271 let entry = pivot_data.entry((row_key, col_key)).or_default();
2273 if let Some(v) = val {
2274 entry.push(v);
2275 }
2276 }
2277
2278 if row_sort_order != 0 {
2280 row_keys.sort_by(|a, b| {
2281 if row_sort_order > 0 {
2282 a.cmp(b)
2283 } else {
2284 b.cmp(a)
2285 }
2286 });
2287 }
2288 if col_sort_order != 0 {
2289 col_keys.sort_by(|a, b| {
2290 if col_sort_order > 0 {
2291 a.cmp(b)
2292 } else {
2293 b.cmp(a)
2294 }
2295 });
2296 }
2297
2298 let mut output: Vec<Vec<LiteralValue>> = Vec::new();
2300
2301 let generate_headers = field_headers == 2 || field_headers == 3;
2303 if generate_headers || has_headers {
2304 let mut header_row: Vec<LiteralValue> = Vec::new();
2305 for _ in 0..rf_cols {
2307 header_row.push(LiteralValue::Empty);
2308 }
2309 for col_key in &col_keys {
2311 let parts: Vec<&str> = col_key.split('\x00').collect();
2313 header_row.push(LiteralValue::Text(parts.join(" ")));
2314 }
2315 if col_total_depth >= 1 {
2317 header_row.push(LiteralValue::Text("Total".to_string()));
2318 }
2319 output.push(header_row);
2320 }
2321
2322 for row_key in &row_keys {
2324 let mut row: Vec<LiteralValue> = Vec::new();
2325
2326 let row_parts: Vec<&str> = row_key.split('\x00').collect();
2328 for part in &row_parts {
2329 row.push(LiteralValue::Text(part.to_string()));
2330 }
2331
2332 let mut row_total_vals: Vec<f64> = Vec::new();
2334 for col_key in &col_keys {
2335 let key = (row_key.clone(), col_key.clone());
2336 let vals = pivot_data.get(&key).map(|v| v.as_slice()).unwrap_or(&[]);
2337 let result = aggregation.apply(vals);
2338
2339 row_total_vals.extend(vals);
2341
2342 if result.is_nan() || vals.is_empty() {
2343 row.push(LiteralValue::Empty);
2344 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2345 row.push(LiteralValue::Int(result as i64));
2346 } else {
2347 row.push(LiteralValue::Number(result));
2348 }
2349 }
2350
2351 if col_total_depth >= 1 {
2353 let result = aggregation.apply(&row_total_vals);
2354 if result.is_nan() {
2355 row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2356 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2357 row.push(LiteralValue::Int(result as i64));
2358 } else {
2359 row.push(LiteralValue::Number(result));
2360 }
2361 }
2362
2363 output.push(row);
2364 }
2365
2366 if row_total_depth >= 1 {
2368 let mut total_row: Vec<LiteralValue> = Vec::new();
2369 total_row.push(LiteralValue::Text("Total".to_string()));
2370 for _ in 1..rf_cols {
2371 total_row.push(LiteralValue::Empty);
2372 }
2373
2374 let mut grand_total_vals: Vec<f64> = Vec::new();
2375 for col_key in &col_keys {
2376 let mut col_vals: Vec<f64> = Vec::new();
2377 for row_key in &row_keys {
2378 let key = (row_key.clone(), col_key.clone());
2379 if let Some(vals) = pivot_data.get(&key) {
2380 col_vals.extend(vals);
2381 }
2382 }
2383 grand_total_vals.extend(&col_vals);
2384 let result = aggregation.apply(&col_vals);
2385 if result.is_nan() {
2386 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2387 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2388 total_row.push(LiteralValue::Int(result as i64));
2389 } else {
2390 total_row.push(LiteralValue::Number(result));
2391 }
2392 }
2393
2394 if col_total_depth >= 1 {
2396 let result = aggregation.apply(&grand_total_vals);
2397 if result.is_nan() {
2398 total_row.push(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)));
2399 } else if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
2400 total_row.push(LiteralValue::Int(result as i64));
2401 } else {
2402 total_row.push(LiteralValue::Number(result));
2403 }
2404 }
2405
2406 output.push(total_row);
2407 }
2408
2409 if output.is_empty() {
2410 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2411 ExcelError::new(ExcelErrorKind::Calc),
2412 )));
2413 }
2414
2415 Ok(collapse_if_scalar(output, _ctx.date_system()))
2416 }
2417}
2418
2419#[derive(Debug)]
2422pub struct FilterFn;
2423impl Function for FilterFn {
2481 func_caps!(PURE);
2482 fn name(&self) -> &'static str {
2483 "FILTER"
2484 }
2485 fn min_args(&self) -> usize {
2486 2
2487 }
2488 fn variadic(&self) -> bool {
2489 true
2490 }
2491 fn arg_schema(&self) -> &'static [ArgSchema] {
2492 use once_cell::sync::Lazy;
2493 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2494 vec![
2495 ArgSchema {
2497 kinds: smallvec::smallvec![ArgKind::Range],
2498 required: true,
2499 by_ref: true,
2500 shape: ShapeKind::Range,
2501 coercion: CoercionPolicy::None,
2502 max: None,
2503 repeating: None,
2504 default: None,
2505 },
2506 ArgSchema {
2508 kinds: smallvec::smallvec![ArgKind::Range],
2509 required: true,
2510 by_ref: true,
2511 shape: ShapeKind::Range,
2512 coercion: CoercionPolicy::None,
2513 max: None,
2514 repeating: None,
2515 default: None,
2516 },
2517 ArgSchema {
2519 kinds: smallvec::smallvec![ArgKind::Any],
2520 required: false,
2521 by_ref: false,
2522 shape: ShapeKind::Scalar,
2523 coercion: CoercionPolicy::None,
2524 max: None,
2525 repeating: None,
2526 default: None,
2527 },
2528 ]
2529 });
2530 &SCHEMA
2531 }
2532 fn eval<'a, 'b, 'c>(
2533 &self,
2534 args: &'c [ArgumentHandle<'a, 'b>],
2535 _ctx: &dyn FunctionContext<'b>,
2536 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2537 if args.len() < 2 {
2538 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2539 ExcelError::new(ExcelErrorKind::Value),
2540 )));
2541 }
2542 let array_view = args[0].range_view()?;
2543 let include_view = args[1].range_view()?;
2544
2545 let (array_rows, array_cols) = array_view.dims();
2546 if array_rows == 0 || array_cols == 0 {
2547 return Ok(crate::traits::CalcValue::Range(
2548 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2549 ));
2550 }
2551
2552 let (include_rows, include_cols) = include_view.dims();
2553 if include_rows != array_rows && include_rows != 1 {
2554 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2555 ExcelError::new(ExcelErrorKind::Value),
2556 )));
2557 }
2558
2559 let mut result: Vec<Vec<LiteralValue>> = Vec::new();
2560 for r in 0..array_rows {
2561 let include_r = if include_rows == array_rows { r } else { 0 };
2562 let mut include = false;
2563 for c in 0..include_cols {
2564 if include_view.get_cell(include_r, c).is_truthy() {
2565 include = true;
2566 break;
2567 }
2568 }
2569
2570 if include {
2571 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(array_cols);
2572 for c in 0..array_cols {
2573 row_out.push(array_view.get_cell(r, c));
2574 }
2575 result.push(row_out);
2576 }
2577 }
2578
2579 if result.is_empty() {
2580 if args.len() >= 3 {
2581 return args[2].value();
2582 }
2583 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2584 ExcelError::new(ExcelErrorKind::Calc),
2585 )));
2586 }
2587
2588 Ok(crate::traits::CalcValue::Range(
2589 crate::engine::range_view::RangeView::from_owned_rows(result, _ctx.date_system()),
2590 ))
2591 }
2592}
2593
2594#[derive(Debug)]
2597pub struct UniqueFn;
2598impl Function for UniqueFn {
2654 func_caps!(PURE);
2655 fn name(&self) -> &'static str {
2656 "UNIQUE"
2657 }
2658 fn min_args(&self) -> usize {
2659 1
2660 }
2661 fn variadic(&self) -> bool {
2662 true
2663 }
2664 fn arg_schema(&self) -> &'static [ArgSchema] {
2665 use once_cell::sync::Lazy;
2666 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2667 vec![
2668 ArgSchema {
2669 kinds: smallvec::smallvec![ArgKind::Range],
2670 required: true,
2671 by_ref: true,
2672 shape: ShapeKind::Range,
2673 coercion: CoercionPolicy::None,
2674 max: None,
2675 repeating: None,
2676 default: None,
2677 },
2678 ArgSchema {
2679 kinds: smallvec::smallvec![ArgKind::Logical],
2680 required: false,
2681 by_ref: false,
2682 shape: ShapeKind::Scalar,
2683 coercion: CoercionPolicy::Logical,
2684 max: None,
2685 repeating: None,
2686 default: Some(LiteralValue::Boolean(false)),
2687 },
2688 ArgSchema {
2689 kinds: smallvec::smallvec![ArgKind::Logical],
2690 required: false,
2691 by_ref: false,
2692 shape: ShapeKind::Scalar,
2693 coercion: CoercionPolicy::Logical,
2694 max: None,
2695 repeating: None,
2696 default: Some(LiteralValue::Boolean(false)),
2697 },
2698 ]
2699 });
2700 &SCHEMA
2701 }
2702 fn eval<'a, 'b, 'c>(
2703 &self,
2704 args: &'c [ArgumentHandle<'a, 'b>],
2705 _ctx: &dyn FunctionContext<'b>,
2706 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2707 let view = match args[0].range_view() {
2708 Ok(v) => v,
2709 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2710 };
2711 let (rows, cols) = view.dims();
2712 if rows == 0 || cols == 0 {
2713 return Ok(crate::traits::CalcValue::Range(
2714 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
2715 ));
2716 }
2717
2718 let by_col = if args.len() >= 2 {
2719 matches!(args[1].value()?.into_literal(), LiteralValue::Boolean(true))
2720 } else {
2721 false
2722 };
2723 let exactly_once = if args.len() >= 3 {
2724 matches!(args[2].value()?.into_literal(), LiteralValue::Boolean(true))
2725 } else {
2726 false
2727 };
2728
2729 if by_col {
2730 #[derive(Hash, Eq, PartialEq, Clone)]
2731 struct ColKey(Vec<LiteralValue>);
2732
2733 let mut order: Vec<ColKey> = Vec::new();
2734 let mut counts: HashMap<ColKey, usize> = HashMap::new();
2735
2736 for c in 0..cols {
2737 let mut col_vals: Vec<LiteralValue> = Vec::with_capacity(rows);
2738 for r in 0..rows {
2739 col_vals.push(view.get_cell(r, c));
2740 }
2741 let key = ColKey(col_vals);
2742 if !counts.contains_key(&key) {
2743 order.push(key.clone());
2744 }
2745 *counts.entry(key).or_insert(0) += 1;
2746 }
2747
2748 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
2749 for k in order {
2750 if !exactly_once || counts.get(&k) == Some(&1) {
2751 out.push(k.0);
2752 }
2753 }
2754 return Ok(collapse_if_scalar(out, _ctx.date_system()));
2755 }
2756
2757 #[derive(Hash, Eq, PartialEq, Clone)]
2758 struct RowKey(Vec<LiteralValue>);
2759
2760 let mut order: Vec<RowKey> = Vec::new();
2761 let mut counts: HashMap<RowKey, usize> = HashMap::new();
2762 for r in 0..rows {
2763 let mut row_vals: Vec<LiteralValue> = Vec::with_capacity(cols);
2764 for c in 0..cols {
2765 row_vals.push(view.get_cell(r, c));
2766 }
2767 let key = RowKey(row_vals);
2768 if !counts.contains_key(&key) {
2769 order.push(key.clone());
2770 }
2771 *counts.entry(key).or_insert(0) += 1;
2772 }
2773
2774 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
2775 for k in order {
2776 if !exactly_once || counts.get(&k) == Some(&1) {
2777 out.push(k.0);
2778 }
2779 }
2780 Ok(collapse_if_scalar(out, _ctx.date_system()))
2781 }
2782}
2783
2784#[derive(Debug)]
2787pub struct SequenceFn;
2788impl Function for SequenceFn {
2833 func_caps!(PURE);
2834 fn name(&self) -> &'static str {
2835 "SEQUENCE"
2836 }
2837 fn min_args(&self) -> usize {
2838 1
2839 }
2840 fn variadic(&self) -> bool {
2841 true
2842 }
2843 fn arg_schema(&self) -> &'static [ArgSchema] {
2844 use once_cell::sync::Lazy;
2845 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
2846 vec![
2847 ArgSchema {
2849 kinds: smallvec::smallvec![ArgKind::Number],
2850 required: true,
2851 by_ref: false,
2852 shape: ShapeKind::Scalar,
2853 coercion: CoercionPolicy::NumberLenientText,
2854 max: None,
2855 repeating: None,
2856 default: None,
2857 },
2858 ArgSchema {
2860 kinds: smallvec::smallvec![ArgKind::Number],
2861 required: false,
2862 by_ref: false,
2863 shape: ShapeKind::Scalar,
2864 coercion: CoercionPolicy::NumberLenientText,
2865 max: None,
2866 repeating: None,
2867 default: Some(LiteralValue::Int(1)),
2868 },
2869 ArgSchema {
2871 kinds: smallvec::smallvec![ArgKind::Number],
2872 required: false,
2873 by_ref: false,
2874 shape: ShapeKind::Scalar,
2875 coercion: CoercionPolicy::NumberLenientText,
2876 max: None,
2877 repeating: None,
2878 default: Some(LiteralValue::Int(1)),
2879 },
2880 ArgSchema {
2882 kinds: smallvec::smallvec![ArgKind::Number],
2883 required: false,
2884 by_ref: false,
2885 shape: ShapeKind::Scalar,
2886 coercion: CoercionPolicy::NumberLenientText,
2887 max: None,
2888 repeating: None,
2889 default: Some(LiteralValue::Int(1)),
2890 },
2891 ]
2892 });
2893 &SCHEMA
2894 }
2895 fn eval<'a, 'b, 'c>(
2896 &self,
2897 args: &'c [ArgumentHandle<'a, 'b>],
2898 _ctx: &dyn FunctionContext<'b>,
2899 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2900 let num = |a: &ArgumentHandle| -> Result<f64, ExcelError> {
2902 Ok(match a.value()?.into_literal() {
2903 LiteralValue::Int(i) => i as f64,
2904 LiteralValue::Number(n) => n,
2905 _other => {
2906 return Err(ExcelError::new(ExcelErrorKind::Value));
2907 }
2908 })
2909 };
2910 let rows_f = num(&args[0])?;
2911 let rows = rows_f as i64;
2912 let cols = if args.len() >= 2 {
2913 num(&args[1])? as i64
2914 } else {
2915 1
2916 };
2917 let start = if args.len() >= 3 { num(&args[2])? } else { 1.0 };
2918 let step = if args.len() >= 4 { num(&args[3])? } else { 1.0 };
2919 if rows <= 0 || cols <= 0 {
2920 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2921 ExcelError::new(ExcelErrorKind::Value),
2922 )));
2923 }
2924 let total = rows.saturating_mul(cols);
2925 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows as usize);
2927 let mut current = start;
2928 for _r in 0..rows {
2929 let mut row_vec: Vec<LiteralValue> = Vec::with_capacity(cols as usize);
2930 for _c in 0..cols {
2931 if (current.fract().abs() < 1e-12) && current.abs() < (i64::MAX as f64) {
2933 row_vec.push(LiteralValue::Int(current as i64));
2934 } else {
2935 row_vec.push(LiteralValue::Number(current));
2936 }
2937 current += step;
2938 }
2939 out.push(row_vec);
2940 }
2941
2942 Ok(collapse_if_scalar(out, _ctx.date_system()))
2943 }
2944}
2945
2946#[derive(Debug)]
2949pub struct TransposeFn;
2950impl Function for TransposeFn {
3003 func_caps!(PURE);
3004 fn name(&self) -> &'static str {
3005 "TRANSPOSE"
3006 }
3007 fn min_args(&self) -> usize {
3008 1
3009 }
3010 fn variadic(&self) -> bool {
3011 false
3012 }
3013 fn arg_schema(&self) -> &'static [ArgSchema] {
3014 use once_cell::sync::Lazy;
3015 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3016 vec![ArgSchema {
3017 kinds: smallvec::smallvec![ArgKind::Range],
3018 required: true,
3019 by_ref: true,
3020 shape: ShapeKind::Range,
3021 coercion: CoercionPolicy::None,
3022 max: None,
3023 repeating: None,
3024 default: None,
3025 }]
3026 });
3027 &SCHEMA
3028 }
3029 fn eval<'a, 'b, 'c>(
3030 &self,
3031 args: &'c [ArgumentHandle<'a, 'b>],
3032 _ctx: &dyn FunctionContext<'b>,
3033 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3034 let view = match args[0].range_view() {
3035 Ok(v) => v,
3036 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3037 };
3038 let (rows, cols) = view.dims();
3039 if rows == 0 || cols == 0 {
3040 return Ok(crate::traits::CalcValue::Range(
3041 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3042 ));
3043 }
3044
3045 let mut out: Vec<Vec<LiteralValue>> = vec![Vec::with_capacity(rows); cols];
3046 for (c, col) in out.iter_mut().enumerate().take(cols) {
3047 for r in 0..rows {
3048 col.push(view.get_cell(r, c));
3049 }
3050 }
3051 Ok(collapse_if_scalar(out, _ctx.date_system()))
3052 }
3053}
3054
3055#[derive(Debug)]
3058pub struct TakeFn;
3059impl Function for TakeFn {
3115 func_caps!(PURE);
3116 fn name(&self) -> &'static str {
3117 "TAKE"
3118 }
3119 fn min_args(&self) -> usize {
3120 2
3121 }
3122 fn variadic(&self) -> bool {
3123 true
3124 }
3125 fn arg_schema(&self) -> &'static [ArgSchema] {
3126 use once_cell::sync::Lazy;
3127 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3128 vec![
3129 ArgSchema {
3130 kinds: smallvec::smallvec![ArgKind::Range],
3131 required: true,
3132 by_ref: true,
3133 shape: ShapeKind::Range,
3134 coercion: CoercionPolicy::None,
3135 max: None,
3136 repeating: None,
3137 default: None,
3138 },
3139 ArgSchema {
3140 kinds: smallvec::smallvec![ArgKind::Number],
3141 required: true,
3142 by_ref: false,
3143 shape: ShapeKind::Scalar,
3144 coercion: CoercionPolicy::NumberLenientText,
3145 max: None,
3146 repeating: None,
3147 default: None,
3148 },
3149 ArgSchema {
3150 kinds: smallvec::smallvec![ArgKind::Number],
3151 required: false,
3152 by_ref: false,
3153 shape: ShapeKind::Scalar,
3154 coercion: CoercionPolicy::NumberLenientText,
3155 max: None,
3156 repeating: None,
3157 default: None,
3158 },
3159 ]
3160 });
3161 &SCHEMA
3162 }
3163 fn eval<'a, 'b, 'c>(
3164 &self,
3165 args: &'c [ArgumentHandle<'a, 'b>],
3166 _ctx: &dyn FunctionContext<'b>,
3167 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3168 let view = match args[0].range_view() {
3169 Ok(v) => v,
3170 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3171 };
3172 let (rows, cols) = view.dims();
3173 if rows == 0 || cols == 0 {
3174 return Ok(crate::traits::CalcValue::Range(
3175 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3176 ));
3177 }
3178
3179 let height = rows as i64;
3180 let width = cols as i64;
3181
3182 let num = |a: &ArgumentHandle| -> Result<i64, ExcelError> {
3183 Ok(match a.value()?.into_literal() {
3184 LiteralValue::Int(i) => i,
3185 LiteralValue::Number(n) => n as i64,
3186 _ => 0,
3187 })
3188 };
3189 let take_rows = num(&args[1])?;
3190 let take_cols = if args.len() >= 3 {
3191 Some(num(&args[2])?)
3192 } else {
3193 None
3194 };
3195
3196 if take_rows.abs() > height {
3197 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3198 ExcelError::new(ExcelErrorKind::Value),
3199 )));
3200 }
3201
3202 let (row_start, row_end) = if take_rows >= 0 {
3203 (0usize, take_rows as usize)
3204 } else {
3205 ((height + take_rows) as usize, height as usize)
3206 };
3207
3208 let (col_start, col_end) = if let Some(tc) = take_cols {
3209 if tc.abs() > width {
3210 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3211 ExcelError::new(ExcelErrorKind::Value),
3212 )));
3213 }
3214 if tc >= 0 {
3215 (0usize, tc as usize)
3216 } else {
3217 ((width + tc) as usize, width as usize)
3218 }
3219 } else {
3220 (0usize, width as usize)
3221 };
3222
3223 if row_start >= row_end || col_start >= col_end {
3224 return Ok(crate::traits::CalcValue::Range(
3225 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3226 ));
3227 }
3228
3229 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_end - row_start);
3230 for r in row_start..row_end {
3231 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(col_end - col_start);
3232 for c in col_start..col_end {
3233 row_out.push(view.get_cell(r, c));
3234 }
3235 out.push(row_out);
3236 }
3237
3238 Ok(collapse_if_scalar(out, _ctx.date_system()))
3239 }
3240}
3241
3242#[derive(Debug)]
3245pub struct DropFn;
3246impl Function for DropFn {
3299 func_caps!(PURE);
3300 fn name(&self) -> &'static str {
3301 "DROP"
3302 }
3303 fn min_args(&self) -> usize {
3304 2
3305 }
3306 fn variadic(&self) -> bool {
3307 true
3308 }
3309 fn arg_schema(&self) -> &'static [ArgSchema] {
3310 use once_cell::sync::Lazy;
3311 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
3312 vec![
3313 ArgSchema {
3314 kinds: smallvec::smallvec![ArgKind::Range],
3315 required: true,
3316 by_ref: true,
3317 shape: ShapeKind::Range,
3318 coercion: CoercionPolicy::None,
3319 max: None,
3320 repeating: None,
3321 default: None,
3322 },
3323 ArgSchema {
3324 kinds: smallvec::smallvec![ArgKind::Number],
3325 required: true,
3326 by_ref: false,
3327 shape: ShapeKind::Scalar,
3328 coercion: CoercionPolicy::NumberLenientText,
3329 max: None,
3330 repeating: None,
3331 default: None,
3332 },
3333 ArgSchema {
3334 kinds: smallvec::smallvec![ArgKind::Number],
3335 required: false,
3336 by_ref: false,
3337 shape: ShapeKind::Scalar,
3338 coercion: CoercionPolicy::NumberLenientText,
3339 max: None,
3340 repeating: None,
3341 default: None,
3342 },
3343 ]
3344 });
3345 &SCHEMA
3346 }
3347 fn eval<'a, 'b, 'c>(
3348 &self,
3349 args: &'c [ArgumentHandle<'a, 'b>],
3350 _ctx: &dyn FunctionContext<'b>,
3351 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3352 let view = match args[0].range_view() {
3353 Ok(v) => v,
3354 Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3355 };
3356 let (rows, cols) = view.dims();
3357 if rows == 0 || cols == 0 {
3358 return Ok(crate::traits::CalcValue::Range(
3359 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3360 ));
3361 }
3362
3363 let height = rows as i64;
3364 let width = cols as i64;
3365
3366 let num = |a: &ArgumentHandle| -> Result<i64, ExcelError> {
3367 Ok(match a.value()?.into_literal() {
3368 LiteralValue::Int(i) => i,
3369 LiteralValue::Number(n) => n as i64,
3370 _ => 0,
3371 })
3372 };
3373 let drop_rows = num(&args[1])?;
3374 let drop_cols = if args.len() >= 3 {
3375 Some(num(&args[2])?)
3376 } else {
3377 None
3378 };
3379
3380 let (row_start, row_end) = if drop_rows >= 0 {
3381 ((drop_rows as usize).min(height as usize), height as usize)
3382 } else {
3383 (0usize, (height + drop_rows).max(0) as usize)
3384 };
3385
3386 let (col_start, col_end) = if let Some(dc) = drop_cols {
3387 if dc >= 0 {
3388 ((dc as usize).min(width as usize), width as usize)
3389 } else {
3390 (0usize, (width + dc).max(0) as usize)
3391 }
3392 } else {
3393 (0usize, width as usize)
3394 };
3395
3396 if row_start >= row_end || col_start >= col_end {
3397 return Ok(crate::traits::CalcValue::Range(
3398 crate::engine::range_view::RangeView::from_owned_rows(vec![], _ctx.date_system()),
3399 ));
3400 }
3401
3402 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_end - row_start);
3403 for r in row_start..row_end {
3404 let mut row_out: Vec<LiteralValue> = Vec::with_capacity(col_end - col_start);
3405 for c in col_start..col_end {
3406 row_out.push(view.get_cell(r, c));
3407 }
3408 out.push(row_out);
3409 }
3410
3411 Ok(collapse_if_scalar(out, _ctx.date_system()))
3412 }
3413}
3414
3415pub fn register_builtins() {
3416 use crate::function_registry::register_function;
3417 use std::sync::Arc;
3418 register_function(Arc::new(XLookupFn));
3419 register_function(Arc::new(FilterFn));
3420 register_function(Arc::new(UniqueFn));
3421 register_function(Arc::new(SequenceFn));
3422 register_function(Arc::new(TransposeFn));
3423 register_function(Arc::new(TakeFn));
3424 register_function(Arc::new(DropFn));
3425 register_function(Arc::new(XMatchFn));
3426 register_function(Arc::new(SortFn));
3427 register_function(Arc::new(SortByFn));
3428 register_function(Arc::new(RandArrayFn));
3429 register_function(Arc::new(GroupByFn));
3430 register_function(Arc::new(PivotByFn));
3431}
3432
3433#[cfg(test)]
3436mod tests {
3437 use super::*;
3438 use crate::test_workbook::TestWorkbook;
3439 use crate::traits::ArgumentHandle;
3440 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
3441 use std::sync::Arc;
3442
3443 #[test]
3444 fn test_all_dynamic_functions_registered() {
3445 crate::builtins::load_builtins();
3447
3448 let functions = [
3449 "XLOOKUP",
3450 "FILTER",
3451 "UNIQUE",
3452 "SEQUENCE",
3453 "TRANSPOSE",
3454 "TAKE",
3455 "DROP",
3456 "XMATCH",
3457 "SORT",
3458 "SORTBY",
3459 "RANDARRAY",
3460 "GROUPBY",
3461 "PIVOTBY",
3462 ];
3463
3464 for name in &functions {
3465 let result = crate::function_registry::get("", name);
3466 assert!(result.is_some(), "Function {} should be registered", name);
3467 }
3468 }
3469
3470 fn lit(v: LiteralValue) -> ASTNode {
3471 ASTNode::new(ASTNodeType::Literal(v), None)
3472 }
3473
3474 fn range(r: &str, sr: u32, sc: u32, er: u32, ec: u32) -> ASTNode {
3475 ASTNode::new(
3476 ASTNodeType::Reference {
3477 original: r.into(),
3478 reference: ReferenceType::range(None, Some(sr), Some(sc), Some(er), Some(ec)),
3479 },
3480 None,
3481 )
3482 }
3483
3484 #[test]
3485 fn xlookup_basic_exact_and_if_not_found() {
3486 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3487 let wb = wb
3488 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("a".into()))
3489 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("b".into()))
3490 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3491 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20));
3492 let ctx = wb.interpreter();
3493 let lookup_range = range("A1:A2", 1, 1, 2, 1);
3494 let return_range = range("B1:B2", 1, 2, 2, 2);
3495 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3496 let key_b = lit(LiteralValue::Text("b".into()));
3497 let args = vec![
3498 ArgumentHandle::new(&key_b, &ctx),
3499 ArgumentHandle::new(&lookup_range, &ctx),
3500 ArgumentHandle::new(&return_range, &ctx),
3501 ];
3502 let v = f
3503 .dispatch(&args, &ctx.function_context(None))
3504 .unwrap()
3505 .into_literal();
3506 assert_eq!(v, LiteralValue::Number(20.0));
3507 let key_missing = lit(LiteralValue::Text("z".into()));
3508 let if_nf = lit(LiteralValue::Text("NF".into()));
3509 let args_nf = vec![
3510 ArgumentHandle::new(&key_missing, &ctx),
3511 ArgumentHandle::new(&lookup_range, &ctx),
3512 ArgumentHandle::new(&return_range, &ctx),
3513 ArgumentHandle::new(&if_nf, &ctx),
3514 ];
3515 let v_nf = f
3516 .dispatch(&args_nf, &ctx.function_context(None))
3517 .unwrap()
3518 .into_literal();
3519 assert_eq!(v_nf, LiteralValue::Text("NF".into()));
3520 }
3521
3522 #[test]
3523 fn xlookup_match_modes_next_smaller_larger() {
3524 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3525 let wb = wb
3526 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3527 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
3528 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
3529 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(1))
3530 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(2))
3531 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(3));
3532 let ctx = wb.interpreter();
3533 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3534 let return_range = range("B1:B3", 1, 2, 3, 2);
3535 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3536 let needle_25 = lit(LiteralValue::Int(25));
3537 let mm_next_smaller = lit(LiteralValue::Int(-1));
3538 let nf_text = lit(LiteralValue::Text("NF".into()));
3539 let args_smaller = vec![
3540 ArgumentHandle::new(&needle_25, &ctx),
3541 ArgumentHandle::new(&lookup_range, &ctx),
3542 ArgumentHandle::new(&return_range, &ctx),
3543 ArgumentHandle::new(&nf_text, &ctx),
3544 ArgumentHandle::new(&mm_next_smaller, &ctx),
3545 ];
3546 let v_smaller = f
3547 .dispatch(&args_smaller, &ctx.function_context(None))
3548 .unwrap()
3549 .into_literal();
3550 assert_eq!(v_smaller, LiteralValue::Number(2.0));
3551 let mm_next_larger = lit(LiteralValue::Int(1));
3552 let nf_text2 = lit(LiteralValue::Text("NF".into()));
3553 let args_larger = vec![
3554 ArgumentHandle::new(&needle_25, &ctx),
3555 ArgumentHandle::new(&lookup_range, &ctx),
3556 ArgumentHandle::new(&return_range, &ctx),
3557 ArgumentHandle::new(&nf_text2, &ctx),
3558 ArgumentHandle::new(&mm_next_larger, &ctx),
3559 ];
3560 let v_larger = f
3561 .dispatch(&args_larger, &ctx.function_context(None))
3562 .unwrap()
3563 .into_literal();
3564 assert_eq!(v_larger, LiteralValue::Number(3.0));
3565 }
3566
3567 #[test]
3568 fn xlookup_wildcard_and_not_found_default_na() {
3569 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3570 let wb = wb
3571 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Alpha".into()))
3572 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Beta".into()))
3573 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Gamma".into()))
3574 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
3575 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
3576 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(300));
3577 let ctx = wb.interpreter();
3578 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3579 let return_range = range("B1:B3", 1, 2, 3, 2);
3580 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3581 let pattern = lit(LiteralValue::Text("*et*".into()));
3583 let match_mode_wild = lit(LiteralValue::Int(2));
3584 let nf_binding = lit(LiteralValue::Text("NF".into()));
3585 let args_wild = vec![
3586 ArgumentHandle::new(&pattern, &ctx),
3587 ArgumentHandle::new(&lookup_range, &ctx),
3588 ArgumentHandle::new(&return_range, &ctx),
3589 ArgumentHandle::new(&nf_binding, &ctx),
3590 ArgumentHandle::new(&match_mode_wild, &ctx),
3591 ];
3592 let v_wild = f
3593 .dispatch(&args_wild, &ctx.function_context(None))
3594 .unwrap()
3595 .into_literal();
3596 assert_eq!(v_wild, LiteralValue::Number(200.0));
3597 let pattern_lit_star = lit(LiteralValue::Text("~*eta".into()));
3599 let args_lit = vec![
3600 ArgumentHandle::new(&pattern_lit_star, &ctx),
3601 ArgumentHandle::new(&lookup_range, &ctx),
3602 ArgumentHandle::new(&return_range, &ctx),
3603 ArgumentHandle::new(&nf_binding, &ctx),
3604 ArgumentHandle::new(&match_mode_wild, &ctx),
3605 ];
3606 let v_lit = f
3607 .dispatch(&args_lit, &ctx.function_context(None))
3608 .unwrap()
3609 .into_literal();
3610 match v_lit {
3611 LiteralValue::Text(s) => assert_eq!(s, "NF"),
3612 other => panic!("expected NF text got {other:?}"),
3613 }
3614 let missing = lit(LiteralValue::Text("Zeta".into()));
3616 let args_nf = vec![
3617 ArgumentHandle::new(&missing, &ctx),
3618 ArgumentHandle::new(&lookup_range, &ctx),
3619 ArgumentHandle::new(&return_range, &ctx),
3620 ];
3621 let v_nf = f
3622 .dispatch(&args_nf, &ctx.function_context(None))
3623 .unwrap()
3624 .into_literal();
3625 match v_nf {
3626 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Na),
3627 other => panic!("expected #N/A got {other:?}"),
3628 }
3629 }
3630
3631 #[test]
3632 fn xlookup_unicode_exact_and_wildcard_are_case_insensitive() {
3633 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3634 let wb = wb
3635 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("ИВАН".into()))
3636 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Петр".into()))
3637 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Иванов".into()))
3638 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
3639 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
3640 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(300));
3641 let ctx = wb.interpreter();
3642 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3643 let return_range = range("B1:B3", 1, 2, 3, 2);
3644 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3645 let nf = lit(LiteralValue::Text("NF".into()));
3646
3647 let exact = lit(LiteralValue::Text("иван".into()));
3648 let exact_args = vec![
3649 ArgumentHandle::new(&exact, &ctx),
3650 ArgumentHandle::new(&lookup_range, &ctx),
3651 ArgumentHandle::new(&return_range, &ctx),
3652 ArgumentHandle::new(&nf, &ctx),
3653 ];
3654 let exact_v = f
3655 .dispatch(&exact_args, &ctx.function_context(None))
3656 .unwrap()
3657 .into_literal();
3658 assert_eq!(exact_v, LiteralValue::Number(100.0));
3659
3660 let wildcard = lit(LiteralValue::Text("ив?н*".into()));
3661 let wildcard_mode = lit(LiteralValue::Int(2));
3662 let wildcard_args = vec![
3663 ArgumentHandle::new(&wildcard, &ctx),
3664 ArgumentHandle::new(&lookup_range, &ctx),
3665 ArgumentHandle::new(&return_range, &ctx),
3666 ArgumentHandle::new(&nf, &ctx),
3667 ArgumentHandle::new(&wildcard_mode, &ctx),
3668 ];
3669 let wildcard_v = f
3670 .dispatch(&wildcard_args, &ctx.function_context(None))
3671 .unwrap()
3672 .into_literal();
3673 assert_eq!(wildcard_v, LiteralValue::Number(100.0));
3674 }
3675
3676 #[test]
3677 fn xlookup_unicode_reverse_search_uses_prepared_matcher() {
3678 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3679 let wb = wb
3680 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("ИВАН".into()))
3681 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Петр".into()))
3682 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Иванов".into()))
3683 .with_cell_a1("Sheet1", "A4", LiteralValue::Text("ИВАН".into()))
3684 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
3685 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
3686 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(300))
3687 .with_cell_a1("Sheet1", "B4", LiteralValue::Int(400));
3688 let ctx = wb.interpreter();
3689 let lookup_range = range("A1:A4", 1, 1, 4, 1);
3690 let return_range = range("B1:B4", 1, 2, 4, 2);
3691 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3692 let nf = lit(LiteralValue::Text("NF".into()));
3693 let reverse = lit(LiteralValue::Int(-1));
3694 let exact_mode = lit(LiteralValue::Int(0));
3695
3696 let exact = lit(LiteralValue::Text("иван".into()));
3697 let exact_args = vec![
3698 ArgumentHandle::new(&exact, &ctx),
3699 ArgumentHandle::new(&lookup_range, &ctx),
3700 ArgumentHandle::new(&return_range, &ctx),
3701 ArgumentHandle::new(&nf, &ctx),
3702 ArgumentHandle::new(&exact_mode, &ctx),
3703 ArgumentHandle::new(&reverse, &ctx),
3704 ];
3705 let exact_v = f
3706 .dispatch(&exact_args, &ctx.function_context(None))
3707 .unwrap()
3708 .into_literal();
3709 assert_eq!(exact_v, LiteralValue::Number(400.0));
3710
3711 let wildcard = lit(LiteralValue::Text("ив?н*".into()));
3712 let wildcard_mode = lit(LiteralValue::Int(2));
3713 let wildcard_args = vec![
3714 ArgumentHandle::new(&wildcard, &ctx),
3715 ArgumentHandle::new(&lookup_range, &ctx),
3716 ArgumentHandle::new(&return_range, &ctx),
3717 ArgumentHandle::new(&nf, &ctx),
3718 ArgumentHandle::new(&wildcard_mode, &ctx),
3719 ArgumentHandle::new(&reverse, &ctx),
3720 ];
3721 let wildcard_v = f
3722 .dispatch(&wildcard_args, &ctx.function_context(None))
3723 .unwrap()
3724 .into_literal();
3725 assert_eq!(wildcard_v, LiteralValue::Number(400.0));
3726 }
3727
3728 #[test]
3729 fn xlookup_reverse_search_mode_picks_last() {
3730 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3731 let wb = wb
3732 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3733 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3734 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(1))
3735 .with_cell_a1("Sheet1", "B1", LiteralValue::Text("First".into()))
3736 .with_cell_a1("Sheet1", "B2", LiteralValue::Text("Mid".into()))
3737 .with_cell_a1("Sheet1", "B3", LiteralValue::Text("Last".into()));
3738 let ctx = wb.interpreter();
3739 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3740 let return_range = range("B1:B3", 1, 2, 3, 2);
3741 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3742 let needle_one = lit(LiteralValue::Int(1));
3743 let search_rev = lit(LiteralValue::Int(-1));
3744 let nf_binding2 = lit(LiteralValue::Text("NF".into()));
3745 let match_mode_zero = lit(LiteralValue::Int(0));
3746 let args_rev = vec![
3747 ArgumentHandle::new(&needle_one, &ctx),
3748 ArgumentHandle::new(&lookup_range, &ctx),
3749 ArgumentHandle::new(&return_range, &ctx),
3750 ArgumentHandle::new(&nf_binding2, &ctx),
3751 ArgumentHandle::new(&match_mode_zero, &ctx),
3752 ArgumentHandle::new(&search_rev, &ctx),
3753 ];
3754 let v_rev = f
3755 .dispatch(&args_rev, &ctx.function_context(None))
3756 .unwrap()
3757 .into_literal();
3758 assert_eq!(v_rev, LiteralValue::Text("Last".into()));
3759 }
3760
3761 #[test]
3762 fn xlookup_horizontal_returns_column_vector_for_matrix_return() {
3763 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3764 let wb = wb
3765 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3766 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(20))
3767 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(30))
3768 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(1))
3769 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(2))
3770 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(3))
3771 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(4))
3772 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(5))
3773 .with_cell_a1("Sheet1", "C3", LiteralValue::Int(6));
3774 let ctx = wb.interpreter();
3775 let lookup_range = range("A1:C1", 1, 1, 1, 3);
3776 let return_range = range("A2:C3", 2, 1, 3, 3);
3777 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3778 let needle = lit(LiteralValue::Int(20));
3779 let args = vec![
3780 ArgumentHandle::new(&needle, &ctx),
3781 ArgumentHandle::new(&lookup_range, &ctx),
3782 ArgumentHandle::new(&return_range, &ctx),
3783 ];
3784 let v = f
3785 .dispatch(&args, &ctx.function_context(None))
3786 .unwrap()
3787 .into_literal();
3788 match v {
3789 LiteralValue::Array(a) => {
3790 assert_eq!(
3791 a,
3792 vec![
3793 vec![LiteralValue::Number(2.0)],
3794 vec![LiteralValue::Number(5.0)]
3795 ]
3796 );
3797 }
3798 other => panic!("expected array got {other:?}"),
3799 }
3800 }
3801
3802 #[test]
3803 fn xlookup_vertical_returns_row_vector_for_matrix_return() {
3804 let wb = TestWorkbook::new().with_function(Arc::new(XLookupFn));
3805 let wb = wb
3806 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
3807 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
3808 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
3809 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(101))
3810 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(102))
3811 .with_cell_a1("Sheet1", "D1", LiteralValue::Int(103))
3812 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(201))
3813 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(202))
3814 .with_cell_a1("Sheet1", "D2", LiteralValue::Int(203))
3815 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(301))
3816 .with_cell_a1("Sheet1", "C3", LiteralValue::Int(302))
3817 .with_cell_a1("Sheet1", "D3", LiteralValue::Int(303));
3818 let ctx = wb.interpreter();
3819 let lookup_range = range("A1:A3", 1, 1, 3, 1);
3820 let return_range = range("B1:D3", 1, 2, 3, 4);
3821 let f = ctx.context.get_function("", "XLOOKUP").unwrap();
3822 let needle = lit(LiteralValue::Int(20));
3823 let args = vec![
3824 ArgumentHandle::new(&needle, &ctx),
3825 ArgumentHandle::new(&lookup_range, &ctx),
3826 ArgumentHandle::new(&return_range, &ctx),
3827 ];
3828 let v = f
3829 .dispatch(&args, &ctx.function_context(None))
3830 .unwrap()
3831 .into_literal();
3832 match v {
3833 LiteralValue::Array(a) => {
3834 assert_eq!(
3835 a,
3836 vec![vec![
3837 LiteralValue::Number(201.0),
3838 LiteralValue::Number(202.0),
3839 LiteralValue::Number(203.0)
3840 ]]
3841 );
3842 }
3843 other => panic!("expected array got {other:?}"),
3844 }
3845 }
3846
3847 #[test]
3848 fn filter_basic_and_if_empty() {
3849 let wb = TestWorkbook::new().with_function(Arc::new(FilterFn));
3850 let wb = wb
3851 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3852 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3853 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3854 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
3855 .with_cell_a1("Sheet1", "C1", LiteralValue::Boolean(true))
3856 .with_cell_a1("Sheet1", "C2", LiteralValue::Boolean(false));
3857 let ctx = wb.interpreter();
3858 let array_range = range("A1:B2", 1, 1, 2, 2);
3859 let include_range = range("C1:C2", 1, 3, 2, 3);
3860 let f = ctx.context.get_function("", "FILTER").unwrap();
3861 let args = vec![
3862 ArgumentHandle::new(&array_range, &ctx),
3863 ArgumentHandle::new(&include_range, &ctx),
3864 ];
3865 let v = f
3866 .dispatch(&args, &ctx.function_context(None))
3867 .unwrap()
3868 .into_literal();
3869 match v {
3870 LiteralValue::Array(a) => {
3871 assert_eq!(a.len(), 1);
3872 assert_eq!(
3873 a[0],
3874 vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)]
3875 );
3876 }
3877 other => panic!("expected array got {other:?}"),
3878 }
3879 let wb2 = wb
3880 .with_cell_a1("Sheet1", "C1", LiteralValue::Boolean(false))
3881 .with_cell_a1("Sheet1", "C2", LiteralValue::Boolean(false));
3882 let ctx2 = wb2.interpreter();
3883 let f2 = ctx2.context.get_function("", "FILTER").unwrap();
3884 let empty_text = lit(LiteralValue::Text("EMPTY".into()));
3885 let args_empty = vec![
3886 ArgumentHandle::new(&array_range, &ctx2),
3887 ArgumentHandle::new(&include_range, &ctx2),
3888 ArgumentHandle::new(&empty_text, &ctx2),
3889 ];
3890 let v_empty = f2
3891 .dispatch(&args_empty, &ctx2.function_context(None))
3892 .unwrap()
3893 .into_literal();
3894 assert_eq!(v_empty, LiteralValue::Text("EMPTY".into()));
3895 }
3896
3897 #[test]
3898 fn unique_basic_and_exactly_once() {
3899 let wb = TestWorkbook::new().with_function(Arc::new(UniqueFn));
3900 let wb = wb
3901 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3902 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(1))
3903 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(2))
3904 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(3));
3905 let ctx = wb.interpreter();
3906 let range = range("A1:A4", 1, 1, 4, 1);
3907 let f = ctx.context.get_function("", "UNIQUE").unwrap();
3908 let args = vec![ArgumentHandle::new(&range, &ctx)];
3909 let v = f
3910 .dispatch(&args, &ctx.function_context(None))
3911 .unwrap()
3912 .into_literal();
3913 match v {
3914 LiteralValue::Array(a) => {
3915 assert_eq!(a.len(), 3);
3916 assert_eq!(a[0][0], LiteralValue::Number(1.0));
3917 }
3918 _ => panic!("expected array"),
3919 }
3920 }
3921
3922 #[test]
3923 fn sequence_basic_rows_cols_step() {
3924 let wb = TestWorkbook::new().with_function(Arc::new(SequenceFn));
3925 let ctx = wb.interpreter();
3926 let f = ctx.context.get_function("", "SEQUENCE").unwrap();
3927 let rows = lit(LiteralValue::Int(2));
3928 let cols = lit(LiteralValue::Int(3));
3929 let start = lit(LiteralValue::Int(5));
3930 let step = lit(LiteralValue::Int(2));
3931 let args = vec![
3932 ArgumentHandle::new(&rows, &ctx),
3933 ArgumentHandle::new(&cols, &ctx),
3934 ArgumentHandle::new(&start, &ctx),
3935 ArgumentHandle::new(&step, &ctx),
3936 ];
3937 let v = f
3938 .dispatch(&args, &ctx.function_context(None))
3939 .unwrap()
3940 .into_literal();
3941 match v {
3942 LiteralValue::Array(a) => {
3943 assert_eq!(a.len(), 2);
3944 assert_eq!(a[0][0], LiteralValue::Number(5.0));
3945 }
3946 other => panic!("expected array got {other:?}"),
3947 }
3948 }
3949
3950 #[test]
3951 fn transpose_basic() {
3952 let wb = TestWorkbook::new().with_function(Arc::new(TransposeFn));
3953 let wb = wb
3954 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3955 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
3956 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
3957 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20));
3958 let ctx = wb.interpreter();
3959 let arr = range("A1:B2", 1, 1, 2, 2);
3960 let f = ctx.context.get_function("", "TRANSPOSE").unwrap();
3961 let args = vec![ArgumentHandle::new(&arr, &ctx)];
3962 let v = f
3963 .dispatch(&args, &ctx.function_context(None))
3964 .unwrap()
3965 .into_literal();
3966 match v {
3967 LiteralValue::Array(a) => {
3968 assert_eq!(a.len(), 2);
3969 assert_eq!(
3970 a[0],
3971 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)]
3972 );
3973 }
3974 other => panic!("expected array got {other:?}"),
3975 }
3976 }
3977
3978 #[test]
3979 fn take_basic() {
3980 let wb = TestWorkbook::new().with_function(Arc::new(TakeFn));
3981 let wb = wb
3982 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
3983 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2));
3984 let ctx = wb.interpreter();
3985 let arr = range("A1:A2", 1, 1, 2, 1);
3986 let f = ctx.context.get_function("", "TAKE").unwrap();
3987 let one = lit(LiteralValue::Int(1));
3988 let args = vec![
3989 ArgumentHandle::new(&arr, &ctx),
3990 ArgumentHandle::new(&one, &ctx),
3991 ];
3992 let v = f
3993 .dispatch(&args, &ctx.function_context(None))
3994 .unwrap()
3995 .into_literal();
3996 assert_eq!(v, LiteralValue::Number(1.0));
3997 }
3998
3999 #[test]
4000 fn drop_basic() {
4001 let wb = TestWorkbook::new().with_function(Arc::new(DropFn));
4002 let wb = wb
4003 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4004 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2));
4005 let ctx = wb.interpreter();
4006 let arr = range("A1:A2", 1, 1, 2, 1);
4007 let f = ctx.context.get_function("", "DROP").unwrap();
4008 let one = lit(LiteralValue::Int(1));
4009 let args = vec![
4010 ArgumentHandle::new(&arr, &ctx),
4011 ArgumentHandle::new(&one, &ctx),
4012 ];
4013 let v = f
4014 .dispatch(&args, &ctx.function_context(None))
4015 .unwrap()
4016 .into_literal();
4017 assert_eq!(v, LiteralValue::Number(2.0));
4018 }
4019
4020 #[test]
4021 fn xmatch_exact_match_default() {
4022 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4023 let wb = wb
4024 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("apple".into()))
4025 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("banana".into()))
4026 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("cherry".into()));
4027 let ctx = wb.interpreter();
4028 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4029 let f = ctx.context.get_function("", "XMATCH").unwrap();
4030 let key = lit(LiteralValue::Text("banana".into()));
4031 let args = vec![
4032 ArgumentHandle::new(&key, &ctx),
4033 ArgumentHandle::new(&lookup_range, &ctx),
4034 ];
4035 let v = f
4036 .dispatch(&args, &ctx.function_context(None))
4037 .unwrap()
4038 .into_literal();
4039 assert_eq!(v, LiteralValue::Int(2));
4040 }
4041
4042 #[test]
4043 fn xmatch_exact_or_next_smaller() {
4044 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4045 let wb = wb
4046 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
4047 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
4048 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30));
4049 let ctx = wb.interpreter();
4050 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4051 let f = ctx.context.get_function("", "XMATCH").unwrap();
4052 let needle = lit(LiteralValue::Int(25));
4053 let match_mode = lit(LiteralValue::Int(-1)); let args = vec![
4055 ArgumentHandle::new(&needle, &ctx),
4056 ArgumentHandle::new(&lookup_range, &ctx),
4057 ArgumentHandle::new(&match_mode, &ctx),
4058 ];
4059 let v = f
4060 .dispatch(&args, &ctx.function_context(None))
4061 .unwrap()
4062 .into_literal();
4063 assert_eq!(v, LiteralValue::Int(2)); }
4065
4066 #[test]
4067 fn xmatch_exact_or_next_larger() {
4068 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4069 let wb = wb
4070 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
4071 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
4072 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30));
4073 let ctx = wb.interpreter();
4074 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4075 let f = ctx.context.get_function("", "XMATCH").unwrap();
4076 let needle = lit(LiteralValue::Int(25));
4077 let match_mode = lit(LiteralValue::Int(1)); let args = vec![
4079 ArgumentHandle::new(&needle, &ctx),
4080 ArgumentHandle::new(&lookup_range, &ctx),
4081 ArgumentHandle::new(&match_mode, &ctx),
4082 ];
4083 let v = f
4084 .dispatch(&args, &ctx.function_context(None))
4085 .unwrap()
4086 .into_literal();
4087 assert_eq!(v, LiteralValue::Int(3)); }
4089
4090 #[test]
4091 fn xmatch_wildcard() {
4092 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4093 let wb = wb
4094 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("alpha".into()))
4095 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("beta".into()))
4096 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("gamma".into()));
4097 let ctx = wb.interpreter();
4098 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4099 let f = ctx.context.get_function("", "XMATCH").unwrap();
4100 let pattern = lit(LiteralValue::Text("*eta".into()));
4101 let match_mode = lit(LiteralValue::Int(2)); let args = vec![
4103 ArgumentHandle::new(&pattern, &ctx),
4104 ArgumentHandle::new(&lookup_range, &ctx),
4105 ArgumentHandle::new(&match_mode, &ctx),
4106 ];
4107 let v = f
4108 .dispatch(&args, &ctx.function_context(None))
4109 .unwrap()
4110 .into_literal();
4111 assert_eq!(v, LiteralValue::Int(2)); }
4113
4114 #[test]
4115 fn xmatch_unicode_wildcard_is_case_insensitive() {
4116 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4117 let wb = wb
4118 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("ИВАН".into()))
4119 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Петр".into()))
4120 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Иванов".into()));
4121 let ctx = wb.interpreter();
4122 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4123 let f = ctx.context.get_function("", "XMATCH").unwrap();
4124 let pattern = lit(LiteralValue::Text("ив?н*".into()));
4125 let match_mode = lit(LiteralValue::Int(2));
4126 let args = vec![
4127 ArgumentHandle::new(&pattern, &ctx),
4128 ArgumentHandle::new(&lookup_range, &ctx),
4129 ArgumentHandle::new(&match_mode, &ctx),
4130 ];
4131 let v = f
4132 .dispatch(&args, &ctx.function_context(None))
4133 .unwrap()
4134 .into_literal();
4135 assert_eq!(v, LiteralValue::Int(1));
4136 }
4137
4138 #[test]
4139 fn xmatch_reverse_search() {
4140 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4141 let wb = wb
4142 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4143 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
4144 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(1)); let ctx = wb.interpreter();
4146 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4147 let f = ctx.context.get_function("", "XMATCH").unwrap();
4148 let needle = lit(LiteralValue::Int(1));
4149 let match_mode = lit(LiteralValue::Int(0));
4150 let search_mode = lit(LiteralValue::Int(-1)); let args = vec![
4152 ArgumentHandle::new(&needle, &ctx),
4153 ArgumentHandle::new(&lookup_range, &ctx),
4154 ArgumentHandle::new(&match_mode, &ctx),
4155 ArgumentHandle::new(&search_mode, &ctx),
4156 ];
4157 let v = f
4158 .dispatch(&args, &ctx.function_context(None))
4159 .unwrap()
4160 .into_literal();
4161 assert_eq!(v, LiteralValue::Int(3)); }
4163
4164 #[test]
4165 fn xmatch_not_found() {
4166 let wb = TestWorkbook::new().with_function(Arc::new(XMatchFn));
4167 let wb = wb
4168 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
4169 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
4170 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(3));
4171 let ctx = wb.interpreter();
4172 let lookup_range = range("A1:A3", 1, 1, 3, 1);
4173 let f = ctx.context.get_function("", "XMATCH").unwrap();
4174 let needle = lit(LiteralValue::Int(5));
4175 let args = vec![
4176 ArgumentHandle::new(&needle, &ctx),
4177 ArgumentHandle::new(&lookup_range, &ctx),
4178 ];
4179 let v = f
4180 .dispatch(&args, &ctx.function_context(None))
4181 .unwrap()
4182 .into_literal();
4183 match v {
4184 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Na),
4185 other => panic!("expected #N/A got {other:?}"),
4186 }
4187 }
4188
4189 #[test]
4190 fn sort_basic_ascending() {
4191 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4192 let wb = wb
4193 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(30))
4194 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
4195 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20));
4196 let ctx = wb.interpreter();
4197 let arr = range("A1:A3", 1, 1, 3, 1);
4198 let f = ctx.context.get_function("", "SORT").unwrap();
4199 let args = vec![ArgumentHandle::new(&arr, &ctx)];
4200 let v = f
4201 .dispatch(&args, &ctx.function_context(None))
4202 .unwrap()
4203 .into_literal();
4204 match v {
4205 LiteralValue::Array(a) => {
4206 assert_eq!(a.len(), 3);
4207 assert_eq!(a[0][0], LiteralValue::Number(10.0));
4208 assert_eq!(a[1][0], LiteralValue::Number(20.0));
4209 assert_eq!(a[2][0], LiteralValue::Number(30.0));
4210 }
4211 other => panic!("expected array got {other:?}"),
4212 }
4213 }
4214
4215 #[test]
4216 fn sort_descending() {
4217 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4218 let wb = wb
4219 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(30))
4220 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
4221 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20));
4222 let ctx = wb.interpreter();
4223 let arr = range("A1:A3", 1, 1, 3, 1);
4224 let f = ctx.context.get_function("", "SORT").unwrap();
4225 let sort_index = lit(LiteralValue::Int(1));
4226 let sort_order = lit(LiteralValue::Int(-1)); let args = vec![
4228 ArgumentHandle::new(&arr, &ctx),
4229 ArgumentHandle::new(&sort_index, &ctx),
4230 ArgumentHandle::new(&sort_order, &ctx),
4231 ];
4232 let v = f
4233 .dispatch(&args, &ctx.function_context(None))
4234 .unwrap()
4235 .into_literal();
4236 match v {
4237 LiteralValue::Array(a) => {
4238 assert_eq!(a.len(), 3);
4239 assert_eq!(a[0][0], LiteralValue::Number(30.0));
4240 assert_eq!(a[1][0], LiteralValue::Number(20.0));
4241 assert_eq!(a[2][0], LiteralValue::Number(10.0));
4242 }
4243 other => panic!("expected array got {other:?}"),
4244 }
4245 }
4246
4247 #[test]
4248 fn sort_by_column() {
4249 let wb = TestWorkbook::new().with_function(Arc::new(SortFn));
4250 let wb = wb
4251 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4252 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(30))
4253 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4254 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(10))
4255 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4256 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(20));
4257 let ctx = wb.interpreter();
4258 let arr = range("A1:B3", 1, 1, 3, 2);
4259 let f = ctx.context.get_function("", "SORT").unwrap();
4260 let sort_index = lit(LiteralValue::Int(2)); let args = vec![
4262 ArgumentHandle::new(&arr, &ctx),
4263 ArgumentHandle::new(&sort_index, &ctx),
4264 ];
4265 let v = f
4266 .dispatch(&args, &ctx.function_context(None))
4267 .unwrap()
4268 .into_literal();
4269 match v {
4270 LiteralValue::Array(a) => {
4271 assert_eq!(a.len(), 3);
4272 assert_eq!(a[0][0], LiteralValue::Text("Alice".into()));
4274 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4275 assert_eq!(a[2][0], LiteralValue::Text("Charlie".into()));
4276 }
4277 other => panic!("expected array got {other:?}"),
4278 }
4279 }
4280
4281 #[test]
4282 fn sortby_basic() {
4283 let wb = TestWorkbook::new().with_function(Arc::new(SortByFn));
4284 let wb = wb
4285 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4286 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4287 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4288 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(3))
4289 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(1))
4290 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(2));
4291 let ctx = wb.interpreter();
4292 let arr = range("A1:A3", 1, 1, 3, 1);
4293 let by_arr = range("B1:B3", 1, 2, 3, 2);
4294 let f = ctx.context.get_function("", "SORTBY").unwrap();
4295 let args = vec![
4296 ArgumentHandle::new(&arr, &ctx),
4297 ArgumentHandle::new(&by_arr, &ctx),
4298 ];
4299 let v = f
4300 .dispatch(&args, &ctx.function_context(None))
4301 .unwrap()
4302 .into_literal();
4303 match v {
4304 LiteralValue::Array(a) => {
4305 assert_eq!(a.len(), 3);
4306 assert_eq!(a[0][0], LiteralValue::Text("Alice".into()));
4308 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4309 assert_eq!(a[2][0], LiteralValue::Text("Charlie".into()));
4310 }
4311 other => panic!("expected array got {other:?}"),
4312 }
4313 }
4314
4315 #[test]
4316 fn sortby_descending() {
4317 let wb = TestWorkbook::new().with_function(Arc::new(SortByFn));
4318 let wb = wb
4319 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Charlie".into()))
4320 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Alice".into()))
4321 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("Bob".into()))
4322 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(3))
4323 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(1))
4324 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(2));
4325 let ctx = wb.interpreter();
4326 let arr = range("A1:A3", 1, 1, 3, 1);
4327 let by_arr = range("B1:B3", 1, 2, 3, 2);
4328 let sort_order = lit(LiteralValue::Int(-1)); let f = ctx.context.get_function("", "SORTBY").unwrap();
4330 let args = vec![
4331 ArgumentHandle::new(&arr, &ctx),
4332 ArgumentHandle::new(&by_arr, &ctx),
4333 ArgumentHandle::new(&sort_order, &ctx),
4334 ];
4335 let v = f
4336 .dispatch(&args, &ctx.function_context(None))
4337 .unwrap()
4338 .into_literal();
4339 match v {
4340 LiteralValue::Array(a) => {
4341 assert_eq!(a.len(), 3);
4342 assert_eq!(a[0][0], LiteralValue::Text("Charlie".into()));
4344 assert_eq!(a[1][0], LiteralValue::Text("Bob".into()));
4345 assert_eq!(a[2][0], LiteralValue::Text("Alice".into()));
4346 }
4347 other => panic!("expected array got {other:?}"),
4348 }
4349 }
4350
4351 #[test]
4352 fn randarray_basic() {
4353 let wb = TestWorkbook::new().with_function(Arc::new(RandArrayFn));
4354 let ctx = wb.interpreter();
4355 let f = ctx.context.get_function("", "RANDARRAY").unwrap();
4356
4357 let rows = lit(LiteralValue::Int(2));
4359 let cols = lit(LiteralValue::Int(3));
4360 let args = vec![
4361 ArgumentHandle::new(&rows, &ctx),
4362 ArgumentHandle::new(&cols, &ctx),
4363 ];
4364 let v = f
4365 .dispatch(&args, &ctx.function_context(None))
4366 .unwrap()
4367 .into_literal();
4368 match v {
4369 LiteralValue::Array(a) => {
4370 assert_eq!(a.len(), 2);
4371 assert_eq!(a[0].len(), 3);
4372 for row in &a {
4374 for cell in row {
4375 match cell {
4376 LiteralValue::Number(n) => {
4377 assert!(*n >= 0.0 && *n < 1.0, "Value {n} not in [0, 1)");
4378 }
4379 other => panic!("expected Number got {other:?}"),
4380 }
4381 }
4382 }
4383 }
4384 other => panic!("expected array got {other:?}"),
4385 }
4386 }
4387
4388 #[test]
4389 fn randarray_whole_numbers() {
4390 let wb = TestWorkbook::new().with_function(Arc::new(RandArrayFn));
4391 let ctx = wb.interpreter();
4392 let f = ctx.context.get_function("", "RANDARRAY").unwrap();
4393
4394 let rows = lit(LiteralValue::Int(3));
4396 let cols = lit(LiteralValue::Int(2));
4397 let min = lit(LiteralValue::Int(1));
4398 let max = lit(LiteralValue::Int(10));
4399 let whole = lit(LiteralValue::Boolean(true));
4400 let args = vec![
4401 ArgumentHandle::new(&rows, &ctx),
4402 ArgumentHandle::new(&cols, &ctx),
4403 ArgumentHandle::new(&min, &ctx),
4404 ArgumentHandle::new(&max, &ctx),
4405 ArgumentHandle::new(&whole, &ctx),
4406 ];
4407 let v = f
4408 .dispatch(&args, &ctx.function_context(None))
4409 .unwrap()
4410 .into_literal();
4411 match v {
4412 LiteralValue::Array(a) => {
4413 assert_eq!(a.len(), 3);
4414 assert_eq!(a[0].len(), 2);
4415 for row in &a {
4417 for cell in row {
4418 let n = match cell {
4419 LiteralValue::Int(n) => *n as f64,
4420 LiteralValue::Number(n) => *n,
4421 other => panic!("expected Int or Number got {other:?}"),
4422 };
4423 assert!((1.0..=10.0).contains(&n), "Value {n} not in [1, 10]");
4424 assert!(n.fract() == 0.0, "Value {n} is not a whole number");
4426 }
4427 }
4428 }
4429 other => panic!("expected array got {other:?}"),
4430 }
4431 }
4432}