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