Skip to main content

formualizer_eval/builtins/text/
len_left_right.rs

1use 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}