1use crate::{Evaluation, SpannedExpr};
4
5#[derive(Debug)]
9pub struct Function<'src>(pub(crate) &'src str);
10
11impl PartialEq for Function<'_> {
12 fn eq(&self, other: &Self) -> bool {
13 self.0.eq_ignore_ascii_case(other.0)
14 }
15}
16impl PartialEq<str> for Function<'_> {
17 fn eq(&self, other: &str) -> bool {
18 self.0.eq_ignore_ascii_case(other)
19 }
20}
21
22#[derive(Debug, PartialEq)]
24pub struct Call<'src> {
25 pub func: Function<'src>,
27 pub args: Vec<SpannedExpr<'src>>,
29}
30
31impl<'src> Call<'src> {
32 pub(crate) fn consteval(&self) -> Option<Evaluation> {
35 let args = self
36 .args
37 .iter()
38 .map(|arg| arg.consteval())
39 .collect::<Option<Vec<Evaluation>>>()?;
40
41 match &self.func {
42 f if f == "format" => Self::consteval_format(&args),
43 f if f == "contains" => Self::consteval_contains(&args),
44 f if f == "startsWith" => Self::consteval_startswith(&args),
45 f if f == "endsWith" => Self::consteval_endswith(&args),
46 f if f == "toJSON" => Self::consteval_tojson(&args),
47 f if f == "fromJSON" => Self::consteval_fromjson(&args),
48 f if f == "join" => Self::consteval_join(&args),
49 _ => None,
50 }
51 }
52
53 fn consteval_format(args: &[Evaluation]) -> Option<Evaluation> {
57 if args.is_empty() {
58 return None;
59 }
60
61 let template = args[0].sema().to_string();
62 let mut result = String::new();
63 let mut index = 0;
64
65 while index < template.len() {
66 let lbrace = template[index..].find('{').map(|pos| index + pos);
67 let rbrace = template[index..].find('}').map(|pos| index + pos);
68
69 #[allow(clippy::unwrap_used)]
71 if let Some(lbrace_pos) = lbrace
72 && (rbrace.is_none() || rbrace.unwrap() > lbrace_pos)
73 {
74 if template.as_bytes().get(lbrace_pos + 1) == Some(&b'{') {
76 result.push_str(&template[index..=lbrace_pos]);
77 index = lbrace_pos + 2;
78 continue;
79 }
80
81 if let Some(rbrace_pos) = rbrace
83 && rbrace_pos > lbrace_pos + 1
84 && let Some(arg_index) = Self::read_arg_index(&template, lbrace_pos + 1)
85 {
86 if 1 + arg_index > args.len() - 1 {
88 return None;
90 }
91
92 if lbrace_pos > index {
94 result.push_str(&template[index..lbrace_pos]);
95 }
96
97 result.push_str(&args[1 + arg_index].sema().to_string());
99 index = rbrace_pos + 1;
100 continue;
101 }
102
103 return None;
105 }
106
107 if let Some(rbrace_pos) = rbrace {
109 #[allow(clippy::unwrap_used)]
110 if lbrace.is_none() || lbrace.unwrap() > rbrace_pos {
111 if template.as_bytes().get(rbrace_pos + 1) == Some(&b'}') {
113 result.push_str(&template[index..=rbrace_pos]);
114 index = rbrace_pos + 2;
115 } else {
116 return None;
118 }
119 }
120 } else {
121 result.push_str(&template[index..]);
123 break;
124 }
125 }
126
127 Some(Evaluation::String(result))
128 }
129
130 fn read_arg_index(string: &str, start_index: usize) -> Option<usize> {
132 let mut length = 0;
133 let chars: Vec<char> = string.chars().collect();
134
135 while start_index + length < chars.len() {
137 let next_char = chars[start_index + length];
138 if next_char.is_ascii_digit() {
139 length += 1;
140 } else {
141 break;
142 }
143 }
144
145 if length < 1 {
147 return None;
148 }
149
150 let number_str: String = chars[start_index..start_index + length].iter().collect();
152 number_str.parse::<usize>().ok()
153 }
154
155 fn consteval_contains(args: &[Evaluation]) -> Option<Evaluation> {
159 if args.len() != 2 {
160 return None;
161 }
162
163 let search = &args[0];
164 let item = &args[1];
165
166 match search {
167 Evaluation::String(_)
169 | Evaluation::Number(_)
170 | Evaluation::Boolean(_)
171 | Evaluation::Null => {
172 let search_str = search.sema().to_string().to_lowercase();
173 let item_str = item.sema().to_string().to_lowercase();
174 Some(Evaluation::Boolean(search_str.contains(&item_str)))
175 }
176 Evaluation::Array(arr) => {
178 if arr.iter().any(|element| element.sema() == item.sema()) {
179 Some(Evaluation::Boolean(true))
180 } else {
181 Some(Evaluation::Boolean(false))
182 }
183 }
184 Evaluation::Object(_) => None,
186 }
187 }
188
189 fn consteval_startswith(args: &[Evaluation]) -> Option<Evaluation> {
193 if args.len() != 2 {
194 return None;
195 }
196
197 let search_string = &args[0];
198 let search_value = &args[1];
199
200 match (search_string, search_value) {
202 (
203 Evaluation::String(_)
204 | Evaluation::Number(_)
205 | Evaluation::Boolean(_)
206 | Evaluation::Null,
207 Evaluation::String(_)
208 | Evaluation::Number(_)
209 | Evaluation::Boolean(_)
210 | Evaluation::Null,
211 ) => {
212 let string_str = search_string.sema().to_string().to_lowercase();
214 let prefix_str = search_value.sema().to_string().to_lowercase();
215 Some(Evaluation::Boolean(string_str.starts_with(&prefix_str)))
216 }
217 _ => Some(Evaluation::Boolean(false)),
219 }
220 }
221
222 fn consteval_endswith(args: &[Evaluation]) -> Option<Evaluation> {
226 if args.len() != 2 {
227 return None;
228 }
229
230 let search_string = &args[0];
231 let search_value = &args[1];
232
233 match (search_string, search_value) {
235 (
236 Evaluation::String(_)
237 | Evaluation::Number(_)
238 | Evaluation::Boolean(_)
239 | Evaluation::Null,
240 Evaluation::String(_)
241 | Evaluation::Number(_)
242 | Evaluation::Boolean(_)
243 | Evaluation::Null,
244 ) => {
245 let string_str = search_string.sema().to_string().to_lowercase();
247 let suffix_str = search_value.sema().to_string().to_lowercase();
248 Some(Evaluation::Boolean(string_str.ends_with(&suffix_str)))
249 }
250 _ => Some(Evaluation::Boolean(false)),
252 }
253 }
254
255 fn consteval_tojson(args: &[Evaluation]) -> Option<Evaluation> {
259 if args.len() != 1 {
260 return None;
261 }
262
263 let value = &args[0];
264 let json_value: serde_json::Value = value.clone().try_into().ok()?;
265 let json_str = serde_json::to_string_pretty(&json_value).ok()?;
266
267 Some(Evaluation::String(json_str))
268 }
269
270 fn consteval_fromjson(args: &[Evaluation]) -> Option<Evaluation> {
274 if args.len() != 1 {
275 return None;
276 }
277
278 let json_str = args[0].sema().to_string();
279
280 if json_str.trim().is_empty() {
282 return None;
283 }
284
285 serde_json::from_str::<serde_json::Value>(&json_str)
286 .ok()?
287 .try_into()
288 .ok()
289 }
290
291 fn consteval_join(args: &[Evaluation]) -> Option<Evaluation> {
295 if args.is_empty() || args.len() > 2 {
296 return None;
297 }
298
299 let array_or_string = &args[0];
300
301 let separator = if args.len() > 1 {
303 args[1].sema().to_string()
304 } else {
305 ",".to_string()
306 };
307
308 match array_or_string {
309 Evaluation::String(_)
311 | Evaluation::Number(_)
312 | Evaluation::Boolean(_)
313 | Evaluation::Null => Some(Evaluation::String(array_or_string.sema().to_string())),
314 Evaluation::Array(arr) => {
316 let joined = arr
317 .iter()
318 .map(|item| item.sema().to_string())
319 .collect::<Vec<String>>()
320 .join(&separator);
321 Some(Evaluation::String(joined))
322 }
323 Evaluation::Object(_) => Some(Evaluation::String("".to_string())),
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use anyhow::Result;
332
333 use crate::{Expr, call::Call};
334
335 #[test]
336 fn test_consteval_fromjson() -> Result<()> {
337 use crate::Evaluation;
338
339 let test_cases = &[
340 ("fromJSON('null')", Evaluation::Null),
342 ("fromJSON('true')", Evaluation::Boolean(true)),
343 ("fromJSON('false')", Evaluation::Boolean(false)),
344 ("fromJSON('42')", Evaluation::Number(42.0)),
345 ("fromJSON('3.14')", Evaluation::Number(3.14)),
346 ("fromJSON('-0')", Evaluation::Number(0.0)),
347 ("fromJSON('0')", Evaluation::Number(0.0)),
348 (
349 "fromJSON('\"hello\"')",
350 Evaluation::String("hello".to_string()),
351 ),
352 ("fromJSON('\"\"')", Evaluation::String("".to_string())),
353 ("fromJSON('[]')", Evaluation::Array(vec![])),
355 (
356 "fromJSON('[1, 2, 3]')",
357 Evaluation::Array(vec![
358 Evaluation::Number(1.0),
359 Evaluation::Number(2.0),
360 Evaluation::Number(3.0),
361 ]),
362 ),
363 (
364 "fromJSON('[\"a\", \"b\", null, true, 123]')",
365 Evaluation::Array(vec![
366 Evaluation::String("a".to_string()),
367 Evaluation::String("b".to_string()),
368 Evaluation::Null,
369 Evaluation::Boolean(true),
370 Evaluation::Number(123.0),
371 ]),
372 ),
373 (
375 "fromJSON('{}')",
376 Evaluation::Object(std::collections::HashMap::new()),
377 ),
378 (
379 "fromJSON('{\"key\": \"value\"}')",
380 Evaluation::Object({
381 let mut map = std::collections::HashMap::new();
382 map.insert("key".to_string(), Evaluation::String("value".to_string()));
383 map
384 }),
385 ),
386 (
387 "fromJSON('{\"num\": 42, \"bool\": true, \"null\": null}')",
388 Evaluation::Object({
389 let mut map = std::collections::HashMap::new();
390 map.insert("num".to_string(), Evaluation::Number(42.0));
391 map.insert("bool".to_string(), Evaluation::Boolean(true));
392 map.insert("null".to_string(), Evaluation::Null);
393 map
394 }),
395 ),
396 (
398 "fromJSON('{\"array\": [1, 2], \"object\": {\"nested\": true}}')",
399 Evaluation::Object({
400 let mut map = std::collections::HashMap::new();
401 map.insert(
402 "array".to_string(),
403 Evaluation::Array(vec![Evaluation::Number(1.0), Evaluation::Number(2.0)]),
404 );
405 let mut nested_map = std::collections::HashMap::new();
406 nested_map.insert("nested".to_string(), Evaluation::Boolean(true));
407 map.insert("object".to_string(), Evaluation::Object(nested_map));
408 map
409 }),
410 ),
411 ];
412
413 for (expr_str, expected) in test_cases {
414 let expr = Expr::parse(expr_str)?;
415 let result = expr.consteval().unwrap();
416 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
417 }
418
419 Ok(())
420 }
421
422 #[test]
423 fn test_consteval_fromjson_error_cases() -> Result<()> {
424 let error_cases = &[
425 "fromJSON('')", "fromJSON(' ')", "fromJSON('invalid')", "fromJSON('{invalid}')", "fromJSON('[1, 2,]')", ];
431
432 for expr_str in error_cases {
433 let expr = Expr::parse(expr_str)?;
434 let result = expr.consteval();
435 assert!(
436 result.is_none(),
437 "Expected None for invalid JSON: {}",
438 expr_str
439 );
440 }
441
442 Ok(())
443 }
444
445 #[test]
446 fn test_consteval_fromjson_display_format() -> Result<()> {
447 use crate::Evaluation;
448
449 let test_cases = &[
450 (Evaluation::Array(vec![Evaluation::Number(1.0)]), "Array"),
451 (
452 Evaluation::Object(std::collections::HashMap::new()),
453 "Object",
454 ),
455 ];
456
457 for (result, expected) in test_cases {
458 assert_eq!(result.sema().to_string(), *expected);
459 }
460
461 Ok(())
462 }
463
464 #[test]
465 fn test_consteval_tojson_fromjson_roundtrip() -> Result<()> {
466 use crate::Evaluation;
467
468 let test_cases = &[
470 "[1, 2, 3]",
472 r#"{"key": "value"}"#,
474 r#"[1, "hello", true, null]"#,
476 r#"{"array": [1, 2], "object": {"nested": true}}"#,
478 ];
479
480 for json_str in test_cases {
481 let from_expr_str = format!("fromJSON('{}')", json_str);
483 let from_expr = Expr::parse(&from_expr_str)?;
484 let parsed = from_expr.consteval().unwrap();
485
486 let to_result = Call::consteval_tojson(&[parsed.clone()]).unwrap();
488
489 let reparsed_expr_str = format!("fromJSON('{}')", to_result.sema().to_string());
491 let reparsed_expr = Expr::parse(&reparsed_expr_str)?;
492 let reparsed = reparsed_expr.consteval().unwrap();
493
494 match (&parsed, &reparsed) {
496 (Evaluation::Array(a), Evaluation::Array(b)) => assert_eq!(a, b),
497 (Evaluation::Object(_), Evaluation::Object(_)) => {
498 assert!(matches!(parsed, Evaluation::Object(_)));
501 assert!(matches!(reparsed, Evaluation::Object(_)));
502 }
503 (a, b) => assert_eq!(a, b),
504 }
505 }
506
507 Ok(())
508 }
509
510 #[test]
511 fn test_consteval_format() -> Result<()> {
512 use crate::Evaluation;
513
514 let test_cases = &[
515 (
517 "format('Hello {0}', 'world')",
518 Evaluation::String("Hello world".to_string()),
519 ),
520 (
521 "format('{0} {1}', 'Hello', 'world')",
522 Evaluation::String("Hello world".to_string()),
523 ),
524 (
525 "format('Value: {0}', 42)",
526 Evaluation::String("Value: 42".to_string()),
527 ),
528 (
530 "format('{{0}}', 'test')",
531 Evaluation::String("{0}".to_string()),
532 ),
533 (
534 "format('{{Hello}} {0}', 'world')",
535 Evaluation::String("{Hello} world".to_string()),
536 ),
537 (
538 "format('{0} {{1}}', 'Hello')",
539 Evaluation::String("Hello {1}".to_string()),
540 ),
541 (
542 "format('}}{{', 'test')",
543 Evaluation::String("}{".to_string()),
544 ),
545 (
546 "format('{{{{}}}}', 'test')",
547 Evaluation::String("{{}}".to_string()),
548 ),
549 (
551 "format('{0} {1} {2}', 'a', 'b', 'c')",
552 Evaluation::String("a b c".to_string()),
553 ),
554 (
555 "format('{2} {1} {0}', 'a', 'b', 'c')",
556 Evaluation::String("c b a".to_string()),
557 ),
558 (
560 "format('{0} {0} {0}', 'test')",
561 Evaluation::String("test test test".to_string()),
562 ),
563 (
565 "format('Hello world')",
566 Evaluation::String("Hello world".to_string()),
567 ),
568 ("format('abc {{')", Evaluation::String("abc {".to_string())),
570 ("format('abc }}')", Evaluation::String("abc }".to_string())),
571 (
572 "format('abc {{}}')",
573 Evaluation::String("abc {}".to_string()),
574 ),
575 ];
576
577 for (expr_str, expected) in test_cases {
578 let expr = Expr::parse(expr_str)?;
579 let result = expr.consteval().unwrap();
580 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
581 }
582
583 Ok(())
584 }
585
586 #[test]
587 fn test_consteval_format_error_cases() -> Result<()> {
588 let error_cases = &[
589 "format('{0', 'test')", "format('0}', 'test')", "format('{a}', 'test')", "format('{1}', 'test')", "format('{0} {2}', 'a', 'b')", "format('{}', 'test')", "format('{-1}', 'test')", ];
598
599 for expr_str in error_cases {
600 let expr = Expr::parse(expr_str)?;
601 let result = expr.consteval();
602 assert!(
603 result.is_none(),
604 "Expected None for invalid format string: {}",
605 expr_str
606 );
607 }
608
609 Ok(())
610 }
611
612 #[test]
613 fn test_consteval_contains() -> Result<()> {
614 use crate::Evaluation;
615
616 let test_cases = &[
617 (
619 "contains('hello world', 'world')",
620 Evaluation::Boolean(true),
621 ),
622 (
623 "contains('hello world', 'WORLD')",
624 Evaluation::Boolean(true),
625 ),
626 (
627 "contains('HELLO WORLD', 'world')",
628 Evaluation::Boolean(true),
629 ),
630 ("contains('hello world', 'foo')", Evaluation::Boolean(false)),
631 ("contains('test', '')", Evaluation::Boolean(true)),
632 ("contains('123', '2')", Evaluation::Boolean(true)),
634 ("contains(123, '2')", Evaluation::Boolean(true)),
635 ("contains('hello123', 123)", Evaluation::Boolean(true)),
636 ("contains('true', true)", Evaluation::Boolean(true)),
638 ("contains('false', false)", Evaluation::Boolean(true)),
639 ("contains('null', null)", Evaluation::Boolean(true)),
641 ("contains(null, '')", Evaluation::Boolean(true)),
642 (
644 "contains(fromJSON('[1, 2, 3]'), 2)",
645 Evaluation::Boolean(true),
646 ),
647 (
648 "contains(fromJSON('[1, 2, 3]'), 4)",
649 Evaluation::Boolean(false),
650 ),
651 (
652 "contains(fromJSON('[\"a\", \"b\", \"c\"]'), 'b')",
653 Evaluation::Boolean(true),
654 ),
655 (
656 "contains(fromJSON('[\"a\", \"b\", \"c\"]'), 'B')",
657 Evaluation::Boolean(false), ),
659 (
660 "contains(fromJSON('[true, false, null]'), true)",
661 Evaluation::Boolean(true),
662 ),
663 (
664 "contains(fromJSON('[true, false, null]'), null)",
665 Evaluation::Boolean(true),
666 ),
667 (
669 "contains(fromJSON('[]'), 'anything')",
670 Evaluation::Boolean(false),
671 ),
672 (
674 "contains(fromJSON('[1, \"hello\", true, null]'), 'hello')",
675 Evaluation::Boolean(true),
676 ),
677 (
678 "contains(fromJSON('[1, \"hello\", true, null]'), 1)",
679 Evaluation::Boolean(true),
680 ),
681 ];
682
683 for (expr_str, expected) in test_cases {
684 let expr = Expr::parse(expr_str)?;
685 let result = expr.consteval().unwrap();
686 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
687 }
688
689 Ok(())
690 }
691
692 #[test]
693 fn test_consteval_join() -> Result<()> {
694 use crate::Evaluation;
695
696 let test_cases = &[
697 (
699 "join(fromJSON('[\"a\", \"b\", \"c\"]'))",
700 Evaluation::String("a,b,c".to_string()),
701 ),
702 (
703 "join(fromJSON('[1, 2, 3]'))",
704 Evaluation::String("1,2,3".to_string()),
705 ),
706 (
707 "join(fromJSON('[true, false, null]'))",
708 Evaluation::String("true,false,".to_string()),
709 ),
710 (
712 "join(fromJSON('[\"a\", \"b\", \"c\"]'), ' ')",
713 Evaluation::String("a b c".to_string()),
714 ),
715 (
716 "join(fromJSON('[1, 2, 3]'), '-')",
717 Evaluation::String("1-2-3".to_string()),
718 ),
719 (
720 "join(fromJSON('[\"hello\", \"world\"]'), ' | ')",
721 Evaluation::String("hello | world".to_string()),
722 ),
723 (
724 "join(fromJSON('[\"a\", \"b\", \"c\"]'), '')",
725 Evaluation::String("abc".to_string()),
726 ),
727 ("join(fromJSON('[]'))", Evaluation::String("".to_string())),
729 (
730 "join(fromJSON('[]'), '-')",
731 Evaluation::String("".to_string()),
732 ),
733 (
735 "join(fromJSON('[\"single\"]'))",
736 Evaluation::String("single".to_string()),
737 ),
738 (
739 "join(fromJSON('[\"single\"]'), '-')",
740 Evaluation::String("single".to_string()),
741 ),
742 ("join('hello')", Evaluation::String("hello".to_string())),
744 (
745 "join('hello', '-')",
746 Evaluation::String("hello".to_string()),
747 ),
748 ("join(123)", Evaluation::String("123".to_string())),
749 ("join(true)", Evaluation::String("true".to_string())),
750 ("join(null)", Evaluation::String("".to_string())),
751 (
753 "join(fromJSON('[1, \"hello\", true, null]'))",
754 Evaluation::String("1,hello,true,".to_string()),
755 ),
756 (
757 "join(fromJSON('[1, \"hello\", true, null]'), ' | ')",
758 Evaluation::String("1 | hello | true | ".to_string()),
759 ),
760 (
762 "join(fromJSON('[\"a\", \"b\", \"c\"]'), 123)",
763 Evaluation::String("a123b123c".to_string()),
764 ),
765 (
766 "join(fromJSON('[\"a\", \"b\", \"c\"]'), true)",
767 Evaluation::String("atruebtruec".to_string()),
768 ),
769 ];
770
771 for (expr_str, expected) in test_cases {
772 let expr = Expr::parse(expr_str)?;
773 let result = expr.consteval().unwrap();
774 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
775 }
776
777 Ok(())
778 }
779
780 #[test]
781 fn test_consteval_endswith() -> Result<()> {
782 use crate::Evaluation;
783
784 let test_cases = &[
785 (
787 "endsWith('hello world', 'world')",
788 Evaluation::Boolean(true),
789 ),
790 (
791 "endsWith('hello world', 'WORLD')",
792 Evaluation::Boolean(true),
793 ),
794 (
795 "endsWith('HELLO WORLD', 'world')",
796 Evaluation::Boolean(true),
797 ),
798 (
799 "endsWith('hello world', 'hello')",
800 Evaluation::Boolean(false),
801 ),
802 ("endsWith('hello world', 'foo')", Evaluation::Boolean(false)),
803 ("endsWith('test', '')", Evaluation::Boolean(true)),
805 ("endsWith('', '')", Evaluation::Boolean(true)),
806 ("endsWith('', 'test')", Evaluation::Boolean(false)),
807 ("endsWith('123', '3')", Evaluation::Boolean(true)),
809 ("endsWith(123, '3')", Evaluation::Boolean(true)),
810 ("endsWith('hello123', 123)", Evaluation::Boolean(true)),
811 ("endsWith(12345, 345)", Evaluation::Boolean(true)),
812 ("endsWith('test true', true)", Evaluation::Boolean(true)),
814 ("endsWith('test false', false)", Evaluation::Boolean(true)),
815 ("endsWith(true, 'ue')", Evaluation::Boolean(true)),
816 ("endsWith('test null', null)", Evaluation::Boolean(true)),
818 ("endsWith(null, '')", Evaluation::Boolean(true)),
819 ("endsWith('something', null)", Evaluation::Boolean(true)), (
822 "endsWith(fromJSON('[1, 2, 3]'), '3')",
823 Evaluation::Boolean(false),
824 ),
825 (
826 "endsWith('test', fromJSON('[1, 2, 3]'))",
827 Evaluation::Boolean(false),
828 ),
829 (
830 "endsWith(fromJSON('{\"key\": \"value\"}'), 'value')",
831 Evaluation::Boolean(false),
832 ),
833 (
834 "endsWith('test', fromJSON('{\"key\": \"value\"}'))",
835 Evaluation::Boolean(false),
836 ),
837 (
839 "endsWith('TestString', 'STRING')",
840 Evaluation::Boolean(true),
841 ),
842 ("endsWith('CamelCase', 'case')", Evaluation::Boolean(true)),
843 ("endsWith('exact', 'exact')", Evaluation::Boolean(true)),
845 (
847 "endsWith('short', 'very long suffix')",
848 Evaluation::Boolean(false),
849 ),
850 ];
851
852 for (expr_str, expected) in test_cases {
853 let expr = Expr::parse(expr_str)?;
854 let result = expr.consteval().unwrap();
855 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
856 }
857
858 Ok(())
859 }
860
861 #[test]
862 fn test_consteval_startswith() -> Result<()> {
863 use crate::Evaluation;
864
865 let test_cases = &[
866 (
868 "startsWith('hello world', 'hello')",
869 Evaluation::Boolean(true),
870 ),
871 (
872 "startsWith('hello world', 'HELLO')",
873 Evaluation::Boolean(true),
874 ),
875 (
876 "startsWith('HELLO WORLD', 'hello')",
877 Evaluation::Boolean(true),
878 ),
879 (
880 "startsWith('hello world', 'world')",
881 Evaluation::Boolean(false),
882 ),
883 (
884 "startsWith('hello world', 'foo')",
885 Evaluation::Boolean(false),
886 ),
887 ("startsWith('test', '')", Evaluation::Boolean(true)),
889 ("startsWith('', '')", Evaluation::Boolean(true)),
890 ("startsWith('', 'test')", Evaluation::Boolean(false)),
891 ("startsWith('123', '1')", Evaluation::Boolean(true)),
893 ("startsWith(123, '1')", Evaluation::Boolean(true)),
894 ("startsWith('123hello', 123)", Evaluation::Boolean(true)),
895 ("startsWith(12345, 123)", Evaluation::Boolean(true)),
896 ("startsWith('true test', true)", Evaluation::Boolean(true)),
898 ("startsWith('false test', false)", Evaluation::Boolean(true)),
899 ("startsWith(true, 'tr')", Evaluation::Boolean(true)),
900 ("startsWith('null test', null)", Evaluation::Boolean(true)),
902 ("startsWith(null, '')", Evaluation::Boolean(true)),
903 (
904 "startsWith('something', null)",
905 Evaluation::Boolean(true), ),
907 (
909 "startsWith(fromJSON('[1, 2, 3]'), '1')",
910 Evaluation::Boolean(false),
911 ),
912 (
913 "startsWith('test', fromJSON('[1, 2, 3]'))",
914 Evaluation::Boolean(false),
915 ),
916 (
917 "startsWith(fromJSON('{\"key\": \"value\"}'), 'key')",
918 Evaluation::Boolean(false),
919 ),
920 (
921 "startsWith('test', fromJSON('{\"key\": \"value\"}'))",
922 Evaluation::Boolean(false),
923 ),
924 (
926 "startsWith('TestString', 'TEST')",
927 Evaluation::Boolean(true),
928 ),
929 (
930 "startsWith('CamelCase', 'camel')",
931 Evaluation::Boolean(true),
932 ),
933 ("startsWith('exact', 'exact')", Evaluation::Boolean(true)),
935 (
937 "startsWith('short', 'very long prefix')",
938 Evaluation::Boolean(false),
939 ),
940 (
942 "startsWith('prefix_suffix', 'prefix')",
943 Evaluation::Boolean(true),
944 ),
945 (
946 "startsWith('prefix_suffix', 'suffix')",
947 Evaluation::Boolean(false),
948 ),
949 ];
950
951 for (expr_str, expected) in test_cases {
952 let expr = Expr::parse(expr_str)?;
953 let result = expr.consteval().unwrap();
954 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
955 }
956
957 Ok(())
958 }
959
960 #[test]
961 fn test_evaluate_constant_functions() -> Result<()> {
962 use crate::Evaluation;
963
964 let test_cases = &[
965 (
967 "format('{0}', 'hello')",
968 Evaluation::String("hello".to_string()),
969 ),
970 (
971 "format('{0} {1}', 'hello', 'world')",
972 Evaluation::String("hello world".to_string()),
973 ),
974 (
975 "format('Value: {0}', 42)",
976 Evaluation::String("Value: 42".to_string()),
977 ),
978 (
980 "contains('hello world', 'world')",
981 Evaluation::Boolean(true),
982 ),
983 ("contains('hello world', 'foo')", Evaluation::Boolean(false)),
984 ("contains('test', '')", Evaluation::Boolean(true)),
985 (
987 "startsWith('hello world', 'hello')",
988 Evaluation::Boolean(true),
989 ),
990 (
991 "startsWith('hello world', 'world')",
992 Evaluation::Boolean(false),
993 ),
994 ("startsWith('test', '')", Evaluation::Boolean(true)),
995 (
997 "endsWith('hello world', 'world')",
998 Evaluation::Boolean(true),
999 ),
1000 (
1001 "endsWith('hello world', 'hello')",
1002 Evaluation::Boolean(false),
1003 ),
1004 ("endsWith('test', '')", Evaluation::Boolean(true)),
1005 (
1007 "toJSON('hello')",
1008 Evaluation::String("\"hello\"".to_string()),
1009 ),
1010 ("toJSON(42)", Evaluation::String("42".to_string())),
1011 ("toJSON(true)", Evaluation::String("true".to_string())),
1012 ("toJSON(null)", Evaluation::String("null".to_string())),
1013 (
1015 "fromJSON('\"hello\"')",
1016 Evaluation::String("hello".to_string()),
1017 ),
1018 ("fromJSON('42')", Evaluation::Number(42.0)),
1019 ("fromJSON('true')", Evaluation::Boolean(true)),
1020 ("fromJSON('null')", Evaluation::Null),
1021 (
1023 "fromJSON('[1, 2, 3]')",
1024 Evaluation::Array(vec![
1025 Evaluation::Number(1.0),
1026 Evaluation::Number(2.0),
1027 Evaluation::Number(3.0),
1028 ]),
1029 ),
1030 (
1031 "fromJSON('{\"key\": \"value\"}')",
1032 Evaluation::Object({
1033 let mut map = std::collections::HashMap::new();
1034 map.insert("key".to_string(), Evaluation::String("value".to_string()));
1035 map
1036 }),
1037 ),
1038 ];
1039
1040 for (expr_str, expected) in test_cases {
1041 let expr = Expr::parse(expr_str)?;
1042 let result = expr.consteval().unwrap();
1043 assert_eq!(
1044 result, *expected,
1045 "Failed for expression: {} {result:?}",
1046 expr_str
1047 );
1048 }
1049
1050 Ok(())
1051 }
1052}