1use super::lookup_utils::{cmp_for_lookup, find_exact_index, is_sorted_ascending};
13use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
14use crate::function::Function;
15use crate::traits::{ArgumentHandle, FunctionContext};
16use formualizer_common::ArgKind;
17use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
18use formualizer_macros::func_caps;
19
20fn binary_search_match(slice: &[LiteralValue], needle: &LiteralValue, mode: i32) -> Option<usize> {
21 if mode == 0 || slice.is_empty() {
22 return None;
23 }
24 if mode == 1 {
26 let mut lo = 0usize;
28 let mut hi = slice.len();
29 while lo < hi {
30 let mid = (lo + hi) / 2;
31 match cmp_for_lookup(&slice[mid], needle) {
32 Some(c) => {
33 if c > 0 {
34 hi = mid;
35 } else {
36 lo = mid + 1;
37 }
38 }
39 None => {
40 hi = mid;
41 }
42 }
43 }
44 if lo == 0 { None } else { Some(lo - 1) }
45 } else {
46 let mut best: Option<usize> = None;
48 for (i, v) in slice.iter().enumerate() {
49 if let Some(c) = cmp_for_lookup(v, needle) {
50 if c == 0 {
51 return Some(i);
52 }
53 if c >= 0 && best.is_none_or(|b| i < b) {
54 best = Some(i);
55 }
56 }
57 }
58 best
59 }
60}
61
62#[derive(Debug)]
63pub struct MatchFn;
64impl Function for MatchFn {
120 fn name(&self) -> &'static str {
121 "MATCH"
122 }
123 fn min_args(&self) -> usize {
124 2
125 }
126 func_caps!(PURE, LOOKUP);
127 fn arg_schema(&self) -> &'static [ArgSchema] {
128 use once_cell::sync::Lazy;
129 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
130 vec![
131 ArgSchema {
133 kinds: smallvec::smallvec![ArgKind::Any],
134 required: true,
135 by_ref: false,
136 shape: ShapeKind::Scalar,
137 coercion: CoercionPolicy::None,
138 max: None,
139 repeating: None,
140 default: None,
141 },
142 ArgSchema {
144 kinds: smallvec::smallvec![ArgKind::Any],
145 required: true,
146 by_ref: false,
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::Number],
156 required: false,
157 by_ref: false,
158 shape: ShapeKind::Scalar,
159 coercion: CoercionPolicy::NumberLenientText,
160 max: None,
161 repeating: None,
162 default: Some(LiteralValue::Number(1.0)),
163 },
164 ]
165 });
166 &SCHEMA
167 }
168 fn eval<'a, 'b, 'c>(
169 &self,
170 args: &'c [ArgumentHandle<'a, 'b>],
171 ctx: &dyn FunctionContext<'b>,
172 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
173 if args.len() < 2 {
174 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
175 ExcelError::new(ExcelErrorKind::Na),
176 )));
177 }
178 let cv = args[0].value()?;
179 let lookup_value = cv.into_literal();
180 if let LiteralValue::Error(e) = lookup_value {
181 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
182 }
183 let mut match_type = 1.0; if args.len() >= 3 {
185 let mt_val = args[2].value()?.into_literal();
186 if let LiteralValue::Error(e) = mt_val {
187 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
188 }
189 match mt_val {
190 LiteralValue::Number(n) => match_type = n,
191 LiteralValue::Int(i) => match_type = i as f64,
192 LiteralValue::Text(s) => {
193 if let Ok(n) = s.parse::<f64>() {
194 match_type = n;
195 }
196 }
197 _ => {}
198 }
199 }
200 let mt = if match_type > 0.0 {
201 1
202 } else if match_type < 0.0 {
203 -1
204 } else {
205 0
206 };
207 let arr_ref = args[1].as_reference_or_eval().ok();
208 if let Some(r) = arr_ref {
209 let current_sheet = ctx.current_sheet();
210 match ctx.resolve_range_view(&r, current_sheet) {
211 Ok(rv) => {
212 if mt == 0 {
213 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
214 if let Some(idx) = super::lookup_utils::find_exact_index_in_view(
215 &rv,
216 &lookup_value,
217 wildcard_mode,
218 )? {
219 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
220 (idx + 1) as i64,
221 )));
222 }
223 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
224 ExcelError::new(ExcelErrorKind::Na),
225 )));
226 }
227
228 let mut values: Vec<LiteralValue> = Vec::new();
230 if let Err(e) = rv.for_each_cell(&mut |v| {
231 values.push(v.clone());
232 Ok(())
233 }) {
234 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
235 }
236
237 let is_sorted = if mt == 1 {
239 is_sorted_ascending(&values)
240 } else if mt == -1 {
241 values
242 .windows(2)
243 .all(|w| cmp_for_lookup(&w[0], &w[1]).is_some_and(|c| c >= 0))
244 } else {
245 true
246 };
247 if !is_sorted {
248 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
249 ExcelError::new(ExcelErrorKind::Na),
250 )));
251 }
252 let idx = if values.len() < 8 {
253 let mut best: Option<(usize, &LiteralValue)> = None;
255 for (i, v) in values.iter().enumerate() {
256 if let Some(c) = cmp_for_lookup(v, &lookup_value) {
257 if mt == 1 {
259 if (c == 0 || c == -1)
261 && (best.is_none() || i > best.unwrap().0)
262 {
263 best = Some((i, v));
264 }
265 } else {
266 if (c == 0 || c == 1) && (best.is_none() || i > best.unwrap().0)
268 {
269 best = Some((i, v));
270 }
271 }
272 }
273 }
274 best.map(|(i, _)| i)
275 } else {
276 binary_search_match(&values, &lookup_value, mt)
277 };
278 match idx {
279 Some(i) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
280 (i + 1) as i64,
281 ))),
282 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
283 ExcelError::new(ExcelErrorKind::Na),
284 ))),
285 }
286 }
287 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
288 }
289 } else {
290 let v = args[1].value()?.into_literal();
292 let values: Vec<LiteralValue> = match v {
293 LiteralValue::Array(rows) => {
294 if rows.len() == 1 {
296 rows.into_iter().next().unwrap_or_default()
298 } else if rows.iter().all(|r| r.len() == 1) {
299 rows.into_iter()
301 .filter_map(|r| r.into_iter().next())
302 .collect()
303 } else {
304 rows.into_iter().flatten().collect()
306 }
307 }
308 other => vec![other],
309 };
310 let idx = if mt == 0 {
311 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
312 find_exact_index(&values, &lookup_value, wildcard_mode)
313 } else {
314 binary_search_match(&values, &lookup_value, mt)
315 };
316 match idx {
317 Some(i) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
318 (i + 1) as i64,
319 ))),
320 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
321 ExcelError::new(ExcelErrorKind::Na),
322 ))),
323 }
324 }
325 }
326}
327
328#[derive(Debug)]
329pub struct VLookupFn;
330impl Function for VLookupFn {
389 fn name(&self) -> &'static str {
390 "VLOOKUP"
391 }
392 fn min_args(&self) -> usize {
393 3
394 }
395 func_caps!(PURE, LOOKUP);
396 fn arg_schema(&self) -> &'static [ArgSchema] {
397 use once_cell::sync::Lazy;
398 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
399 vec![
400 ArgSchema {
402 kinds: smallvec::smallvec![ArgKind::Any],
403 required: true,
404 by_ref: false,
405 shape: ShapeKind::Scalar,
406 coercion: CoercionPolicy::None,
407 max: None,
408 repeating: None,
409 default: None,
410 },
411 ArgSchema {
413 kinds: smallvec::smallvec![ArgKind::Any],
414 required: true,
415 by_ref: false,
416 shape: ShapeKind::Range,
417 coercion: CoercionPolicy::None,
418 max: None,
419 repeating: None,
420 default: None,
421 },
422 ArgSchema {
424 kinds: smallvec::smallvec![ArgKind::Number],
425 required: true,
426 by_ref: false,
427 shape: ShapeKind::Scalar,
428 coercion: CoercionPolicy::NumberStrict,
429 max: None,
430 repeating: None,
431 default: None,
432 },
433 ArgSchema {
435 kinds: smallvec::smallvec![ArgKind::Logical],
436 required: false,
437 by_ref: false,
438 shape: ShapeKind::Scalar,
439 coercion: CoercionPolicy::Logical,
440 max: None,
441 repeating: None,
442 default: Some(LiteralValue::Boolean(false)),
443 },
444 ]
445 });
446 &SCHEMA
447 }
448 fn eval<'a, 'b, 'c>(
449 &self,
450 args: &'c [ArgumentHandle<'a, 'b>],
451 ctx: &dyn FunctionContext<'b>,
452 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
453 if args.len() < 3 {
454 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
455 ExcelError::new(ExcelErrorKind::Na),
456 )));
457 }
458 let lookup_value = args[0].value()?.into_literal();
459
460 let table_ref_opt = args[1].as_reference_or_eval().ok();
462 let col_index = match args[2].value()?.into_literal() {
463 LiteralValue::Int(i) => i,
464 LiteralValue::Number(n) => n as i64,
465 _ => {
466 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
467 ExcelError::new(ExcelErrorKind::Value),
468 )));
469 }
470 };
471 if col_index < 1 {
472 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
473 ExcelError::new(ExcelErrorKind::Value),
474 )));
475 }
476 let approximate = if args.len() >= 4 {
477 match args[3].value()?.into_literal() {
478 LiteralValue::Boolean(b) => b,
479 _ => true,
480 }
481 } else {
482 false };
484 if let Some(table_ref) = table_ref_opt {
486 let current_sheet = ctx.current_sheet();
487 let rv = ctx.resolve_range_view(&table_ref, current_sheet)?;
488 let (rows, cols) = rv.dims();
489 if col_index as usize > cols {
490 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
491 ExcelError::new(ExcelErrorKind::Ref),
492 )));
493 }
494
495 let first_col_view = rv.sub_view(0, 0, rows, 1);
496 let row_idx_opt = if !approximate {
497 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
498 super::lookup_utils::find_exact_index_in_view(
499 &first_col_view,
500 &lookup_value,
501 wildcard_mode,
502 )?
503 } else {
504 let mut first_col: Vec<LiteralValue> = Vec::new();
506 first_col_view.for_each_row(&mut |row| {
507 first_col.push(row[0].clone());
508 Ok(())
509 })?;
510 if first_col.is_empty() {
511 None
512 } else {
513 binary_search_match(&first_col, &lookup_value, 1)
514 }
515 };
516
517 match row_idx_opt {
518 Some(i) => {
519 let target_col_idx = (col_index - 1) as usize;
520 let v = rv.get_cell(i, target_col_idx);
521 let v = match v {
525 LiteralValue::Empty => LiteralValue::Number(0.0),
526 other => other,
527 };
528 Ok(crate::traits::CalcValue::Scalar(v))
529 }
530 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
531 ExcelError::new(ExcelErrorKind::Na),
532 ))),
533 }
534 } else {
535 let v = args[1].value()?.into_literal();
537 let table: Vec<Vec<LiteralValue>> = match v {
538 LiteralValue::Array(rows) => rows,
539 other => vec![vec![other]],
540 };
541 if table.is_empty() {
542 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
543 ExcelError::new(ExcelErrorKind::Na),
544 )));
545 }
546 let width = table.first().map(|r| r.len()).unwrap_or(0);
547 if col_index as usize > width {
548 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
549 ExcelError::new(ExcelErrorKind::Ref),
550 )));
551 }
552
553 let first_col: Vec<LiteralValue> =
555 table.iter().filter_map(|r| r.first().cloned()).collect();
556 let row_idx_opt = if !approximate {
557 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
558 find_exact_index(&first_col, &lookup_value, wildcard_mode)
559 } else {
560 binary_search_match(&first_col, &lookup_value, 1)
561 };
562
563 match row_idx_opt {
564 Some(i) => {
565 let target_col_idx = (col_index - 1) as usize;
566 let val = table
567 .get(i)
568 .and_then(|r| r.get(target_col_idx))
569 .cloned()
570 .unwrap_or(LiteralValue::Empty);
571 let val = match val {
572 LiteralValue::Empty => LiteralValue::Number(0.0),
573 other => other,
574 };
575 Ok(crate::traits::CalcValue::Scalar(val))
576 }
577 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
578 ExcelError::new(ExcelErrorKind::Na),
579 ))),
580 }
581 }
582 }
583}
584
585#[derive(Debug)]
586pub struct HLookupFn;
587impl Function for HLookupFn {
646 fn name(&self) -> &'static str {
647 "HLOOKUP"
648 }
649 fn min_args(&self) -> usize {
650 3
651 }
652 func_caps!(PURE, LOOKUP);
653 fn arg_schema(&self) -> &'static [ArgSchema] {
654 use once_cell::sync::Lazy;
655 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
656 vec![
657 ArgSchema {
659 kinds: smallvec::smallvec![ArgKind::Any],
660 required: true,
661 by_ref: false,
662 shape: ShapeKind::Scalar,
663 coercion: CoercionPolicy::None,
664 max: None,
665 repeating: None,
666 default: None,
667 },
668 ArgSchema {
670 kinds: smallvec::smallvec![ArgKind::Any],
671 required: true,
672 by_ref: false,
673 shape: ShapeKind::Range,
674 coercion: CoercionPolicy::None,
675 max: None,
676 repeating: None,
677 default: None,
678 },
679 ArgSchema {
681 kinds: smallvec::smallvec![ArgKind::Number],
682 required: true,
683 by_ref: false,
684 shape: ShapeKind::Scalar,
685 coercion: CoercionPolicy::NumberStrict,
686 max: None,
687 repeating: None,
688 default: None,
689 },
690 ArgSchema {
692 kinds: smallvec::smallvec![ArgKind::Logical],
693 required: false,
694 by_ref: false,
695 shape: ShapeKind::Scalar,
696 coercion: CoercionPolicy::Logical,
697 max: None,
698 repeating: None,
699 default: Some(LiteralValue::Boolean(false)),
700 },
701 ]
702 });
703 &SCHEMA
704 }
705 fn eval<'a, 'b, 'c>(
706 &self,
707 args: &'c [ArgumentHandle<'a, 'b>],
708 ctx: &dyn FunctionContext<'b>,
709 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
710 if args.len() < 3 {
711 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
712 ExcelError::new(ExcelErrorKind::Na),
713 )));
714 }
715 let lookup_value = args[0].value()?.into_literal();
716
717 let table_ref_opt = args[1].as_reference_or_eval().ok();
719 let row_index = match args[2].value()?.into_literal() {
720 LiteralValue::Int(i) => i,
721 LiteralValue::Number(n) => n as i64,
722 _ => {
723 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
724 ExcelError::new(ExcelErrorKind::Value),
725 )));
726 }
727 };
728 if row_index < 1 {
729 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
730 ExcelError::new(ExcelErrorKind::Value),
731 )));
732 }
733 let approximate = if args.len() >= 4 {
734 match args[3].value()?.into_literal() {
735 LiteralValue::Boolean(b) => b,
736 _ => true,
737 }
738 } else {
739 false
740 };
741 if let Some(table_ref) = table_ref_opt {
743 let current_sheet = ctx.current_sheet();
744 let rv = ctx.resolve_range_view(&table_ref, current_sheet)?;
745 let (rows, cols) = rv.dims();
746 if row_index as usize > rows {
747 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
748 ExcelError::new(ExcelErrorKind::Ref),
749 )));
750 }
751 let first_row_view = rv.sub_view(0, 0, 1, cols);
752 let col_idx_opt = if approximate {
753 let mut first_row: Vec<LiteralValue> = Vec::with_capacity(cols);
754 first_row_view.for_each_row(&mut |row| {
755 if first_row.is_empty() {
756 first_row.extend_from_slice(row);
757 }
758 Ok(())
759 })?;
760 binary_search_match(&first_row, &lookup_value, 1)
761 } else {
762 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
763 super::lookup_utils::find_exact_index_in_view(
764 &first_row_view,
765 &lookup_value,
766 wildcard_mode,
767 )?
768 };
769
770 match col_idx_opt {
771 Some(i) => {
772 let target_row_idx = (row_index - 1) as usize;
773 let v = rv.get_cell(target_row_idx, i);
774 let v = match v {
775 LiteralValue::Empty => LiteralValue::Number(0.0),
776 other => other,
777 };
778 Ok(crate::traits::CalcValue::Scalar(v))
779 }
780 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
781 ExcelError::new(ExcelErrorKind::Na),
782 ))),
783 }
784 } else {
785 let v = args[1].value()?.into_literal();
787 let table: Vec<Vec<LiteralValue>> = match v {
788 LiteralValue::Array(rows) => rows,
789 other => vec![vec![other]],
790 };
791 if table.is_empty() {
792 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
793 ExcelError::new(ExcelErrorKind::Na),
794 )));
795 }
796 let height = table.len();
797 if row_index as usize > height {
798 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
799 ExcelError::new(ExcelErrorKind::Ref),
800 )));
801 }
802
803 let first_row: Vec<LiteralValue> = table.first().cloned().unwrap_or_default();
805 let col_idx_opt = if approximate {
806 binary_search_match(&first_row, &lookup_value, 1)
807 } else {
808 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
809 find_exact_index(&first_row, &lookup_value, wildcard_mode)
810 };
811
812 match col_idx_opt {
813 Some(i) => {
814 let target_row_idx = (row_index - 1) as usize;
815 let val = table
816 .get(target_row_idx)
817 .and_then(|r| r.get(i))
818 .cloned()
819 .unwrap_or(LiteralValue::Empty);
820 let val = match val {
821 LiteralValue::Empty => LiteralValue::Number(0.0),
822 other => other,
823 };
824 Ok(crate::traits::CalcValue::Scalar(val))
825 }
826 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
827 ExcelError::new(ExcelErrorKind::Na),
828 ))),
829 }
830 }
831 }
832}
833
834pub fn register_builtins() {
835 use crate::function_registry::register_function;
836 use std::sync::Arc;
837 register_function(Arc::new(MatchFn));
838 register_function(Arc::new(VLookupFn));
839 register_function(Arc::new(HLookupFn));
840}
841
842#[cfg(test)]
843mod tests {
844 use super::*;
845 use crate::test_workbook::TestWorkbook;
846 use crate::traits::ArgumentHandle;
847 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
848 use std::sync::Arc;
849 fn lit(v: LiteralValue) -> ASTNode {
850 ASTNode::new(ASTNodeType::Literal(v), None)
851 }
852
853 #[test]
854 fn match_wildcard_and_descending_and_unsorted() {
855 let wb = TestWorkbook::new().with_function(Arc::new(MatchFn));
857 let wb = wb
858 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("foo".into()))
859 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("fob".into()))
860 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("bar".into()))
861 .with_cell_a1("Sheet1", "A4", LiteralValue::Text("baz".into()));
862 let ctx = wb.interpreter();
863 let range = ASTNode::new(
864 ASTNodeType::Reference {
865 original: "A1:A4".into(),
866 reference: ReferenceType::range(None, Some(1), Some(1), Some(4), Some(1)),
867 },
868 None,
869 );
870 let f = ctx.context.get_function("", "MATCH").unwrap();
871 let pat = lit(LiteralValue::Text("*o*".into()));
873 let zero = lit(LiteralValue::Int(0));
874 let args = vec![
875 ArgumentHandle::new(&pat, &ctx),
876 ArgumentHandle::new(&range, &ctx),
877 ArgumentHandle::new(&zero, &ctx),
878 ];
879 let v = f
880 .dispatch(&args, &ctx.function_context(None))
881 .unwrap()
882 .into_literal();
883 assert_eq!(v, LiteralValue::Int(1));
884 let pat2 = lit(LiteralValue::Text("b?z".into()));
886 let args2 = vec![
887 ArgumentHandle::new(&pat2, &ctx),
888 ArgumentHandle::new(&range, &ctx),
889 ArgumentHandle::new(&zero, &ctx),
890 ];
891 let v2 = f
892 .dispatch(&args2, &ctx.function_context(None))
893 .unwrap()
894 .into_literal();
895 assert_eq!(v2, LiteralValue::Int(4));
896 let pat3 = lit(LiteralValue::Text("z*".into()));
898 let args3 = vec![
899 ArgumentHandle::new(&pat3, &ctx),
900 ArgumentHandle::new(&range, &ctx),
901 ArgumentHandle::new(&zero, &ctx),
902 ];
903 let v3 = f
904 .dispatch(&args3, &ctx.function_context(None))
905 .unwrap()
906 .into_literal();
907 assert!(matches!(v3, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
908
909 let wb2 = TestWorkbook::new()
911 .with_function(Arc::new(MatchFn))
912 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(50))
913 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(40))
914 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
915 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(20))
916 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(10));
917 let ctx2 = wb2.interpreter();
918 let range2 = ASTNode::new(
919 ASTNodeType::Reference {
920 original: "A1:A5".into(),
921 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
922 },
923 None,
924 );
925 let minus1 = lit(LiteralValue::Int(-1));
926 let thirty = lit(LiteralValue::Int(30));
927 let args_desc = vec![
928 ArgumentHandle::new(&thirty, &ctx2),
929 ArgumentHandle::new(&range2, &ctx2),
930 ArgumentHandle::new(&minus1, &ctx2),
931 ];
932 let v_desc = f
933 .dispatch(&args_desc, &ctx2.function_context(None))
934 .unwrap()
935 .into_literal();
936 assert_eq!(v_desc, LiteralValue::Int(3));
937 let sixty = lit(LiteralValue::Int(60));
939 let args_desc2 = vec![
940 ArgumentHandle::new(&sixty, &ctx2),
941 ArgumentHandle::new(&range2, &ctx2),
942 ArgumentHandle::new(&minus1, &ctx2),
943 ];
944 let v_desc2 = f
945 .dispatch(&args_desc2, &ctx2.function_context(None))
946 .unwrap()
947 .into_literal();
948 assert!(matches!(v_desc2, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
949
950 let wb3 = TestWorkbook::new()
952 .with_function(Arc::new(MatchFn))
953 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
954 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(30))
955 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20))
956 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(40))
957 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(50));
958 let ctx3 = wb3.interpreter();
959 let range3 = ASTNode::new(
960 ASTNodeType::Reference {
961 original: "A1:A5".into(),
962 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
963 },
964 None,
965 );
966 let args_unsorted = vec![
967 ArgumentHandle::new(&thirty, &ctx3),
968 ArgumentHandle::new(&range3, &ctx3),
969 ];
970 let v_unsorted = f
971 .dispatch(&args_unsorted, &ctx3.function_context(None))
972 .unwrap()
973 .into_literal();
974 assert!(matches!(v_unsorted, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
975 let wb4 = TestWorkbook::new()
977 .with_function(Arc::new(MatchFn))
978 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(50))
979 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(30))
980 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(40))
981 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(20))
982 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(10));
983 let ctx4 = wb4.interpreter();
984 let range4 = ASTNode::new(
985 ASTNodeType::Reference {
986 original: "A1:A5".into(),
987 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
988 },
989 None,
990 );
991 let args_unsorted_desc = vec![
992 ArgumentHandle::new(&thirty, &ctx4),
993 ArgumentHandle::new(&range4, &ctx4),
994 ArgumentHandle::new(&minus1, &ctx4),
995 ];
996 let v_unsorted_desc = f
997 .dispatch(&args_unsorted_desc, &ctx4.function_context(None))
998 .unwrap()
999 .into_literal();
1000 assert!(matches!(v_unsorted_desc, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
1001 }
1002
1003 #[test]
1004 fn match_exact_and_approx() {
1005 let wb = TestWorkbook::new().with_function(Arc::new(MatchFn));
1006 let wb = wb
1007 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
1008 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
1009 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
1010 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(40))
1011 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(50));
1012 let ctx = wb.interpreter();
1013 let range = ASTNode::new(
1014 ASTNodeType::Reference {
1015 original: "A1:A5".into(),
1016 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
1017 },
1018 None,
1019 );
1020 let f = ctx.context.get_function("", "MATCH").unwrap();
1021 let thirty = lit(LiteralValue::Int(30));
1022 let zero = lit(LiteralValue::Int(0));
1023 let args = vec![
1024 ArgumentHandle::new(&thirty, &ctx),
1025 ArgumentHandle::new(&range, &ctx),
1026 ArgumentHandle::new(&zero, &ctx),
1027 ];
1028 let v = f
1029 .dispatch(&args, &ctx.function_context(None))
1030 .unwrap()
1031 .into_literal();
1032 assert_eq!(v, LiteralValue::Int(3));
1033 let thirty_seven = lit(LiteralValue::Int(37));
1034 let args = vec![
1035 ArgumentHandle::new(&thirty_seven, &ctx),
1036 ArgumentHandle::new(&range, &ctx),
1037 ];
1038 let v = f
1039 .dispatch(&args, &ctx.function_context(None))
1040 .unwrap()
1041 .into_literal();
1042 assert_eq!(v, LiteralValue::Int(3));
1043 }
1044
1045 #[test]
1046 fn vlookup_basic() {
1047 let wb = TestWorkbook::new()
1048 .with_function(Arc::new(VLookupFn))
1049 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Key1".into()))
1050 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Key2".into()))
1051 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
1052 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200));
1053 let ctx = wb.interpreter();
1054 let table = ASTNode::new(
1055 ASTNodeType::Reference {
1056 original: "A1:B2".into(),
1057 reference: ReferenceType::range(None, Some(1), Some(1), Some(2), Some(2)),
1058 },
1059 None,
1060 );
1061 let f = ctx.context.get_function("", "VLOOKUP").unwrap();
1062 let key2 = lit(LiteralValue::Text("Key2".into()));
1063 let two = lit(LiteralValue::Int(2));
1064 let false_lit = lit(LiteralValue::Boolean(false));
1065 let args = vec![
1066 ArgumentHandle::new(&key2, &ctx),
1067 ArgumentHandle::new(&table, &ctx),
1068 ArgumentHandle::new(&two, &ctx),
1069 ArgumentHandle::new(&false_lit, &ctx),
1070 ];
1071 let v = f
1072 .dispatch(&args, &ctx.function_context(None))
1073 .unwrap()
1074 .into_literal();
1075 assert_eq!(v, LiteralValue::Number(200.0));
1076 }
1077
1078 #[test]
1079 fn vlookup_named_range_reference() {
1080 let wb = TestWorkbook::new()
1081 .with_function(Arc::new(VLookupFn))
1082 .with_named_range(
1083 "Split",
1084 vec![
1085 vec![
1086 LiteralValue::Text("Professional".into()),
1087 LiteralValue::Int(123),
1088 ],
1089 vec![LiteralValue::Text("Support".into()), LiteralValue::Int(77)],
1090 ],
1091 );
1092 let ctx = wb.interpreter();
1093 let table = ASTNode::new(
1094 ASTNodeType::Reference {
1095 original: "Split".into(),
1096 reference: ReferenceType::NamedRange("Split".into()),
1097 },
1098 None,
1099 );
1100 let f = ctx.context.get_function("", "VLOOKUP").unwrap();
1101 let key = lit(LiteralValue::Text("Professional".into()));
1102 let two = lit(LiteralValue::Int(2));
1103 let false_lit = lit(LiteralValue::Boolean(false));
1104 let args = vec![
1105 ArgumentHandle::new(&key, &ctx),
1106 ArgumentHandle::new(&table, &ctx),
1107 ArgumentHandle::new(&two, &ctx),
1108 ArgumentHandle::new(&false_lit, &ctx),
1109 ];
1110 let v = f
1111 .dispatch(&args, &ctx.function_context(None))
1112 .unwrap()
1113 .into_literal();
1114 assert_eq!(v, LiteralValue::Number(123.0));
1115 }
1116
1117 #[test]
1118 fn vlookup_blank_target_cell_returns_zero() {
1119 let wb = TestWorkbook::new()
1122 .with_function(Arc::new(VLookupFn))
1123 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1));
1124
1125 let ctx = wb.interpreter();
1126 let table = ASTNode::new(
1127 ASTNodeType::Reference {
1128 original: "A1:B1".into(),
1129 reference: ReferenceType::range(None, Some(1), Some(1), Some(1), Some(2)),
1130 },
1131 None,
1132 );
1133 let f = ctx.context.get_function("", "VLOOKUP").unwrap();
1134 let key1 = lit(LiteralValue::Int(1));
1135 let two = lit(LiteralValue::Int(2));
1136 let false_lit = lit(LiteralValue::Boolean(false));
1137 let args = vec![
1138 ArgumentHandle::new(&key1, &ctx),
1139 ArgumentHandle::new(&table, &ctx),
1140 ArgumentHandle::new(&two, &ctx),
1141 ArgumentHandle::new(&false_lit, &ctx),
1142 ];
1143 let v = f
1144 .dispatch(&args, &ctx.function_context(None))
1145 .unwrap()
1146 .into_literal();
1147 assert_eq!(v, LiteralValue::Number(0.0));
1148 }
1149
1150 #[test]
1151 fn hlookup_basic() {
1152 let wb = TestWorkbook::new()
1153 .with_function(Arc::new(HLookupFn))
1154 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Key1".into()))
1155 .with_cell_a1("Sheet1", "B1", LiteralValue::Text("Key2".into()))
1156 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(100))
1157 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200));
1158 let ctx = wb.interpreter();
1159 let table = ASTNode::new(
1160 ASTNodeType::Reference {
1161 original: "A1:B2".into(),
1162 reference: ReferenceType::range(None, Some(1), Some(1), Some(2), Some(2)),
1163 },
1164 None,
1165 );
1166 let f = ctx.context.get_function("", "HLOOKUP").unwrap();
1167 let key1 = lit(LiteralValue::Text("Key1".into()));
1168 let two = lit(LiteralValue::Int(2));
1169 let false_lit = lit(LiteralValue::Boolean(false));
1170 let args = vec![
1171 ArgumentHandle::new(&key1, &ctx),
1172 ArgumentHandle::new(&table, &ctx),
1173 ArgumentHandle::new(&two, &ctx),
1174 ArgumentHandle::new(&false_lit, &ctx),
1175 ];
1176 let v = f
1177 .dispatch(&args, &ctx.function_context(None))
1178 .unwrap()
1179 .into_literal();
1180 assert_eq!(v, LiteralValue::Number(100.0));
1181 }
1182
1183 #[test]
1184 fn hlookup_blank_target_cell_returns_zero() {
1185 let wb = TestWorkbook::new()
1186 .with_function(Arc::new(HLookupFn))
1187 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1));
1188
1189 let ctx = wb.interpreter();
1190 let table = ASTNode::new(
1191 ASTNodeType::Reference {
1192 original: "A1:B2".into(),
1193 reference: ReferenceType::range(None, Some(1), Some(1), Some(2), Some(2)),
1194 },
1195 None,
1196 );
1197 let f = ctx.context.get_function("", "HLOOKUP").unwrap();
1198 let key1 = lit(LiteralValue::Int(1));
1199 let two = lit(LiteralValue::Int(2));
1200 let false_lit = lit(LiteralValue::Boolean(false));
1201 let args = vec![
1202 ArgumentHandle::new(&key1, &ctx),
1203 ArgumentHandle::new(&table, &ctx),
1204 ArgumentHandle::new(&two, &ctx),
1205 ArgumentHandle::new(&false_lit, &ctx),
1206 ];
1207 let v = f
1208 .dispatch(&args, &ctx.function_context(None))
1209 .unwrap()
1210 .into_literal();
1211 assert_eq!(v, LiteralValue::Number(0.0));
1212 }
1213}