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