1use crate::{
4 Value,
5 function::{Arity, Function},
6};
7
8use super::error::{NativeError, NativeResult};
9
10#[rustfmt::skip]
12pub fn functions() -> Vec<Function> {
13 vec![
14 Function::new(chr, Arity::required(1), "chr(ord: Number): String"),
15 Function::new(ord, Arity::required(1), "ord(char: String): Number"),
16 Function::new(lowercase, Arity::required(1), "lowercase(text: String): String"),
17 Function::new(uppercase, Arity::required(1), "uppercase(text: String): String"),
18 Function::new(same_text, Arity::required(2), "same_text(left: String, right: String): Boolean"),
19 Function::new(split, Arity::required(2), "split(line: String, separator: String): Array<String>"),
20 Function::new(split_csv, Arity::optional(1, 1), "split_csv(line: String, separator: String = ';'): Array<String>"),
21 Function::new(trim, Arity::required(1), "trim(text: String): String"),
22 Function::new(trim_left, Arity::required(1), "trim_left(text: String): String"),
23 Function::new(trim_right, Arity::required(1), "trim_right(text: String): String"),
24 ]
25}
26
27#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
37pub fn chr(params: &[Value]) -> NativeResult {
38 match params {
39 [Value::Number(ordinal)] if (0.0..127.0).contains(ordinal) => Ok(Value::String(
40 char::from_u32(*ordinal as u32).unwrap_or('\0').to_string(),
41 )),
42 [Value::Number(_)] => Err(NativeError::from("number is out of ASCII range")),
43 [_] => Err(NativeError::WrongParameterType),
44 _ => Err(NativeError::WrongParameterCount(1)),
45 }
46}
47
48pub fn ord(params: &[Value]) -> NativeResult {
58 match params {
59 [Value::String(char)] if char.chars().count() == 1 => {
60 if char.is_ascii() {
61 Ok(Value::Number(f64::from(
62 char.chars().next().unwrap_or('\0') as u8,
63 )))
64 } else {
65 Err(NativeError::from("character is out of ASCII range"))
66 }
67 }
68 [Value::String(_)] => Err(NativeError::from("string is too long")),
69 [_] => Err(NativeError::WrongParameterType),
70 _ => Err(NativeError::WrongParameterCount(1)),
71 }
72}
73
74pub fn lowercase(params: &[Value]) -> NativeResult {
83 match params {
84 [Value::String(text)] => Ok(Value::String(text.to_lowercase())),
85 [_] => Err(NativeError::WrongParameterType),
86 _ => Err(NativeError::WrongParameterCount(1)),
87 }
88}
89
90pub fn uppercase(params: &[Value]) -> NativeResult {
99 match params {
100 [Value::String(text)] => Ok(Value::String(text.to_uppercase())),
101 [_] => Err(NativeError::WrongParameterType),
102 _ => Err(NativeError::WrongParameterCount(1)),
103 }
104}
105
106pub fn same_text(params: &[Value]) -> NativeResult {
119 match params {
120 [Value::String(left), Value::String(right)] => {
121 Ok(Value::Boolean(left.to_lowercase() == right.to_lowercase()))
122 }
123 [_, _] => Err(NativeError::WrongParameterType),
124 _ => Err(NativeError::WrongParameterCount(2)),
125 }
126}
127
128pub fn split(params: &[Value]) -> NativeResult {
137 match params {
138 [Value::String(line), Value::String(separator)] => {
139 let values = line
140 .split(separator)
141 .map(String::from)
142 .map(Value::String)
143 .collect();
144
145 Ok(Value::Array(values))
146 }
147 [_, _] => Err(NativeError::WrongParameterType),
148 _ => Err(NativeError::WrongParameterCount(1)),
149 }
150}
151
152fn char_from_value(value: &Value) -> Option<char> {
153 match value {
154 Value::String(string) if string.len() == 1 => string.chars().next(),
155 _ => None,
156 }
157}
158
159fn parse_csv(line: &str, separator: char) -> Vec<String> {
160 let mut result = Vec::new();
161 let mut field = String::new();
162 let mut in_quotes = false;
163
164 for c in line.chars() {
165 if c == separator && !in_quotes {
166 result.push(field.clone());
167 field.clear();
168 } else if c == '"' {
169 in_quotes = !in_quotes;
170 } else {
171 field.push(c);
172 }
173 }
174
175 result.push(field);
176 result
177}
178
179pub fn split_csv(params: &[Value]) -> NativeResult {
188 let separator = params.get(1).and_then(char_from_value).unwrap_or(';');
189
190 match params {
191 [Value::String(line), ..] => {
192 let values = parse_csv(line, separator)
193 .into_iter()
194 .map(Value::String)
195 .collect();
196 Ok(Value::Array(values))
197 }
198 [_, ..] => Err(NativeError::WrongParameterType),
199 _ => Err(NativeError::WrongParameterCount(1)),
200 }
201}
202
203pub fn trim(params: &[Value]) -> NativeResult {
212 match params {
213 [Value::String(text)] => Ok(Value::String(text.trim().to_string())),
214 [_] => Err(NativeError::WrongParameterType),
215 _ => Err(NativeError::WrongParameterCount(1)),
216 }
217}
218
219pub fn trim_left(params: &[Value]) -> NativeResult {
228 match params {
229 [Value::String(text)] => Ok(Value::String(text.trim_start().to_string())),
230 [_] => Err(NativeError::WrongParameterType),
231 _ => Err(NativeError::WrongParameterCount(1)),
232 }
233}
234
235pub fn trim_right(params: &[Value]) -> NativeResult {
244 match params {
245 [Value::String(text)] => Ok(Value::String(text.trim_end().to_string())),
246 [_] => Err(NativeError::WrongParameterType),
247 _ => Err(NativeError::WrongParameterCount(1)),
248 }
249}
250
251#[cfg(test)]
252mod test {
253 use super::*;
254 use crate::Value;
255
256 #[test]
257 fn string_ord() {
258 assert_eq!(
259 Ok(Value::Number(97.0)),
260 ord(&vec![Value::String(String::from("a"))])
261 );
262
263 assert_eq!(
264 Ok(Value::Number(13.0)),
265 ord(&vec![Value::String(String::from("\r"))])
266 );
267 assert_eq!(
268 Ok(Value::Number(10.0)),
269 ord(&vec![Value::String(String::from("\n"))])
270 );
271
272 assert!(ord(&vec![Value::String(String::from("Hello World"))]).is_err());
273 assert!(ord(&vec![Value::String(String::from("🙄"))]).is_err());
274 }
275
276 #[test]
277 fn string_chr() {
278 assert_eq!(
279 Ok(Value::String(String::from("a"))),
280 chr(&vec![Value::Number(97.0)])
281 );
282
283 assert_eq!(
284 Ok(Value::String(String::from("\0"))),
285 chr(&vec![Value::Number(0.0)])
286 );
287
288 assert!(chr(&vec![Value::Number(256.0)]).is_err());
289 }
290
291 #[test]
292 fn string_lowercase() {
293 assert_eq!(
294 Ok(Value::String(String::from("hello world"))),
295 lowercase(&vec![Value::String(String::from("Hello World"))])
296 );
297
298 assert!(lowercase(&vec![]).is_err());
299 assert!(lowercase(&vec![Value::Boolean(true)]).is_err());
300 }
301
302 #[test]
303 fn string_uppercase() {
304 assert_eq!(
305 Ok(Value::String(String::from("HELLO WORLD"))),
306 uppercase(&vec![Value::String(String::from("Hello World"))])
307 );
308
309 assert!(uppercase(&vec![]).is_err());
310 assert!(uppercase(&vec![Value::Boolean(true)]).is_err());
311 }
312
313 #[test]
314 fn string_split_csv() {
315 assert_eq!(
316 Ok(Value::Array(vec![
317 Value::String(String::from("Hello; World")),
318 Value::String(String::from("1234")),
319 Value::String(String::from("")),
320 Value::String(String::from("End"))
321 ])),
322 split_csv(&vec![Value::String(String::from(
323 "\"Hello; World\";1234;;End"
324 ))])
325 );
326
327 assert_eq!(
328 Ok(Value::Array(vec![Value::String(String::new())])),
329 split_csv(&vec![Value::String(String::from(""))])
330 );
331 }
332
333 #[test]
334 fn string_split() {
335 assert_eq!(
336 Ok(Value::Array(vec![
337 Value::String(String::from("\"Hello")),
338 Value::String(String::from(" World\"")),
339 Value::String(String::from("1234")),
340 Value::String(String::from("")),
341 Value::String(String::from("End"))
342 ])),
343 split(&vec![
344 Value::String(String::from("\"Hello; World\";1234;;End")),
345 Value::String(String::from(";"))
346 ])
347 );
348
349 assert_eq!(
350 Ok(Value::Array(vec![Value::String(String::new())])),
351 split(&vec![
352 Value::String(String::from("")),
353 Value::String(String::from(";"))
354 ])
355 );
356 }
357
358 #[test]
359 fn string_same_text() {
360 assert_eq!(
361 Ok(Value::Boolean(true)),
362 same_text(&vec![
363 Value::String(String::from("hello world")),
364 Value::String(String::from("Hello World"))
365 ])
366 );
367
368 assert_eq!(
369 Ok(Value::Boolean(false)),
370 same_text(&vec![
371 Value::String(String::from("hallo world")),
372 Value::String(String::from("hello world"))
373 ])
374 );
375 }
376
377 #[test]
378 fn string_trim() {
379 assert_eq!(
380 Ok(Value::String(String::from("Hello World"))),
381 trim(&vec![Value::String(String::from(" Hello World "))])
382 );
383
384 assert!(trim(&vec![]).is_err());
385 assert!(trim(&vec![Value::Boolean(true)]).is_err());
386
387 assert_eq!(
388 Ok(Value::String(String::from("Hello World "))),
389 trim_left(&vec![Value::String(String::from(" Hello World "))])
390 );
391
392 assert_eq!(
393 Ok(Value::String(String::from(" Hello World"))),
394 trim_right(&vec![Value::String(String::from(" Hello World "))])
395 );
396 }
397}