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 if let Some(lbrace_pos) = lbrace
71 && (rbrace.is_none() || rbrace.unwrap() > lbrace_pos)
72 {
73 if template.as_bytes().get(lbrace_pos + 1) == Some(&b'{') {
75 result.push_str(&template[index..=lbrace_pos]);
76 index = lbrace_pos + 2;
77 continue;
78 }
79
80 if let Some(rbrace_pos) = rbrace
82 && rbrace_pos > lbrace_pos + 1
83 && let Some(arg_index) = Self::read_arg_index(&template, lbrace_pos + 1)
84 {
85 if 1 + arg_index > args.len() - 1 {
87 return None;
89 }
90
91 if lbrace_pos > index {
93 result.push_str(&template[index..lbrace_pos]);
94 }
95
96 result.push_str(&args[1 + arg_index].sema().to_string());
98 index = rbrace_pos + 1;
99 continue;
100 }
101
102 return None;
104 }
105
106 if let Some(rbrace_pos) = rbrace {
108 if lbrace.is_none() || lbrace.unwrap() > rbrace_pos {
109 if template.as_bytes().get(rbrace_pos + 1) == Some(&b'}') {
111 result.push_str(&template[index..=rbrace_pos]);
112 index = rbrace_pos + 2;
113 } else {
114 return None;
116 }
117 }
118 } else {
119 result.push_str(&template[index..]);
121 break;
122 }
123 }
124
125 Some(Evaluation::String(result))
126 }
127
128 fn read_arg_index(string: &str, start_index: usize) -> Option<usize> {
130 let mut length = 0;
131 let chars: Vec<char> = string.chars().collect();
132
133 while start_index + length < chars.len() {
135 let next_char = chars[start_index + length];
136 if next_char.is_ascii_digit() {
137 length += 1;
138 } else {
139 break;
140 }
141 }
142
143 if length < 1 {
145 return None;
146 }
147
148 let number_str: String = chars[start_index..start_index + length].iter().collect();
150 number_str.parse::<usize>().ok()
151 }
152
153 fn consteval_contains(args: &[Evaluation]) -> Option<Evaluation> {
157 if args.len() != 2 {
158 return None;
159 }
160
161 let search = &args[0];
162 let item = &args[1];
163
164 match search {
165 Evaluation::String(_)
167 | Evaluation::Number(_)
168 | Evaluation::Boolean(_)
169 | Evaluation::Null => {
170 let search_str = search.sema().to_string().to_lowercase();
171 let item_str = item.sema().to_string().to_lowercase();
172 Some(Evaluation::Boolean(search_str.contains(&item_str)))
173 }
174 Evaluation::Array(arr) => {
176 if arr.iter().any(|element| element.sema() == item.sema()) {
177 Some(Evaluation::Boolean(true))
178 } else {
179 Some(Evaluation::Boolean(false))
180 }
181 }
182 Evaluation::Object(_) => None,
184 }
185 }
186
187 fn consteval_startswith(args: &[Evaluation]) -> Option<Evaluation> {
191 if args.len() != 2 {
192 return None;
193 }
194
195 let search_string = &args[0];
196 let search_value = &args[1];
197
198 match (search_string, search_value) {
200 (
201 Evaluation::String(_)
202 | Evaluation::Number(_)
203 | Evaluation::Boolean(_)
204 | Evaluation::Null,
205 Evaluation::String(_)
206 | Evaluation::Number(_)
207 | Evaluation::Boolean(_)
208 | Evaluation::Null,
209 ) => {
210 let string_str = search_string.sema().to_string().to_lowercase();
212 let prefix_str = search_value.sema().to_string().to_lowercase();
213 Some(Evaluation::Boolean(string_str.starts_with(&prefix_str)))
214 }
215 _ => Some(Evaluation::Boolean(false)),
217 }
218 }
219
220 fn consteval_endswith(args: &[Evaluation]) -> Option<Evaluation> {
224 if args.len() != 2 {
225 return None;
226 }
227
228 let search_string = &args[0];
229 let search_value = &args[1];
230
231 match (search_string, search_value) {
233 (
234 Evaluation::String(_)
235 | Evaluation::Number(_)
236 | Evaluation::Boolean(_)
237 | Evaluation::Null,
238 Evaluation::String(_)
239 | Evaluation::Number(_)
240 | Evaluation::Boolean(_)
241 | Evaluation::Null,
242 ) => {
243 let string_str = search_string.sema().to_string().to_lowercase();
245 let suffix_str = search_value.sema().to_string().to_lowercase();
246 Some(Evaluation::Boolean(string_str.ends_with(&suffix_str)))
247 }
248 _ => Some(Evaluation::Boolean(false)),
250 }
251 }
252
253 fn consteval_tojson(args: &[Evaluation]) -> Option<Evaluation> {
257 if args.len() != 1 {
258 return None;
259 }
260
261 let value = &args[0];
262 let json_value: serde_json::Value = value.clone().try_into().ok()?;
263 let json_str = serde_json::to_string_pretty(&json_value).ok()?;
264
265 Some(Evaluation::String(json_str))
266 }
267
268 fn consteval_fromjson(args: &[Evaluation]) -> Option<Evaluation> {
272 if args.len() != 1 {
273 return None;
274 }
275
276 let json_str = args[0].sema().to_string();
277
278 if json_str.trim().is_empty() {
280 return None;
281 }
282
283 serde_json::from_str::<serde_json::Value>(&json_str)
284 .ok()?
285 .try_into()
286 .ok()
287 }
288
289 fn consteval_join(args: &[Evaluation]) -> Option<Evaluation> {
293 if args.is_empty() || args.len() > 2 {
294 return None;
295 }
296
297 let array_or_string = &args[0];
298
299 let separator = if args.len() > 1 {
301 args[1].sema().to_string()
302 } else {
303 ",".to_string()
304 };
305
306 match array_or_string {
307 Evaluation::String(_)
309 | Evaluation::Number(_)
310 | Evaluation::Boolean(_)
311 | Evaluation::Null => Some(Evaluation::String(array_or_string.sema().to_string())),
312 Evaluation::Array(arr) => {
314 let joined = arr
315 .iter()
316 .map(|item| item.sema().to_string())
317 .collect::<Vec<String>>()
318 .join(&separator);
319 Some(Evaluation::String(joined))
320 }
321 Evaluation::Object(_) => Some(Evaluation::String("".to_string())),
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use anyhow::Result;
330
331 use crate::{Expr, call::Call};
332
333 #[test]
334 fn test_consteval_fromjson() -> Result<()> {
335 use crate::Evaluation;
336
337 let test_cases = &[
338 ("fromJSON('null')", Evaluation::Null),
340 ("fromJSON('true')", Evaluation::Boolean(true)),
341 ("fromJSON('false')", Evaluation::Boolean(false)),
342 ("fromJSON('42')", Evaluation::Number(42.0)),
343 ("fromJSON('3.14')", Evaluation::Number(3.14)),
344 ("fromJSON('-0')", Evaluation::Number(0.0)),
345 ("fromJSON('0')", Evaluation::Number(0.0)),
346 (
347 "fromJSON('\"hello\"')",
348 Evaluation::String("hello".to_string()),
349 ),
350 ("fromJSON('\"\"')", Evaluation::String("".to_string())),
351 ("fromJSON('[]')", Evaluation::Array(vec![])),
353 (
354 "fromJSON('[1, 2, 3]')",
355 Evaluation::Array(vec![
356 Evaluation::Number(1.0),
357 Evaluation::Number(2.0),
358 Evaluation::Number(3.0),
359 ]),
360 ),
361 (
362 "fromJSON('[\"a\", \"b\", null, true, 123]')",
363 Evaluation::Array(vec![
364 Evaluation::String("a".to_string()),
365 Evaluation::String("b".to_string()),
366 Evaluation::Null,
367 Evaluation::Boolean(true),
368 Evaluation::Number(123.0),
369 ]),
370 ),
371 (
373 "fromJSON('{}')",
374 Evaluation::Object(std::collections::HashMap::new()),
375 ),
376 (
377 "fromJSON('{\"key\": \"value\"}')",
378 Evaluation::Object({
379 let mut map = std::collections::HashMap::new();
380 map.insert("key".to_string(), Evaluation::String("value".to_string()));
381 map
382 }),
383 ),
384 (
385 "fromJSON('{\"num\": 42, \"bool\": true, \"null\": null}')",
386 Evaluation::Object({
387 let mut map = std::collections::HashMap::new();
388 map.insert("num".to_string(), Evaluation::Number(42.0));
389 map.insert("bool".to_string(), Evaluation::Boolean(true));
390 map.insert("null".to_string(), Evaluation::Null);
391 map
392 }),
393 ),
394 (
396 "fromJSON('{\"array\": [1, 2], \"object\": {\"nested\": true}}')",
397 Evaluation::Object({
398 let mut map = std::collections::HashMap::new();
399 map.insert(
400 "array".to_string(),
401 Evaluation::Array(vec![Evaluation::Number(1.0), Evaluation::Number(2.0)]),
402 );
403 let mut nested_map = std::collections::HashMap::new();
404 nested_map.insert("nested".to_string(), Evaluation::Boolean(true));
405 map.insert("object".to_string(), Evaluation::Object(nested_map));
406 map
407 }),
408 ),
409 ];
410
411 for (expr_str, expected) in test_cases {
412 let expr = Expr::parse(expr_str)?;
413 let result = expr.consteval().unwrap();
414 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
415 }
416
417 Ok(())
418 }
419
420 #[test]
421 fn test_consteval_fromjson_error_cases() -> Result<()> {
422 let error_cases = &[
423 "fromJSON('')", "fromJSON(' ')", "fromJSON('invalid')", "fromJSON('{invalid}')", "fromJSON('[1, 2,]')", ];
429
430 for expr_str in error_cases {
431 let expr = Expr::parse(expr_str)?;
432 let result = expr.consteval();
433 assert!(
434 result.is_none(),
435 "Expected None for invalid JSON: {}",
436 expr_str
437 );
438 }
439
440 Ok(())
441 }
442
443 #[test]
444 fn test_consteval_fromjson_display_format() -> Result<()> {
445 use crate::Evaluation;
446
447 let test_cases = &[
448 (Evaluation::Array(vec![Evaluation::Number(1.0)]), "Array"),
449 (
450 Evaluation::Object(std::collections::HashMap::new()),
451 "Object",
452 ),
453 ];
454
455 for (result, expected) in test_cases {
456 assert_eq!(result.sema().to_string(), *expected);
457 }
458
459 Ok(())
460 }
461
462 #[test]
463 fn test_consteval_tojson_fromjson_roundtrip() -> Result<()> {
464 use crate::Evaluation;
465
466 let test_cases = &[
468 "[1, 2, 3]",
470 r#"{"key": "value"}"#,
472 r#"[1, "hello", true, null]"#,
474 r#"{"array": [1, 2], "object": {"nested": true}}"#,
476 ];
477
478 for json_str in test_cases {
479 let from_expr_str = format!("fromJSON('{}')", json_str);
481 let from_expr = Expr::parse(&from_expr_str)?;
482 let parsed = from_expr.consteval().unwrap();
483
484 let to_result = Call::consteval_tojson(&[parsed.clone()]).unwrap();
486
487 let reparsed_expr_str = format!("fromJSON('{}')", to_result.sema().to_string());
489 let reparsed_expr = Expr::parse(&reparsed_expr_str)?;
490 let reparsed = reparsed_expr.consteval().unwrap();
491
492 match (&parsed, &reparsed) {
494 (Evaluation::Array(a), Evaluation::Array(b)) => assert_eq!(a, b),
495 (Evaluation::Object(_), Evaluation::Object(_)) => {
496 assert!(matches!(parsed, Evaluation::Object(_)));
499 assert!(matches!(reparsed, Evaluation::Object(_)));
500 }
501 (a, b) => assert_eq!(a, b),
502 }
503 }
504
505 Ok(())
506 }
507
508 #[test]
509 fn test_consteval_format() -> Result<()> {
510 use crate::Evaluation;
511
512 let test_cases = &[
513 (
515 "format('Hello {0}', 'world')",
516 Evaluation::String("Hello world".to_string()),
517 ),
518 (
519 "format('{0} {1}', 'Hello', 'world')",
520 Evaluation::String("Hello world".to_string()),
521 ),
522 (
523 "format('Value: {0}', 42)",
524 Evaluation::String("Value: 42".to_string()),
525 ),
526 (
528 "format('{{0}}', 'test')",
529 Evaluation::String("{0}".to_string()),
530 ),
531 (
532 "format('{{Hello}} {0}', 'world')",
533 Evaluation::String("{Hello} world".to_string()),
534 ),
535 (
536 "format('{0} {{1}}', 'Hello')",
537 Evaluation::String("Hello {1}".to_string()),
538 ),
539 (
540 "format('}}{{', 'test')",
541 Evaluation::String("}{".to_string()),
542 ),
543 (
544 "format('{{{{}}}}', 'test')",
545 Evaluation::String("{{}}".to_string()),
546 ),
547 (
549 "format('{0} {1} {2}', 'a', 'b', 'c')",
550 Evaluation::String("a b c".to_string()),
551 ),
552 (
553 "format('{2} {1} {0}', 'a', 'b', 'c')",
554 Evaluation::String("c b a".to_string()),
555 ),
556 (
558 "format('{0} {0} {0}', 'test')",
559 Evaluation::String("test test test".to_string()),
560 ),
561 (
563 "format('Hello world')",
564 Evaluation::String("Hello world".to_string()),
565 ),
566 ("format('abc {{')", Evaluation::String("abc {".to_string())),
568 ("format('abc }}')", Evaluation::String("abc }".to_string())),
569 (
570 "format('abc {{}}')",
571 Evaluation::String("abc {}".to_string()),
572 ),
573 ];
574
575 for (expr_str, expected) in test_cases {
576 let expr = Expr::parse(expr_str)?;
577 let result = expr.consteval().unwrap();
578 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
579 }
580
581 Ok(())
582 }
583
584 #[test]
585 fn test_consteval_format_error_cases() -> Result<()> {
586 let error_cases = &[
587 "format('{0', 'test')", "format('0}', 'test')", "format('{a}', 'test')", "format('{1}', 'test')", "format('{0} {2}', 'a', 'b')", "format('{}', 'test')", "format('{-1}', 'test')", ];
596
597 for expr_str in error_cases {
598 let expr = Expr::parse(expr_str)?;
599 let result = expr.consteval();
600 assert!(
601 result.is_none(),
602 "Expected None for invalid format string: {}",
603 expr_str
604 );
605 }
606
607 Ok(())
608 }
609
610 #[test]
611 fn test_consteval_contains() -> Result<()> {
612 use crate::Evaluation;
613
614 let test_cases = &[
615 (
617 "contains('hello world', 'world')",
618 Evaluation::Boolean(true),
619 ),
620 (
621 "contains('hello world', 'WORLD')",
622 Evaluation::Boolean(true),
623 ),
624 (
625 "contains('HELLO WORLD', 'world')",
626 Evaluation::Boolean(true),
627 ),
628 ("contains('hello world', 'foo')", Evaluation::Boolean(false)),
629 ("contains('test', '')", Evaluation::Boolean(true)),
630 ("contains('123', '2')", Evaluation::Boolean(true)),
632 ("contains(123, '2')", Evaluation::Boolean(true)),
633 ("contains('hello123', 123)", Evaluation::Boolean(true)),
634 ("contains('true', true)", Evaluation::Boolean(true)),
636 ("contains('false', false)", Evaluation::Boolean(true)),
637 ("contains('null', null)", Evaluation::Boolean(true)),
639 ("contains(null, '')", Evaluation::Boolean(true)),
640 (
642 "contains(fromJSON('[1, 2, 3]'), 2)",
643 Evaluation::Boolean(true),
644 ),
645 (
646 "contains(fromJSON('[1, 2, 3]'), 4)",
647 Evaluation::Boolean(false),
648 ),
649 (
650 "contains(fromJSON('[\"a\", \"b\", \"c\"]'), 'b')",
651 Evaluation::Boolean(true),
652 ),
653 (
654 "contains(fromJSON('[\"a\", \"b\", \"c\"]'), 'B')",
655 Evaluation::Boolean(false), ),
657 (
658 "contains(fromJSON('[true, false, null]'), true)",
659 Evaluation::Boolean(true),
660 ),
661 (
662 "contains(fromJSON('[true, false, null]'), null)",
663 Evaluation::Boolean(true),
664 ),
665 (
667 "contains(fromJSON('[]'), 'anything')",
668 Evaluation::Boolean(false),
669 ),
670 (
672 "contains(fromJSON('[1, \"hello\", true, null]'), 'hello')",
673 Evaluation::Boolean(true),
674 ),
675 (
676 "contains(fromJSON('[1, \"hello\", true, null]'), 1)",
677 Evaluation::Boolean(true),
678 ),
679 ];
680
681 for (expr_str, expected) in test_cases {
682 let expr = Expr::parse(expr_str)?;
683 let result = expr.consteval().unwrap();
684 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
685 }
686
687 Ok(())
688 }
689
690 #[test]
691 fn test_consteval_join() -> Result<()> {
692 use crate::Evaluation;
693
694 let test_cases = &[
695 (
697 "join(fromJSON('[\"a\", \"b\", \"c\"]'))",
698 Evaluation::String("a,b,c".to_string()),
699 ),
700 (
701 "join(fromJSON('[1, 2, 3]'))",
702 Evaluation::String("1,2,3".to_string()),
703 ),
704 (
705 "join(fromJSON('[true, false, null]'))",
706 Evaluation::String("true,false,".to_string()),
707 ),
708 (
710 "join(fromJSON('[\"a\", \"b\", \"c\"]'), ' ')",
711 Evaluation::String("a b c".to_string()),
712 ),
713 (
714 "join(fromJSON('[1, 2, 3]'), '-')",
715 Evaluation::String("1-2-3".to_string()),
716 ),
717 (
718 "join(fromJSON('[\"hello\", \"world\"]'), ' | ')",
719 Evaluation::String("hello | world".to_string()),
720 ),
721 (
722 "join(fromJSON('[\"a\", \"b\", \"c\"]'), '')",
723 Evaluation::String("abc".to_string()),
724 ),
725 ("join(fromJSON('[]'))", Evaluation::String("".to_string())),
727 (
728 "join(fromJSON('[]'), '-')",
729 Evaluation::String("".to_string()),
730 ),
731 (
733 "join(fromJSON('[\"single\"]'))",
734 Evaluation::String("single".to_string()),
735 ),
736 (
737 "join(fromJSON('[\"single\"]'), '-')",
738 Evaluation::String("single".to_string()),
739 ),
740 ("join('hello')", Evaluation::String("hello".to_string())),
742 (
743 "join('hello', '-')",
744 Evaluation::String("hello".to_string()),
745 ),
746 ("join(123)", Evaluation::String("123".to_string())),
747 ("join(true)", Evaluation::String("true".to_string())),
748 ("join(null)", Evaluation::String("".to_string())),
749 (
751 "join(fromJSON('[1, \"hello\", true, null]'))",
752 Evaluation::String("1,hello,true,".to_string()),
753 ),
754 (
755 "join(fromJSON('[1, \"hello\", true, null]'), ' | ')",
756 Evaluation::String("1 | hello | true | ".to_string()),
757 ),
758 (
760 "join(fromJSON('[\"a\", \"b\", \"c\"]'), 123)",
761 Evaluation::String("a123b123c".to_string()),
762 ),
763 (
764 "join(fromJSON('[\"a\", \"b\", \"c\"]'), true)",
765 Evaluation::String("atruebtruec".to_string()),
766 ),
767 ];
768
769 for (expr_str, expected) in test_cases {
770 let expr = Expr::parse(expr_str)?;
771 let result = expr.consteval().unwrap();
772 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
773 }
774
775 Ok(())
776 }
777
778 #[test]
779 fn test_consteval_endswith() -> Result<()> {
780 use crate::Evaluation;
781
782 let test_cases = &[
783 (
785 "endsWith('hello world', 'world')",
786 Evaluation::Boolean(true),
787 ),
788 (
789 "endsWith('hello world', 'WORLD')",
790 Evaluation::Boolean(true),
791 ),
792 (
793 "endsWith('HELLO WORLD', 'world')",
794 Evaluation::Boolean(true),
795 ),
796 (
797 "endsWith('hello world', 'hello')",
798 Evaluation::Boolean(false),
799 ),
800 ("endsWith('hello world', 'foo')", Evaluation::Boolean(false)),
801 ("endsWith('test', '')", Evaluation::Boolean(true)),
803 ("endsWith('', '')", Evaluation::Boolean(true)),
804 ("endsWith('', 'test')", Evaluation::Boolean(false)),
805 ("endsWith('123', '3')", Evaluation::Boolean(true)),
807 ("endsWith(123, '3')", Evaluation::Boolean(true)),
808 ("endsWith('hello123', 123)", Evaluation::Boolean(true)),
809 ("endsWith(12345, 345)", Evaluation::Boolean(true)),
810 ("endsWith('test true', true)", Evaluation::Boolean(true)),
812 ("endsWith('test false', false)", Evaluation::Boolean(true)),
813 ("endsWith(true, 'ue')", Evaluation::Boolean(true)),
814 ("endsWith('test null', null)", Evaluation::Boolean(true)),
816 ("endsWith(null, '')", Evaluation::Boolean(true)),
817 ("endsWith('something', null)", Evaluation::Boolean(true)), (
820 "endsWith(fromJSON('[1, 2, 3]'), '3')",
821 Evaluation::Boolean(false),
822 ),
823 (
824 "endsWith('test', fromJSON('[1, 2, 3]'))",
825 Evaluation::Boolean(false),
826 ),
827 (
828 "endsWith(fromJSON('{\"key\": \"value\"}'), 'value')",
829 Evaluation::Boolean(false),
830 ),
831 (
832 "endsWith('test', fromJSON('{\"key\": \"value\"}'))",
833 Evaluation::Boolean(false),
834 ),
835 (
837 "endsWith('TestString', 'STRING')",
838 Evaluation::Boolean(true),
839 ),
840 ("endsWith('CamelCase', 'case')", Evaluation::Boolean(true)),
841 ("endsWith('exact', 'exact')", Evaluation::Boolean(true)),
843 (
845 "endsWith('short', 'very long suffix')",
846 Evaluation::Boolean(false),
847 ),
848 ];
849
850 for (expr_str, expected) in test_cases {
851 let expr = Expr::parse(expr_str)?;
852 let result = expr.consteval().unwrap();
853 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
854 }
855
856 Ok(())
857 }
858
859 #[test]
860 fn test_consteval_startswith() -> Result<()> {
861 use crate::Evaluation;
862
863 let test_cases = &[
864 (
866 "startsWith('hello world', 'hello')",
867 Evaluation::Boolean(true),
868 ),
869 (
870 "startsWith('hello world', 'HELLO')",
871 Evaluation::Boolean(true),
872 ),
873 (
874 "startsWith('HELLO WORLD', 'hello')",
875 Evaluation::Boolean(true),
876 ),
877 (
878 "startsWith('hello world', 'world')",
879 Evaluation::Boolean(false),
880 ),
881 (
882 "startsWith('hello world', 'foo')",
883 Evaluation::Boolean(false),
884 ),
885 ("startsWith('test', '')", Evaluation::Boolean(true)),
887 ("startsWith('', '')", Evaluation::Boolean(true)),
888 ("startsWith('', 'test')", Evaluation::Boolean(false)),
889 ("startsWith('123', '1')", Evaluation::Boolean(true)),
891 ("startsWith(123, '1')", Evaluation::Boolean(true)),
892 ("startsWith('123hello', 123)", Evaluation::Boolean(true)),
893 ("startsWith(12345, 123)", Evaluation::Boolean(true)),
894 ("startsWith('true test', true)", Evaluation::Boolean(true)),
896 ("startsWith('false test', false)", Evaluation::Boolean(true)),
897 ("startsWith(true, 'tr')", Evaluation::Boolean(true)),
898 ("startsWith('null test', null)", Evaluation::Boolean(true)),
900 ("startsWith(null, '')", Evaluation::Boolean(true)),
901 (
902 "startsWith('something', null)",
903 Evaluation::Boolean(true), ),
905 (
907 "startsWith(fromJSON('[1, 2, 3]'), '1')",
908 Evaluation::Boolean(false),
909 ),
910 (
911 "startsWith('test', fromJSON('[1, 2, 3]'))",
912 Evaluation::Boolean(false),
913 ),
914 (
915 "startsWith(fromJSON('{\"key\": \"value\"}'), 'key')",
916 Evaluation::Boolean(false),
917 ),
918 (
919 "startsWith('test', fromJSON('{\"key\": \"value\"}'))",
920 Evaluation::Boolean(false),
921 ),
922 (
924 "startsWith('TestString', 'TEST')",
925 Evaluation::Boolean(true),
926 ),
927 (
928 "startsWith('CamelCase', 'camel')",
929 Evaluation::Boolean(true),
930 ),
931 ("startsWith('exact', 'exact')", Evaluation::Boolean(true)),
933 (
935 "startsWith('short', 'very long prefix')",
936 Evaluation::Boolean(false),
937 ),
938 (
940 "startsWith('prefix_suffix', 'prefix')",
941 Evaluation::Boolean(true),
942 ),
943 (
944 "startsWith('prefix_suffix', 'suffix')",
945 Evaluation::Boolean(false),
946 ),
947 ];
948
949 for (expr_str, expected) in test_cases {
950 let expr = Expr::parse(expr_str)?;
951 let result = expr.consteval().unwrap();
952 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
953 }
954
955 Ok(())
956 }
957
958 #[test]
959 fn test_evaluate_constant_functions() -> Result<()> {
960 use crate::Evaluation;
961
962 let test_cases = &[
963 (
965 "format('{0}', 'hello')",
966 Evaluation::String("hello".to_string()),
967 ),
968 (
969 "format('{0} {1}', 'hello', 'world')",
970 Evaluation::String("hello world".to_string()),
971 ),
972 (
973 "format('Value: {0}', 42)",
974 Evaluation::String("Value: 42".to_string()),
975 ),
976 (
978 "contains('hello world', 'world')",
979 Evaluation::Boolean(true),
980 ),
981 ("contains('hello world', 'foo')", Evaluation::Boolean(false)),
982 ("contains('test', '')", Evaluation::Boolean(true)),
983 (
985 "startsWith('hello world', 'hello')",
986 Evaluation::Boolean(true),
987 ),
988 (
989 "startsWith('hello world', 'world')",
990 Evaluation::Boolean(false),
991 ),
992 ("startsWith('test', '')", Evaluation::Boolean(true)),
993 (
995 "endsWith('hello world', 'world')",
996 Evaluation::Boolean(true),
997 ),
998 (
999 "endsWith('hello world', 'hello')",
1000 Evaluation::Boolean(false),
1001 ),
1002 ("endsWith('test', '')", Evaluation::Boolean(true)),
1003 (
1005 "toJSON('hello')",
1006 Evaluation::String("\"hello\"".to_string()),
1007 ),
1008 ("toJSON(42)", Evaluation::String("42".to_string())),
1009 ("toJSON(true)", Evaluation::String("true".to_string())),
1010 ("toJSON(null)", Evaluation::String("null".to_string())),
1011 (
1013 "fromJSON('\"hello\"')",
1014 Evaluation::String("hello".to_string()),
1015 ),
1016 ("fromJSON('42')", Evaluation::Number(42.0)),
1017 ("fromJSON('true')", Evaluation::Boolean(true)),
1018 ("fromJSON('null')", Evaluation::Null),
1019 (
1021 "fromJSON('[1, 2, 3]')",
1022 Evaluation::Array(vec![
1023 Evaluation::Number(1.0),
1024 Evaluation::Number(2.0),
1025 Evaluation::Number(3.0),
1026 ]),
1027 ),
1028 (
1029 "fromJSON('{\"key\": \"value\"}')",
1030 Evaluation::Object({
1031 let mut map = std::collections::HashMap::new();
1032 map.insert("key".to_string(), Evaluation::String("value".to_string()));
1033 map
1034 }),
1035 ),
1036 ];
1037
1038 for (expr_str, expected) in test_cases {
1039 let expr = Expr::parse(expr_str)?;
1040 let result = expr.consteval().unwrap();
1041 assert_eq!(
1042 result, *expected,
1043 "Failed for expression: {} {result:?}",
1044 expr_str
1045 );
1046 }
1047
1048 Ok(())
1049 }
1050}