1use crate::{Evaluation, EvaluationSema, SpannedExpr};
4
5#[derive(Debug, thiserror::Error)]
7pub enum Error {
8 #[error("Unknown function: {0}")]
10 UnknownFunction(String),
11 #[error("Incorrect arguments for function {0:?}: expected {1}")]
13 Arity(Function, &'static str),
14}
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
20pub enum Function {
21 Contains,
23 StartsWith,
25 EndsWith,
27 Format,
29 Join,
31 ToJSON,
33 FromJSON,
35 HashFiles,
37 Case,
39 Success,
43 Always,
45 Cancelled,
47 Failure,
49}
50
51impl Function {
52 pub(crate) fn new(name: &str) -> Option<Self> {
53 match name.to_ascii_lowercase().as_str() {
54 "contains" => Some(Self::Contains),
55 "startswith" => Some(Self::StartsWith),
56 "endswith" => Some(Self::EndsWith),
57 "format" => Some(Self::Format),
58 "join" => Some(Self::Join),
59 "tojson" => Some(Self::ToJSON),
60 "fromjson" => Some(Self::FromJSON),
61 "hashfiles" => Some(Self::HashFiles),
62 "case" => Some(Self::Case),
63 "success" => Some(Self::Success),
64 "always" => Some(Self::Always),
65 "cancelled" => Some(Self::Cancelled),
66 "failure" => Some(Self::Failure),
67 _ => None,
68 }
69 }
70}
71
72#[derive(Debug, PartialEq)]
74pub struct Call<'src> {
75 pub func: Function,
77 pub args: Vec<SpannedExpr<'src>>,
79}
80
81impl<'src> Call<'src> {
82 pub(crate) fn new(func: &str, args: Vec<SpannedExpr<'src>>) -> Result<Self, Error> {
83 let func = Function::new(func).ok_or_else(|| Error::UnknownFunction(func.to_string()))?;
84
85 match func {
86 Function::Contains | Function::StartsWith | Function::EndsWith if args.len() != 2 => {
87 return Err(Error::Arity(func, "exactly 2 arguments"));
88 }
89 Function::ToJSON | Function::FromJSON if args.len() != 1 => {
90 return Err(Error::Arity(func, "exactly 1 argument"));
91 }
92 Function::Join if args.is_empty() || args.len() > 2 => {
93 return Err(Error::Arity(func, "1 or 2 arguments"));
94 }
95 Function::HashFiles if args.is_empty() => {
96 return Err(Error::Arity(func, "at least 1 argument"));
97 }
98 Function::Case => {
99 if args.len() < 3 {
100 return Err(Error::Arity(func, "at least 3 arguments"));
101 }
102 if args.len().is_multiple_of(2) {
103 return Err(Error::Arity(func, "odd number of arguments"));
104 }
105 }
106 Function::Success | Function::Always | Function::Cancelled | Function::Failure
107 if !args.is_empty() =>
108 {
109 return Err(Error::Arity(func, "no arguments"));
110 }
111 _ => (),
112 }
113
114 Ok(Self { func, args })
115 }
116
117 pub(crate) fn consteval(&self) -> Option<Evaluation> {
120 let args = self
121 .args
122 .iter()
123 .map(|arg| arg.consteval())
124 .collect::<Option<Vec<Evaluation>>>()?;
125
126 match &self.func {
127 Function::Format => Self::consteval_format(&args),
128 Function::Contains => Self::consteval_contains(&args),
129 Function::StartsWith => Self::consteval_startswith(&args),
130 Function::EndsWith => Self::consteval_endswith(&args),
131 Function::ToJSON => Self::consteval_tojson(&args),
132 Function::FromJSON => Self::consteval_fromjson(&args),
133 Function::Join => Self::consteval_join(&args),
134 Function::Case => Self::consteval_case(&args),
135 _ => None,
136 }
137 }
138
139 fn consteval_format(args: &[Evaluation]) -> Option<Evaluation> {
143 if args.is_empty() {
144 return None;
145 }
146
147 let template = args[0].sema().to_string();
148 let mut result = String::new();
149 let mut index = 0;
150
151 while index < template.len() {
152 let lbrace = template[index..].find('{').map(|pos| index + pos);
153 let rbrace = template[index..].find('}').map(|pos| index + pos);
154
155 #[allow(clippy::unwrap_used)]
157 if let Some(lbrace_pos) = lbrace
158 && (rbrace.is_none() || rbrace.unwrap() > lbrace_pos)
159 {
160 if template.as_bytes().get(lbrace_pos + 1) == Some(&b'{') {
162 result.push_str(&template[index..=lbrace_pos]);
163 index = lbrace_pos + 2;
164 continue;
165 }
166
167 if let Some(rbrace_pos) = rbrace
169 && rbrace_pos > lbrace_pos + 1
170 && let Some(arg_index) = Self::read_arg_index(&template, lbrace_pos + 1)
171 {
172 if 1 + arg_index > args.len() - 1 {
174 return None;
176 }
177
178 if lbrace_pos > index {
180 result.push_str(&template[index..lbrace_pos]);
181 }
182
183 result.push_str(&args[1 + arg_index].sema().to_string());
185 index = rbrace_pos + 1;
186 continue;
187 }
188
189 return None;
191 }
192
193 if let Some(rbrace_pos) = rbrace {
195 #[allow(clippy::unwrap_used)]
196 if lbrace.is_none() || lbrace.unwrap() > rbrace_pos {
197 if template.as_bytes().get(rbrace_pos + 1) == Some(&b'}') {
199 result.push_str(&template[index..=rbrace_pos]);
200 index = rbrace_pos + 2;
201 } else {
202 return None;
204 }
205 }
206 } else {
207 result.push_str(&template[index..]);
209 break;
210 }
211 }
212
213 Some(Evaluation::String(result))
214 }
215
216 fn read_arg_index(string: &str, start_index: usize) -> Option<usize> {
218 let mut length = 0;
219 let chars: Vec<char> = string.chars().collect();
220
221 while start_index + length < chars.len() {
223 let next_char = chars[start_index + length];
224 if next_char.is_ascii_digit() {
225 length += 1;
226 } else {
227 break;
228 }
229 }
230
231 if length < 1 {
233 return None;
234 }
235
236 let number_str: String = chars[start_index..start_index + length].iter().collect();
238 number_str.parse::<usize>().ok()
239 }
240
241 fn consteval_contains(args: &[Evaluation]) -> Option<Evaluation> {
245 if args.len() != 2 {
246 return None;
247 }
248
249 let search = &args[0];
250 let item = &args[1];
251
252 match search {
253 Evaluation::String(_)
255 | Evaluation::Number(_)
256 | Evaluation::Boolean(_)
257 | Evaluation::Null => {
258 let search_str = search.sema().to_string().to_uppercase();
259 let item_str = item.sema().to_string().to_uppercase();
260 Some(Evaluation::Boolean(search_str.contains(&item_str)))
261 }
262 Evaluation::Array(arr) => {
264 if arr.iter().any(|element| element.sema() == item.sema()) {
265 Some(Evaluation::Boolean(true))
266 } else {
267 Some(Evaluation::Boolean(false))
268 }
269 }
270 Evaluation::Object(_) => None,
272 }
273 }
274
275 fn consteval_startswith(args: &[Evaluation]) -> Option<Evaluation> {
279 if args.len() != 2 {
280 return None;
281 }
282
283 let search_string = &args[0];
284 let search_value = &args[1];
285
286 match (search_string, search_value) {
288 (
289 Evaluation::String(_)
290 | Evaluation::Number(_)
291 | Evaluation::Boolean(_)
292 | Evaluation::Null,
293 Evaluation::String(_)
294 | Evaluation::Number(_)
295 | Evaluation::Boolean(_)
296 | Evaluation::Null,
297 ) => {
298 let haystack = EvaluationSema::upper_special(&search_string.sema().to_string());
300 let needle = EvaluationSema::upper_special(&search_value.sema().to_string());
301 Some(Evaluation::Boolean(haystack.starts_with(&needle)))
302 }
303 _ => Some(Evaluation::Boolean(false)),
305 }
306 }
307
308 fn consteval_endswith(args: &[Evaluation]) -> Option<Evaluation> {
312 if args.len() != 2 {
313 return None;
314 }
315
316 let search_string = &args[0];
317 let search_value = &args[1];
318
319 match (search_string, search_value) {
321 (
322 Evaluation::String(_)
323 | Evaluation::Number(_)
324 | Evaluation::Boolean(_)
325 | Evaluation::Null,
326 Evaluation::String(_)
327 | Evaluation::Number(_)
328 | Evaluation::Boolean(_)
329 | Evaluation::Null,
330 ) => {
331 let haystack = EvaluationSema::upper_special(&search_string.sema().to_string());
333 let needle = EvaluationSema::upper_special(&search_value.sema().to_string());
334 Some(Evaluation::Boolean(haystack.ends_with(&needle)))
335 }
336 _ => Some(Evaluation::Boolean(false)),
338 }
339 }
340
341 fn consteval_tojson(args: &[Evaluation]) -> Option<Evaluation> {
345 if args.len() != 1 {
346 return None;
347 }
348
349 let value = &args[0];
350 let json_value: serde_json::Value = value.clone().try_into().ok()?;
351 let json_str = serde_json::to_string_pretty(&json_value).ok()?;
352
353 Some(Evaluation::String(json_str))
354 }
355
356 fn consteval_fromjson(args: &[Evaluation]) -> Option<Evaluation> {
360 if args.len() != 1 {
361 return None;
362 }
363
364 let json_str = args[0].sema().to_string();
365
366 if json_str.trim().is_empty() {
368 return None;
369 }
370
371 serde_json::from_str::<serde_json::Value>(&json_str)
372 .ok()?
373 .try_into()
374 .ok()
375 }
376
377 fn consteval_join(args: &[Evaluation]) -> Option<Evaluation> {
381 if args.is_empty() || args.len() > 2 {
382 return None;
383 }
384
385 let array_or_string = &args[0];
386
387 let separator = if args.len() > 1 {
389 args[1].sema().to_string()
390 } else {
391 ",".to_string()
392 };
393
394 match array_or_string {
395 Evaluation::String(_)
397 | Evaluation::Number(_)
398 | Evaluation::Boolean(_)
399 | Evaluation::Null => Some(Evaluation::String(array_or_string.sema().to_string())),
400 Evaluation::Array(arr) => {
402 let joined = arr
403 .iter()
404 .map(|item| item.sema().to_string())
405 .collect::<Vec<String>>()
406 .join(&separator);
407 Some(Evaluation::String(joined))
408 }
409 Evaluation::Object(_) => Some(Evaluation::String("".to_string())),
411 }
412 }
413
414 fn consteval_case(args: &[Evaluation]) -> Option<Evaluation> {
418 if args.len() < 3 || args.len().is_multiple_of(2) {
419 return None;
420 }
421
422 let (pairs, default) = args.as_chunks::<2>();
423 for pair in pairs {
424 let pred = &pair[0];
425 let val = &pair[1];
426
427 match pred {
428 Evaluation::Boolean(true) => return Some(val.clone()),
429 Evaluation::Boolean(false) => continue,
430 _ => return None,
432 }
433 }
434
435 Some(default[0].clone())
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use crate::{
443 Error, Expr,
444 call::{Call, Function},
445 };
446
447 #[test]
448 fn test_function_new() {
449 assert_eq!(Function::new("contains"), Some(Function::Contains));
450 assert_eq!(Function::new("STARTSWITH"), Some(Function::StartsWith));
451 assert_eq!(Function::new("endsWith"), Some(Function::EndsWith));
452
453 assert_eq!(Function::new("unknown"), None);
454 }
455
456 #[test]
457 fn test_consteval_fromjson() -> Result<(), Error> {
458 use crate::Evaluation;
459
460 let test_cases = &[
461 ("fromJSON('null')", Evaluation::Null),
463 ("fromJSON('true')", Evaluation::Boolean(true)),
464 ("fromJSON('false')", Evaluation::Boolean(false)),
465 ("fromJSON('42')", Evaluation::Number(42.0)),
466 ("fromJSON('3.14')", Evaluation::Number(3.14)),
467 ("fromJSON('-0')", Evaluation::Number(0.0)),
468 ("fromJSON('0')", Evaluation::Number(0.0)),
469 (
470 "fromJSON('\"hello\"')",
471 Evaluation::String("hello".to_string()),
472 ),
473 ("fromJSON('\"\"')", Evaluation::String("".to_string())),
474 ("fromJSON('[]')", Evaluation::Array(vec![])),
476 (
477 "fromJSON('[1, 2, 3]')",
478 Evaluation::Array(vec![
479 Evaluation::Number(1.0),
480 Evaluation::Number(2.0),
481 Evaluation::Number(3.0),
482 ]),
483 ),
484 (
485 "fromJSON('[\"a\", \"b\", null, true, 123]')",
486 Evaluation::Array(vec![
487 Evaluation::String("a".to_string()),
488 Evaluation::String("b".to_string()),
489 Evaluation::Null,
490 Evaluation::Boolean(true),
491 Evaluation::Number(123.0),
492 ]),
493 ),
494 (
496 "fromJSON('{}')",
497 Evaluation::Object(std::collections::HashMap::new()),
498 ),
499 (
500 "fromJSON('{\"key\": \"value\"}')",
501 Evaluation::Object({
502 let mut map = std::collections::HashMap::new();
503 map.insert("key".to_string(), Evaluation::String("value".to_string()));
504 map
505 }),
506 ),
507 (
508 "fromJSON('{\"num\": 42, \"bool\": true, \"null\": null}')",
509 Evaluation::Object({
510 let mut map = std::collections::HashMap::new();
511 map.insert("num".to_string(), Evaluation::Number(42.0));
512 map.insert("bool".to_string(), Evaluation::Boolean(true));
513 map.insert("null".to_string(), Evaluation::Null);
514 map
515 }),
516 ),
517 (
519 "fromJSON('{\"array\": [1, 2], \"object\": {\"nested\": true}}')",
520 Evaluation::Object({
521 let mut map = std::collections::HashMap::new();
522 map.insert(
523 "array".to_string(),
524 Evaluation::Array(vec![Evaluation::Number(1.0), Evaluation::Number(2.0)]),
525 );
526 let mut nested_map = std::collections::HashMap::new();
527 nested_map.insert("nested".to_string(), Evaluation::Boolean(true));
528 map.insert("object".to_string(), Evaluation::Object(nested_map));
529 map
530 }),
531 ),
532 ];
533
534 for (expr_str, expected) in test_cases {
535 let expr = Expr::parse(expr_str)?;
536 let result = expr.consteval().unwrap();
537 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
538 }
539
540 Ok(())
541 }
542
543 #[test]
544 fn test_consteval_fromjson_error_cases() -> Result<(), Error> {
545 let error_cases = &[
546 "fromJSON('')", "fromJSON(' ')", "fromJSON('invalid')", "fromJSON('{invalid}')", "fromJSON('[1, 2,]')", ];
552
553 for expr_str in error_cases {
554 let expr = Expr::parse(expr_str)?;
555 let result = expr.consteval();
556 assert!(
557 result.is_none(),
558 "Expected None for invalid JSON: {}",
559 expr_str
560 );
561 }
562
563 Ok(())
564 }
565
566 #[test]
567 fn test_consteval_fromjson_display_format() -> Result<(), Error> {
568 use crate::Evaluation;
569
570 let test_cases = &[
571 (Evaluation::Array(vec![Evaluation::Number(1.0)]), "Array"),
572 (
573 Evaluation::Object(std::collections::HashMap::new()),
574 "Object",
575 ),
576 ];
577
578 for (result, expected) in test_cases {
579 assert_eq!(result.sema().to_string(), *expected);
580 }
581
582 Ok(())
583 }
584
585 #[test]
586 fn test_consteval_tojson_fromjson_roundtrip() -> Result<(), Error> {
587 use crate::Evaluation;
588
589 let test_cases = &[
591 "[1, 2, 3]",
593 r#"{"key": "value"}"#,
595 r#"[1, "hello", true, null]"#,
597 r#"{"array": [1, 2], "object": {"nested": true}}"#,
599 ];
600
601 for json_str in test_cases {
602 let from_expr_str = format!("fromJSON('{}')", json_str);
604 let from_expr = Expr::parse(&from_expr_str)?;
605 let parsed = from_expr.consteval().unwrap();
606
607 let to_result = Call::consteval_tojson(&[parsed.clone()]).unwrap();
609
610 let reparsed_expr_str = format!("fromJSON('{}')", to_result.sema().to_string());
612 let reparsed_expr = Expr::parse(&reparsed_expr_str)?;
613 let reparsed = reparsed_expr.consteval().unwrap();
614
615 match (&parsed, &reparsed) {
617 (Evaluation::Array(a), Evaluation::Array(b)) => assert_eq!(a, b),
618 (Evaluation::Object(_), Evaluation::Object(_)) => {
619 assert!(matches!(parsed, Evaluation::Object(_)));
622 assert!(matches!(reparsed, Evaluation::Object(_)));
623 }
624 (a, b) => assert_eq!(a, b),
625 }
626 }
627
628 Ok(())
629 }
630
631 #[test]
632 fn test_consteval_format() -> Result<(), Error> {
633 use crate::Evaluation;
634
635 let test_cases = &[
636 (
638 "format('Hello {0}', 'world')",
639 Evaluation::String("Hello world".to_string()),
640 ),
641 (
642 "format('{0} {1}', 'Hello', 'world')",
643 Evaluation::String("Hello world".to_string()),
644 ),
645 (
646 "format('Value: {0}', 42)",
647 Evaluation::String("Value: 42".to_string()),
648 ),
649 (
651 "format('{{0}}', 'test')",
652 Evaluation::String("{0}".to_string()),
653 ),
654 (
655 "format('{{Hello}} {0}', 'world')",
656 Evaluation::String("{Hello} world".to_string()),
657 ),
658 (
659 "format('{0} {{1}}', 'Hello')",
660 Evaluation::String("Hello {1}".to_string()),
661 ),
662 (
663 "format('}}{{', 'test')",
664 Evaluation::String("}{".to_string()),
665 ),
666 (
667 "format('{{{{}}}}', 'test')",
668 Evaluation::String("{{}}".to_string()),
669 ),
670 (
672 "format('{0} {1} {2}', 'a', 'b', 'c')",
673 Evaluation::String("a b c".to_string()),
674 ),
675 (
676 "format('{2} {1} {0}', 'a', 'b', 'c')",
677 Evaluation::String("c b a".to_string()),
678 ),
679 (
681 "format('{0} {0} {0}', 'test')",
682 Evaluation::String("test test test".to_string()),
683 ),
684 (
686 "format('Hello world')",
687 Evaluation::String("Hello world".to_string()),
688 ),
689 ("format('abc {{')", Evaluation::String("abc {".to_string())),
691 ("format('abc }}')", Evaluation::String("abc }".to_string())),
692 (
693 "format('abc {{}}')",
694 Evaluation::String("abc {}".to_string()),
695 ),
696 ];
697
698 for (expr_str, expected) in test_cases {
699 let expr = Expr::parse(expr_str)?;
700 let result = expr.consteval().unwrap();
701 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
702 }
703
704 Ok(())
705 }
706
707 #[test]
708 fn test_consteval_format_error_cases() -> Result<(), Error> {
709 let error_cases = &[
710 "format('{0', 'test')", "format('0}', 'test')", "format('{a}', 'test')", "format('{1}', 'test')", "format('{0} {2}', 'a', 'b')", "format('{}', 'test')", "format('{-1}', 'test')", ];
719
720 for expr_str in error_cases {
721 let expr = Expr::parse(expr_str)?;
722 let result = expr.consteval();
723 assert!(
724 result.is_none(),
725 "Expected None for invalid format string: {}",
726 expr_str
727 );
728 }
729
730 Ok(())
731 }
732
733 #[test]
734 fn test_consteval_contains() -> Result<(), Error> {
735 use crate::Evaluation;
736
737 let test_cases = &[
738 (
740 "contains('hello world', 'world')",
741 Evaluation::Boolean(true),
742 ),
743 (
744 "contains('hello world', 'WORLD')",
745 Evaluation::Boolean(true),
746 ),
747 (
748 "contains('HELLO WORLD', 'world')",
749 Evaluation::Boolean(true),
750 ),
751 ("contains('hello world', 'foo')", Evaluation::Boolean(false)),
752 ("contains('test', '')", Evaluation::Boolean(true)),
753 ("contains('123', '2')", Evaluation::Boolean(true)),
755 ("contains(123, '2')", Evaluation::Boolean(true)),
756 ("contains('hello123', 123)", Evaluation::Boolean(true)),
757 ("contains('true', true)", Evaluation::Boolean(true)),
759 ("contains('false', false)", Evaluation::Boolean(true)),
760 ("contains('null', null)", Evaluation::Boolean(true)),
762 ("contains(null, '')", Evaluation::Boolean(true)),
763 (
765 "contains(fromJSON('[1, 2, 3]'), 2)",
766 Evaluation::Boolean(true),
767 ),
768 (
769 "contains(fromJSON('[1, 2, 3]'), 4)",
770 Evaluation::Boolean(false),
771 ),
772 (
773 "contains(fromJSON('[\"a\", \"b\", \"c\"]'), 'b')",
774 Evaluation::Boolean(true),
775 ),
776 (
777 "contains(fromJSON('[\"a\", \"b\", \"c\"]'), 'B')",
778 Evaluation::Boolean(true), ),
780 (
781 "contains(fromJSON('[true, false, null]'), true)",
782 Evaluation::Boolean(true),
783 ),
784 (
785 "contains(fromJSON('[true, false, null]'), null)",
786 Evaluation::Boolean(true),
787 ),
788 (
790 "contains(fromJSON('[]'), 'anything')",
791 Evaluation::Boolean(false),
792 ),
793 (
795 "contains(fromJSON('[1, \"hello\", true, null]'), 'hello')",
796 Evaluation::Boolean(true),
797 ),
798 (
799 "contains(fromJSON('[1, \"hello\", true, null]'), 1)",
800 Evaluation::Boolean(true),
801 ),
802 ];
803
804 for (expr_str, expected) in test_cases {
805 let expr = Expr::parse(expr_str)?;
806 let result = expr.consteval().unwrap();
807 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
808 }
809
810 Ok(())
811 }
812
813 #[test]
814 fn test_consteval_join() -> Result<(), Error> {
815 use crate::Evaluation;
816
817 let test_cases = &[
818 (
820 "join(fromJSON('[\"a\", \"b\", \"c\"]'))",
821 Evaluation::String("a,b,c".to_string()),
822 ),
823 (
824 "join(fromJSON('[1, 2, 3]'))",
825 Evaluation::String("1,2,3".to_string()),
826 ),
827 (
828 "join(fromJSON('[true, false, null]'))",
829 Evaluation::String("true,false,".to_string()),
830 ),
831 (
833 "join(fromJSON('[\"a\", \"b\", \"c\"]'), ' ')",
834 Evaluation::String("a b c".to_string()),
835 ),
836 (
837 "join(fromJSON('[1, 2, 3]'), '-')",
838 Evaluation::String("1-2-3".to_string()),
839 ),
840 (
841 "join(fromJSON('[\"hello\", \"world\"]'), ' | ')",
842 Evaluation::String("hello | world".to_string()),
843 ),
844 (
845 "join(fromJSON('[\"a\", \"b\", \"c\"]'), '')",
846 Evaluation::String("abc".to_string()),
847 ),
848 ("join(fromJSON('[]'))", Evaluation::String("".to_string())),
850 (
851 "join(fromJSON('[]'), '-')",
852 Evaluation::String("".to_string()),
853 ),
854 (
856 "join(fromJSON('[\"single\"]'))",
857 Evaluation::String("single".to_string()),
858 ),
859 (
860 "join(fromJSON('[\"single\"]'), '-')",
861 Evaluation::String("single".to_string()),
862 ),
863 ("join('hello')", Evaluation::String("hello".to_string())),
865 (
866 "join('hello', '-')",
867 Evaluation::String("hello".to_string()),
868 ),
869 ("join(123)", Evaluation::String("123".to_string())),
870 ("join(true)", Evaluation::String("true".to_string())),
871 ("join(null)", Evaluation::String("".to_string())),
872 (
874 "join(fromJSON('[1, \"hello\", true, null]'))",
875 Evaluation::String("1,hello,true,".to_string()),
876 ),
877 (
878 "join(fromJSON('[1, \"hello\", true, null]'), ' | ')",
879 Evaluation::String("1 | hello | true | ".to_string()),
880 ),
881 (
883 "join(fromJSON('[\"a\", \"b\", \"c\"]'), 123)",
884 Evaluation::String("a123b123c".to_string()),
885 ),
886 (
887 "join(fromJSON('[\"a\", \"b\", \"c\"]'), true)",
888 Evaluation::String("atruebtruec".to_string()),
889 ),
890 ];
891
892 for (expr_str, expected) in test_cases {
893 let expr = Expr::parse(expr_str)?;
894 let result = expr.consteval().unwrap();
895 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
896 }
897
898 Ok(())
899 }
900
901 #[test]
902 fn test_consteval_endswith() -> Result<(), Error> {
903 use crate::Evaluation;
904
905 let test_cases = &[
906 (
908 "endsWith('hello world', 'world')",
909 Evaluation::Boolean(true),
910 ),
911 (
912 "endsWith('hello world', 'WORLD')",
913 Evaluation::Boolean(true),
914 ),
915 (
916 "endsWith('HELLO WORLD', 'world')",
917 Evaluation::Boolean(true),
918 ),
919 (
920 "endsWith('hello world', 'hello')",
921 Evaluation::Boolean(false),
922 ),
923 ("endsWith('hello world', 'foo')", Evaluation::Boolean(false)),
924 ("endsWith('test', '')", Evaluation::Boolean(true)),
926 ("endsWith('', '')", Evaluation::Boolean(true)),
927 ("endsWith('', 'test')", Evaluation::Boolean(false)),
928 ("endsWith('123', '3')", Evaluation::Boolean(true)),
930 ("endsWith(123, '3')", Evaluation::Boolean(true)),
931 ("endsWith('hello123', 123)", Evaluation::Boolean(true)),
932 ("endsWith(12345, 345)", Evaluation::Boolean(true)),
933 ("endsWith('test true', true)", Evaluation::Boolean(true)),
935 ("endsWith('test false', false)", Evaluation::Boolean(true)),
936 ("endsWith(true, 'ue')", Evaluation::Boolean(true)),
937 ("endsWith('test null', null)", Evaluation::Boolean(true)),
939 ("endsWith(null, '')", Evaluation::Boolean(true)),
940 ("endsWith('something', null)", Evaluation::Boolean(true)), (
943 "endsWith(fromJSON('[1, 2, 3]'), '3')",
944 Evaluation::Boolean(false),
945 ),
946 (
947 "endsWith('test', fromJSON('[1, 2, 3]'))",
948 Evaluation::Boolean(false),
949 ),
950 (
951 "endsWith(fromJSON('{\"key\": \"value\"}'), 'value')",
952 Evaluation::Boolean(false),
953 ),
954 (
955 "endsWith('test', fromJSON('{\"key\": \"value\"}'))",
956 Evaluation::Boolean(false),
957 ),
958 (
960 "endsWith('TestString', 'STRING')",
961 Evaluation::Boolean(true),
962 ),
963 ("endsWith('CamelCase', 'case')", Evaluation::Boolean(true)),
964 ("endsWith('exact', 'exact')", Evaluation::Boolean(true)),
966 (
968 "endsWith('short', 'very long suffix')",
969 Evaluation::Boolean(false),
970 ),
971 ];
972
973 for (expr_str, expected) in test_cases {
974 let expr = Expr::parse(expr_str)?;
975 let result = expr.consteval().unwrap();
976 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
977 }
978
979 Ok(())
980 }
981
982 #[test]
983 fn test_consteval_startswith() -> Result<(), Error> {
984 use crate::Evaluation;
985
986 let test_cases = &[
987 (
989 "startsWith('hello world', 'hello')",
990 Evaluation::Boolean(true),
991 ),
992 (
993 "startsWith('hello world', 'HELLO')",
994 Evaluation::Boolean(true),
995 ),
996 (
997 "startsWith('HELLO WORLD', 'hello')",
998 Evaluation::Boolean(true),
999 ),
1000 (
1001 "startsWith('hello world', 'world')",
1002 Evaluation::Boolean(false),
1003 ),
1004 (
1005 "startsWith('hello world', 'foo')",
1006 Evaluation::Boolean(false),
1007 ),
1008 ("startsWith('test', '')", Evaluation::Boolean(true)),
1010 ("startsWith('', '')", Evaluation::Boolean(true)),
1011 ("startsWith('', 'test')", Evaluation::Boolean(false)),
1012 ("startsWith('123', '1')", Evaluation::Boolean(true)),
1014 ("startsWith(123, '1')", Evaluation::Boolean(true)),
1015 ("startsWith('123hello', 123)", Evaluation::Boolean(true)),
1016 ("startsWith(12345, 123)", Evaluation::Boolean(true)),
1017 ("startsWith('true test', true)", Evaluation::Boolean(true)),
1019 ("startsWith('false test', false)", Evaluation::Boolean(true)),
1020 ("startsWith(true, 'tr')", Evaluation::Boolean(true)),
1021 ("startsWith('null test', null)", Evaluation::Boolean(true)),
1023 ("startsWith(null, '')", Evaluation::Boolean(true)),
1024 (
1025 "startsWith('something', null)",
1026 Evaluation::Boolean(true), ),
1028 (
1030 "startsWith(fromJSON('[1, 2, 3]'), '1')",
1031 Evaluation::Boolean(false),
1032 ),
1033 (
1034 "startsWith('test', fromJSON('[1, 2, 3]'))",
1035 Evaluation::Boolean(false),
1036 ),
1037 (
1038 "startsWith(fromJSON('{\"key\": \"value\"}'), 'key')",
1039 Evaluation::Boolean(false),
1040 ),
1041 (
1042 "startsWith('test', fromJSON('{\"key\": \"value\"}'))",
1043 Evaluation::Boolean(false),
1044 ),
1045 (
1047 "startsWith('TestString', 'TEST')",
1048 Evaluation::Boolean(true),
1049 ),
1050 (
1051 "startsWith('CamelCase', 'camel')",
1052 Evaluation::Boolean(true),
1053 ),
1054 ("startsWith('exact', 'exact')", Evaluation::Boolean(true)),
1056 (
1058 "startsWith('short', 'very long prefix')",
1059 Evaluation::Boolean(false),
1060 ),
1061 (
1063 "startsWith('prefix_suffix', 'prefix')",
1064 Evaluation::Boolean(true),
1065 ),
1066 (
1067 "startsWith('prefix_suffix', 'suffix')",
1068 Evaluation::Boolean(false),
1069 ),
1070 ];
1071
1072 for (expr_str, expected) in test_cases {
1073 let expr = Expr::parse(expr_str)?;
1074 let result = expr.consteval().unwrap();
1075 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
1076 }
1077
1078 Ok(())
1079 }
1080
1081 #[test]
1082 fn test_consteval_case() -> Result<(), Error> {
1083 use crate::Evaluation;
1084
1085 let test_cases = &[
1086 (
1088 "case(true, 'yes', false, 'no', 'maybe')",
1089 Some(Evaluation::String("yes".to_string())),
1090 ),
1091 (
1092 "case(false, 'yes', true, 'no', 'maybe')",
1093 Some(Evaluation::String("no".to_string())),
1094 ),
1095 (
1097 "case(false, 'first', false, 'second', true, 'third', 'default')",
1098 Some(Evaluation::String("third".to_string())),
1099 ),
1100 (
1102 "case(false, 'first', false, 'second', false, 'third', 'default')",
1103 Some(Evaluation::String("default".to_string())),
1104 ),
1105 ("case(1, 'yes', 0, 'no', 'maybe')", None),
1107 ];
1108
1109 for (expr_str, expected) in test_cases {
1110 let expr = Expr::parse(expr_str)?;
1111 let result = expr.consteval();
1112 assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
1113 }
1114
1115 Ok(())
1116 }
1117
1118 #[test]
1119 fn test_evaluate_constant_functions() -> Result<(), Error> {
1120 use crate::Evaluation;
1121
1122 let test_cases = &[
1123 (
1125 "format('{0}', 'hello')",
1126 Evaluation::String("hello".to_string()),
1127 ),
1128 (
1129 "format('{0} {1}', 'hello', 'world')",
1130 Evaluation::String("hello world".to_string()),
1131 ),
1132 (
1133 "format('Value: {0}', 42)",
1134 Evaluation::String("Value: 42".to_string()),
1135 ),
1136 (
1138 "contains('hello world', 'world')",
1139 Evaluation::Boolean(true),
1140 ),
1141 ("contains('hello world', 'foo')", Evaluation::Boolean(false)),
1142 ("contains('test', '')", Evaluation::Boolean(true)),
1143 (
1145 "startsWith('hello world', 'hello')",
1146 Evaluation::Boolean(true),
1147 ),
1148 (
1149 "startsWith('hello world', 'world')",
1150 Evaluation::Boolean(false),
1151 ),
1152 ("startsWith('test', '')", Evaluation::Boolean(true)),
1153 (
1155 "endsWith('hello world', 'world')",
1156 Evaluation::Boolean(true),
1157 ),
1158 (
1159 "endsWith('hello world', 'hello')",
1160 Evaluation::Boolean(false),
1161 ),
1162 ("endsWith('test', '')", Evaluation::Boolean(true)),
1163 (
1165 "toJSON('hello')",
1166 Evaluation::String("\"hello\"".to_string()),
1167 ),
1168 ("toJSON(42)", Evaluation::String("42".to_string())),
1169 ("toJSON(true)", Evaluation::String("true".to_string())),
1170 ("toJSON(null)", Evaluation::String("null".to_string())),
1171 (
1173 "fromJSON('\"hello\"')",
1174 Evaluation::String("hello".to_string()),
1175 ),
1176 ("fromJSON('42')", Evaluation::Number(42.0)),
1177 ("fromJSON('true')", Evaluation::Boolean(true)),
1178 ("fromJSON('null')", Evaluation::Null),
1179 (
1181 "fromJSON('[1, 2, 3]')",
1182 Evaluation::Array(vec![
1183 Evaluation::Number(1.0),
1184 Evaluation::Number(2.0),
1185 Evaluation::Number(3.0),
1186 ]),
1187 ),
1188 (
1189 "fromJSON('{\"key\": \"value\"}')",
1190 Evaluation::Object({
1191 let mut map = std::collections::HashMap::new();
1192 map.insert("key".to_string(), Evaluation::String("value".to_string()));
1193 map
1194 }),
1195 ),
1196 ];
1197
1198 for (expr_str, expected) in test_cases {
1199 let expr = Expr::parse(expr_str)?;
1200 let result = expr.consteval().unwrap();
1201 assert_eq!(
1202 result, *expected,
1203 "Failed for expression: {} {result:?}",
1204 expr_str
1205 );
1206 }
1207
1208 Ok(())
1209 }
1210}