formualizer_eval/builtins/text/
find_search_exact.rs1use super::super::utils::ARG_ANY_ONE;
2use crate::args::ArgSchema;
3use crate::function::Function;
4use crate::traits::{ArgumentHandle, FunctionContext};
5use formualizer_common::{ExcelError, LiteralValue};
6use formualizer_macros::func_caps;
7
8fn to_text<'a, 'b>(a: &ArgumentHandle<'a, 'b>) -> Result<String, ExcelError> {
9 let v = a.value()?;
10 Ok(match v.as_ref() {
11 LiteralValue::Text(s) => s.clone(),
12 LiteralValue::Empty => String::new(),
13 LiteralValue::Boolean(b) => {
14 if *b {
15 "TRUE".into()
16 } else {
17 "FALSE".into()
18 }
19 }
20 LiteralValue::Int(i) => i.to_string(),
21 LiteralValue::Number(f) => f.to_string(),
22 LiteralValue::Error(e) => return Err(e.clone()),
23 other => other.to_string(),
24 })
25}
26
27#[derive(Debug)]
29pub struct FindFn;
30impl Function for FindFn {
31 func_caps!(PURE);
32 fn name(&self) -> &'static str {
33 "FIND"
34 }
35 fn min_args(&self) -> usize {
36 2
37 }
38 fn variadic(&self) -> bool {
39 true
40 }
41 fn arg_schema(&self) -> &'static [ArgSchema] {
42 &ARG_ANY_ONE[..]
43 }
44 fn eval_scalar<'a, 'b>(
45 &self,
46 args: &'a [ArgumentHandle<'a, 'b>],
47 _: &dyn FunctionContext,
48 ) -> Result<LiteralValue, ExcelError> {
49 if args.len() < 2 || args.len() > 3 {
50 return Ok(LiteralValue::Error(ExcelError::new_value()));
51 }
52 let needle = to_text(&args[0])?;
53 let hay = to_text(&args[1])?;
54 let start = if args.len() == 3 {
55 let n = number_like(&args[2])?;
56 if n < 1 {
57 return Ok(LiteralValue::Error(ExcelError::new_value()));
58 }
59 (n - 1) as usize
60 } else {
61 0
62 };
63 if needle.is_empty() {
64 return Ok(LiteralValue::Int(1));
65 }
66 if start > hay.len() {
67 return Ok(LiteralValue::Error(ExcelError::new_value()));
68 }
69 if let Some(pos) = hay[start..].find(&needle) {
70 Ok(LiteralValue::Int((start + pos + 1) as i64))
71 } else {
72 Ok(LiteralValue::Error(ExcelError::new_value()))
73 }
74 }
75}
76
77#[derive(Debug)]
79pub struct SearchFn;
80impl Function for SearchFn {
81 func_caps!(PURE);
82 fn name(&self) -> &'static str {
83 "SEARCH"
84 }
85 fn min_args(&self) -> usize {
86 2
87 }
88 fn variadic(&self) -> bool {
89 true
90 }
91 fn arg_schema(&self) -> &'static [ArgSchema] {
92 &ARG_ANY_ONE[..]
93 }
94 fn eval_scalar<'a, 'b>(
95 &self,
96 args: &'a [ArgumentHandle<'a, 'b>],
97 _: &dyn FunctionContext,
98 ) -> Result<LiteralValue, ExcelError> {
99 if args.len() < 2 || args.len() > 3 {
100 return Ok(LiteralValue::Error(ExcelError::new_value()));
101 }
102 let needle = to_text(&args[0])?.to_ascii_lowercase();
103 let hay_raw = to_text(&args[1])?;
104 let hay = hay_raw.to_ascii_lowercase();
105 let start = if args.len() == 3 {
106 let n = number_like(&args[2])?;
107 if n < 1 {
108 return Ok(LiteralValue::Error(ExcelError::new_value()));
109 }
110 (n - 1) as usize
111 } else {
112 0
113 };
114 if needle.is_empty() {
115 return Ok(LiteralValue::Int(1));
116 }
117 if start > hay.len() {
118 return Ok(LiteralValue::Error(ExcelError::new_value()));
119 }
120 let is_wild = needle.contains('*') || needle.contains('?');
123 if !is_wild {
124 if let Some(pos) = hay[start..].find(&needle) {
125 return Ok(LiteralValue::Int((start + pos + 1) as i64));
126 } else {
127 return Ok(LiteralValue::Error(ExcelError::new_value()));
128 }
129 }
130 for offset in start..=hay.len() {
132 if wildcard_match(&needle, &hay[offset..]) {
133 return Ok(LiteralValue::Int((offset + 1) as i64));
134 }
135 }
136 Ok(LiteralValue::Error(ExcelError::from_error_string(
137 "#VALUE!",
138 )))
139 }
140}
141
142fn wildcard_match(pat: &str, text: &str) -> bool {
143 fn rec(p: &[u8], t: &[u8]) -> bool {
144 if p.is_empty() {
145 return true;
146 }
147 match p[0] {
148 b'*' => {
149 for i in 0..=t.len() {
150 if rec(&p[1..], &t[i..]) {
151 return true;
152 }
153 }
154 false
155 }
156 b'?' => {
157 if t.is_empty() {
158 false
159 } else {
160 rec(&p[1..], &t[1..])
161 }
162 }
163 c => {
164 if !t.is_empty() && t[0] == c {
165 rec(&p[1..], &t[1..])
166 } else {
167 false
168 }
169 }
170 }
171 }
172 rec(pat.as_bytes(), text.as_bytes())
173}
174
175#[derive(Debug)]
177pub struct ExactFn;
178impl Function for ExactFn {
179 func_caps!(PURE);
180 fn name(&self) -> &'static str {
181 "EXACT"
182 }
183 fn min_args(&self) -> usize {
184 2
185 }
186 fn arg_schema(&self) -> &'static [ArgSchema] {
187 &ARG_ANY_ONE[..]
188 }
189 fn eval_scalar<'a, 'b>(
190 &self,
191 args: &'a [ArgumentHandle<'a, 'b>],
192 _: &dyn FunctionContext,
193 ) -> Result<LiteralValue, ExcelError> {
194 let a = to_text(&args[0])?;
195 let b = to_text(&args[1])?;
196 Ok(LiteralValue::Boolean(a == b))
197 }
198}
199
200fn number_like<'a, 'b>(a: &ArgumentHandle<'a, 'b>) -> Result<i64, ExcelError> {
201 let v = a.value()?;
202 Ok(match v.as_ref() {
203 LiteralValue::Int(i) => *i,
204 LiteralValue::Number(f) => *f as i64,
205 LiteralValue::Text(t) => t.parse::<i64>().unwrap_or(0),
206 LiteralValue::Boolean(b) => {
207 if *b {
208 1
209 } else {
210 0
211 }
212 }
213 LiteralValue::Empty => 0,
214 LiteralValue::Error(e) => return Err(e.clone()),
215 other => other.to_string().parse::<i64>().unwrap_or(0),
216 })
217}
218
219pub fn register_builtins() {
220 use std::sync::Arc;
221 crate::function_registry::register_function(Arc::new(FindFn));
222 crate::function_registry::register_function(Arc::new(SearchFn));
223 crate::function_registry::register_function(Arc::new(ExactFn));
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use crate::test_workbook::TestWorkbook;
230 use crate::traits::ArgumentHandle;
231 use formualizer_common::LiteralValue;
232 use formualizer_parse::parser::{ASTNode, ASTNodeType};
233 fn lit(v: LiteralValue) -> ASTNode {
234 ASTNode::new(ASTNodeType::Literal(v), None)
235 }
236 #[test]
237 fn find_search() {
238 let wb = TestWorkbook::new()
239 .with_function(std::sync::Arc::new(FindFn))
240 .with_function(std::sync::Arc::new(SearchFn));
241 let ctx = wb.interpreter();
242 let f = ctx.context.get_function("", "FIND").unwrap();
243 let s = ctx.context.get_function("", "SEARCH").unwrap();
244 let hay = lit(LiteralValue::Text("Hello World".into()));
245 let needle = lit(LiteralValue::Text("World".into()));
246 assert_eq!(
247 f.dispatch(
248 &[
249 ArgumentHandle::new(&needle, &ctx),
250 ArgumentHandle::new(&hay, &ctx)
251 ],
252 &ctx.function_context(None)
253 )
254 .unwrap(),
255 LiteralValue::Int(7)
256 );
257 let needle2 = lit(LiteralValue::Text("world".into()));
258 assert_eq!(
259 s.dispatch(
260 &[
261 ArgumentHandle::new(&needle2, &ctx),
262 ArgumentHandle::new(&hay, &ctx)
263 ],
264 &ctx.function_context(None)
265 )
266 .unwrap(),
267 LiteralValue::Int(7)
268 );
269 }
270}