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