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