formualizer_eval/builtins/text/
char_code_rept.rs1use super::super::utils::{ARG_ANY_ONE, ARG_ANY_TWO, coerce_num};
4use crate::args::ArgSchema;
5use crate::function::Function;
6use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
7use formualizer_common::{ExcelError, LiteralValue};
8use formualizer_macros::func_caps;
9
10fn scalar_like_value(arg: &ArgumentHandle<'_, '_>) -> Result<LiteralValue, ExcelError> {
11 Ok(match arg.value()? {
12 CalcValue::Scalar(v) => v,
13 CalcValue::Range(rv) => rv.get_cell(0, 0),
14 })
15}
16
17#[derive(Debug)]
20pub struct CharFn;
21impl Function for CharFn {
22 func_caps!(PURE);
23 fn name(&self) -> &'static str {
24 "CHAR"
25 }
26 fn min_args(&self) -> usize {
27 1
28 }
29 fn arg_schema(&self) -> &'static [ArgSchema] {
30 &ARG_ANY_ONE[..]
31 }
32 fn eval<'a, 'b, 'c>(
33 &self,
34 args: &'c [ArgumentHandle<'a, 'b>],
35 _: &dyn FunctionContext<'b>,
36 ) -> Result<CalcValue<'b>, ExcelError> {
37 let v = scalar_like_value(&args[0])?;
38 let n = match v {
39 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
40 other => coerce_num(&other)?,
41 };
42
43 let code = n.trunc() as i32;
44
45 if !(1..=255).contains(&code) {
47 return Ok(CalcValue::Scalar(LiteralValue::Error(
48 ExcelError::new_value(),
49 )));
50 }
51
52 let unicode_char = match code as u8 {
54 0x80 => '\u{20AC}', 0x82 => '\u{201A}', 0x83 => '\u{0192}', 0x84 => '\u{201E}', 0x85 => '\u{2026}', 0x86 => '\u{2020}', 0x87 => '\u{2021}', 0x88 => '\u{02C6}', 0x89 => '\u{2030}', 0x8A => '\u{0160}', 0x8B => '\u{2039}', 0x8C => '\u{0152}', 0x8E => '\u{017D}', 0x91 => '\u{2018}', 0x92 => '\u{2019}', 0x93 => '\u{201C}', 0x94 => '\u{201D}', 0x95 => '\u{2022}', 0x96 => '\u{2013}', 0x97 => '\u{2014}', 0x98 => '\u{02DC}', 0x99 => '\u{2122}', 0x9A => '\u{0161}', 0x9B => '\u{203A}', 0x9C => '\u{0153}', 0x9E => '\u{017E}', 0x9F => '\u{0178}', 0x81 | 0x8D | 0x8F | 0x90 | 0x9D => {
82 '\u{FFFD}'
84 }
85 c => char::from(c),
86 };
87
88 Ok(CalcValue::Scalar(LiteralValue::Text(
89 unicode_char.to_string(),
90 )))
91 }
92}
93
94#[derive(Debug)]
96pub struct CodeFn;
97impl Function for CodeFn {
98 func_caps!(PURE);
99 fn name(&self) -> &'static str {
100 "CODE"
101 }
102 fn min_args(&self) -> usize {
103 1
104 }
105 fn arg_schema(&self) -> &'static [ArgSchema] {
106 &ARG_ANY_ONE[..]
107 }
108 fn eval<'a, 'b, 'c>(
109 &self,
110 args: &'c [ArgumentHandle<'a, 'b>],
111 _: &dyn FunctionContext<'b>,
112 ) -> Result<CalcValue<'b>, ExcelError> {
113 let v = scalar_like_value(&args[0])?;
114 let s = match v {
115 LiteralValue::Text(t) => t,
116 LiteralValue::Empty => {
117 return Ok(CalcValue::Scalar(LiteralValue::Error(
118 ExcelError::new_value(),
119 )));
120 }
121 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
122 other => other.to_string(),
123 };
124
125 if s.is_empty() {
126 return Ok(CalcValue::Scalar(LiteralValue::Error(
127 ExcelError::new_value(),
128 )));
129 }
130
131 let first_char = s.chars().next().unwrap();
132
133 let code = match first_char {
135 '\u{20AC}' => 0x80, '\u{201A}' => 0x82, '\u{0192}' => 0x83, '\u{201E}' => 0x84, '\u{2026}' => 0x85, '\u{2020}' => 0x86, '\u{2021}' => 0x87, '\u{02C6}' => 0x88, '\u{2030}' => 0x89, '\u{0160}' => 0x8A, '\u{2039}' => 0x8B, '\u{0152}' => 0x8C, '\u{017D}' => 0x8E, '\u{2018}' => 0x91, '\u{2019}' => 0x92, '\u{201C}' => 0x93, '\u{201D}' => 0x94, '\u{2022}' => 0x95, '\u{2013}' => 0x96, '\u{2014}' => 0x97, '\u{02DC}' => 0x98, '\u{2122}' => 0x99, '\u{0161}' => 0x9A, '\u{203A}' => 0x9B, '\u{0153}' => 0x9C, '\u{017E}' => 0x9E, '\u{0178}' => 0x9F, c if (c as u32) < 256 => c as i64,
163 c => c as i64, };
165
166 Ok(CalcValue::Scalar(LiteralValue::Int(code)))
167 }
168}
169
170#[derive(Debug)]
172pub struct ReptFn;
173impl Function for ReptFn {
174 func_caps!(PURE);
175 fn name(&self) -> &'static str {
176 "REPT"
177 }
178 fn min_args(&self) -> usize {
179 2
180 }
181 fn arg_schema(&self) -> &'static [ArgSchema] {
182 &ARG_ANY_TWO[..]
183 }
184 fn eval<'a, 'b, 'c>(
185 &self,
186 args: &'c [ArgumentHandle<'a, 'b>],
187 _: &dyn FunctionContext<'b>,
188 ) -> Result<CalcValue<'b>, ExcelError> {
189 let text_val = scalar_like_value(&args[0])?;
190 let count_val = scalar_like_value(&args[1])?;
191
192 let text = match text_val {
193 LiteralValue::Text(t) => t,
194 LiteralValue::Empty => String::new(),
195 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
196 other => other.to_string(),
197 };
198
199 let count = match count_val {
200 LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
201 other => coerce_num(&other)?,
202 };
203
204 let count = count.trunc() as i64;
205
206 if count < 0 {
207 return Ok(CalcValue::Scalar(LiteralValue::Error(
208 ExcelError::new_value(),
209 )));
210 }
211
212 let max_result_len = 32767;
214 let result_len = text.len() * (count as usize);
215 if result_len > max_result_len {
216 return Ok(CalcValue::Scalar(LiteralValue::Error(
217 ExcelError::new_value(),
218 )));
219 }
220
221 let result = text.repeat(count as usize);
222 Ok(CalcValue::Scalar(LiteralValue::Text(result)))
223 }
224}
225
226pub fn register_builtins() {
227 use std::sync::Arc;
228 crate::function_registry::register_function(Arc::new(CharFn));
229 crate::function_registry::register_function(Arc::new(CodeFn));
230 crate::function_registry::register_function(Arc::new(ReptFn));
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use crate::test_workbook::TestWorkbook;
237 use crate::traits::ArgumentHandle;
238 use formualizer_parse::parser::{ASTNode, ASTNodeType};
239
240 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
241 wb.interpreter()
242 }
243 fn lit(v: LiteralValue) -> ASTNode {
244 ASTNode::new(ASTNodeType::Literal(v), None)
245 }
246
247 #[test]
248 fn char_basic() {
249 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CharFn));
250 let ctx = interp(&wb);
251 let n = lit(LiteralValue::Number(65.0));
252 let f = ctx.context.get_function("", "CHAR").unwrap();
253 assert_eq!(
254 f.dispatch(
255 &[ArgumentHandle::new(&n, &ctx)],
256 &ctx.function_context(None)
257 )
258 .unwrap()
259 .into_literal(),
260 LiteralValue::Text("A".to_string())
261 );
262 }
263
264 #[test]
265 fn code_basic() {
266 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CodeFn));
267 let ctx = interp(&wb);
268 let s = lit(LiteralValue::Text("A".to_string()));
269 let f = ctx.context.get_function("", "CODE").unwrap();
270 assert_eq!(
271 f.dispatch(
272 &[ArgumentHandle::new(&s, &ctx)],
273 &ctx.function_context(None)
274 )
275 .unwrap()
276 .into_literal(),
277 LiteralValue::Int(65)
278 );
279 }
280
281 #[test]
282 fn rept_basic() {
283 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ReptFn));
284 let ctx = interp(&wb);
285 let s = lit(LiteralValue::Text("ab".to_string()));
286 let n = lit(LiteralValue::Number(3.0));
287 let f = ctx.context.get_function("", "REPT").unwrap();
288 assert_eq!(
289 f.dispatch(
290 &[ArgumentHandle::new(&s, &ctx), ArgumentHandle::new(&n, &ctx)],
291 &ctx.function_context(None)
292 )
293 .unwrap()
294 .into_literal(),
295 LiteralValue::Text("ababab".to_string())
296 );
297 }
298}