formualizer_eval/builtins/text/
len_left_right.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)]
16pub struct LenFn;
17impl Function for LenFn {
18 func_caps!(PURE);
19 fn name(&self) -> &'static str {
20 "LEN"
21 }
22 fn min_args(&self) -> usize {
23 1
24 }
25 fn arg_schema(&self) -> &'static [ArgSchema] {
26 &ARG_ANY_ONE[..]
27 }
28 fn eval<'a, 'b, 'c>(
29 &self,
30 args: &'c [ArgumentHandle<'a, 'b>],
31 _: &dyn FunctionContext<'b>,
32 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
33 let v = scalar_like_value(&args[0])?;
34 let count = match v {
35 LiteralValue::Text(s) => s.chars().count() as i64,
36 LiteralValue::Empty => 0,
37 LiteralValue::Error(e) => {
38 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
39 }
40 other => other.to_string().chars().count() as i64,
41 };
42 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(count)))
43 }
44}
45
46#[derive(Debug)]
47pub struct LeftFn;
48impl Function for LeftFn {
49 func_caps!(PURE);
50 fn name(&self) -> &'static str {
51 "LEFT"
52 }
53 fn min_args(&self) -> usize {
54 1
55 }
56 fn variadic(&self) -> bool {
57 true
58 }
59 fn arg_schema(&self) -> &'static [ArgSchema] {
60 &ARG_ANY_ONE[..]
61 }
62 fn eval<'a, 'b, 'c>(
63 &self,
64 args: &'c [ArgumentHandle<'a, 'b>],
65 _: &dyn FunctionContext<'b>,
66 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
67 if args.is_empty() || args.len() > 2 {
68 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
69 ExcelError::new_value(),
70 )));
71 }
72 let s_val = scalar_like_value(&args[0])?;
73 let s = match s_val {
74 LiteralValue::Text(t) => t,
75 LiteralValue::Empty => String::new(),
76 LiteralValue::Error(e) => {
77 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
78 }
79 other => other.to_string(),
80 };
81 let n: i64 = if args.len() == 2 {
82 number_like(&args[1])?
83 } else {
84 1
85 };
86 if n < 0 {
87 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
88 ExcelError::new_value(),
89 )));
90 }
91 let chars: Vec<char> = s.chars().collect();
92 let take = (n as usize).min(chars.len());
93 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
94 chars[..take].iter().collect(),
95 )))
96 }
97}
98
99#[derive(Debug)]
100pub struct RightFn;
101impl Function for RightFn {
102 func_caps!(PURE);
103 fn name(&self) -> &'static str {
104 "RIGHT"
105 }
106 fn min_args(&self) -> usize {
107 1
108 }
109 fn variadic(&self) -> bool {
110 true
111 }
112 fn arg_schema(&self) -> &'static [ArgSchema] {
113 &ARG_ANY_ONE[..]
114 }
115 fn eval<'a, 'b, 'c>(
116 &self,
117 args: &'c [ArgumentHandle<'a, 'b>],
118 _: &dyn FunctionContext<'b>,
119 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
120 if args.is_empty() || args.len() > 2 {
121 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
122 ExcelError::new_value(),
123 )));
124 }
125 let s_val = scalar_like_value(&args[0])?;
126 let s = match s_val {
127 LiteralValue::Text(t) => t,
128 LiteralValue::Empty => String::new(),
129 LiteralValue::Error(e) => {
130 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
131 }
132 other => other.to_string(),
133 };
134 let n: i64 = if args.len() == 2 {
135 number_like(&args[1])?
136 } else {
137 1
138 };
139 if n < 0 {
140 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
141 ExcelError::new_value(),
142 )));
143 }
144 let chars: Vec<char> = s.chars().collect();
145 let len = chars.len();
146 let start = len.saturating_sub(n as usize);
147 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
148 chars[start..].iter().collect(),
149 )))
150 }
151}
152
153fn number_like<'a, 'b>(arg: &ArgumentHandle<'a, 'b>) -> Result<i64, ExcelError> {
154 let v = scalar_like_value(arg)?;
155 Ok(match v {
156 LiteralValue::Int(i) => i,
157 LiteralValue::Number(f) => f as i64,
158 LiteralValue::Empty => 0,
159 LiteralValue::Text(t) => t.parse::<i64>().unwrap_or(0),
160 LiteralValue::Boolean(b) => {
161 if b {
162 1
163 } else {
164 0
165 }
166 }
167 LiteralValue::Error(e) => return Err(e),
168 other => other.to_string().parse::<i64>().unwrap_or(0),
169 })
170}
171
172pub fn register_builtins() {
173 use std::sync::Arc;
174 crate::function_registry::register_function(Arc::new(LenFn));
175 crate::function_registry::register_function(Arc::new(LeftFn));
176 crate::function_registry::register_function(Arc::new(RightFn));
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::test_workbook::TestWorkbook;
183 use crate::traits::ArgumentHandle;
184 use formualizer_common::LiteralValue;
185 use formualizer_parse::parser::{ASTNode, ASTNodeType};
186 fn lit(v: LiteralValue) -> ASTNode {
187 ASTNode::new(ASTNodeType::Literal(v), None)
188 }
189 #[test]
190 fn len_basic() {
191 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(LenFn));
192 let ctx = wb.interpreter();
193 let f = ctx.context.get_function("", "LEN").unwrap();
194 let s = lit(LiteralValue::Text("abc".into()));
195 let out = f
196 .dispatch(
197 &[ArgumentHandle::new(&s, &ctx)],
198 &ctx.function_context(None),
199 )
200 .unwrap();
201 assert_eq!(out.into_literal(), LiteralValue::Int(3));
202 }
203 #[test]
204 fn left_right() {
205 let wb = TestWorkbook::new()
206 .with_function(std::sync::Arc::new(LeftFn))
207 .with_function(std::sync::Arc::new(RightFn));
208 let ctx = wb.interpreter();
209 let l = ctx.context.get_function("", "LEFT").unwrap();
210 let r = ctx.context.get_function("", "RIGHT").unwrap();
211 let s = lit(LiteralValue::Text("hello".into()));
212 let n = lit(LiteralValue::Int(2));
213 assert_eq!(
214 l.dispatch(
215 &[ArgumentHandle::new(&s, &ctx), ArgumentHandle::new(&n, &ctx)],
216 &ctx.function_context(None)
217 )
218 .unwrap()
219 .into_literal(),
220 LiteralValue::Text("he".into())
221 );
222 assert_eq!(
223 r.dispatch(
224 &[ArgumentHandle::new(&s, &ctx), ArgumentHandle::new(&n, &ctx)],
225 &ctx.function_context(None)
226 )
227 .unwrap()
228 .into_literal(),
229 LiteralValue::Text("lo".into())
230 );
231 }
232}