1use crate::engine::module::{ExportItem, ExportList, ImportType, ItemType, ModuleManager};
2use crate::engine::rule::{Condition, ConditionGroup, Rule};
3use crate::errors::{Result, RuleEngineError};
4use crate::types::{ActionType, Operator, Value};
5use chrono::{DateTime, Utc};
6use once_cell::sync::Lazy;
7use regex::Regex;
8use std::collections::HashMap;
9
10#[cfg(feature = "streaming")]
12pub mod stream_syntax;
13
14static RULE_REGEX: Lazy<Regex> = Lazy::new(|| {
16 Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
17 .expect("Invalid rule regex pattern")
18});
19
20static RULE_SPLIT_REGEX: Lazy<Regex> = Lazy::new(|| {
21 Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
22 .expect("Invalid rule split regex pattern")
23});
24
25static DEFMODULE_REGEX: Lazy<Regex> = Lazy::new(|| {
27 Regex::new(r#"defmodule\s+([A-Z_]\w*)\s*\{([^}]*)\}"#).expect("Invalid defmodule regex pattern")
28});
29
30static DEFMODULE_SPLIT_REGEX: Lazy<Regex> = Lazy::new(|| {
31 Regex::new(r#"(?s)defmodule\s+[A-Z_]\w*\s*\{[^}]*\}"#)
32 .expect("Invalid defmodule split regex pattern")
33});
34
35static WHEN_THEN_REGEX: Lazy<Regex> = Lazy::new(|| {
36 Regex::new(r"when\s+(.+?)\s+then\s+(.+)").expect("Invalid when-then regex pattern")
37});
38
39static SALIENCE_REGEX: Lazy<Regex> =
40 Lazy::new(|| Regex::new(r"salience\s+(\d+)").expect("Invalid salience regex pattern"));
41
42static TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
43 Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
44 .expect("Invalid test condition regex")
45});
46
47static TYPED_TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
48 Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
49 .expect("Invalid typed test condition regex")
50});
51
52static FUNCTION_CALL_REGEX: Lazy<Regex> = Lazy::new(|| {
53 Regex::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
54 .expect("Invalid function call regex")
55});
56
57static CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
58 Regex::new(r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*(?:\s*[+\-*/%]\s*[a-zA-Z0-9_\.]+)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
59 .expect("Invalid condition regex")
60});
61
62static METHOD_CALL_REGEX: Lazy<Regex> =
63 Lazy::new(|| Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).expect("Invalid method call regex"));
64
65static FUNCTION_BINDING_REGEX: Lazy<Regex> = Lazy::new(|| {
66 Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).expect("Invalid function binding regex")
67});
68
69static MULTIFIELD_COLLECT_REGEX: Lazy<Regex> = Lazy::new(|| {
71 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
72 .expect("Invalid multifield collect regex")
73});
74
75static MULTIFIELD_COUNT_REGEX: Lazy<Regex> = Lazy::new(|| {
76 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
77 .expect("Invalid multifield count regex")
78});
79
80static MULTIFIELD_FIRST_REGEX: Lazy<Regex> = Lazy::new(|| {
81 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
82 .expect("Invalid multifield first regex")
83});
84
85static MULTIFIELD_LAST_REGEX: Lazy<Regex> = Lazy::new(|| {
86 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
87 .expect("Invalid multifield last regex")
88});
89
90static MULTIFIELD_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
91 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#).expect("Invalid multifield empty regex")
92});
93
94static MULTIFIELD_NOT_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
95 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
96 .expect("Invalid multifield not_empty regex")
97});
98
99static SIMPLE_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
100 Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).expect("Invalid simple condition regex")
101});
102
103pub struct GRLParser;
106
107#[derive(Debug, Default)]
109struct RuleAttributes {
110 pub no_loop: bool,
111 pub lock_on_active: bool,
112 pub agenda_group: Option<String>,
113 pub activation_group: Option<String>,
114 pub date_effective: Option<DateTime<Utc>>,
115 pub date_expires: Option<DateTime<Utc>>,
116}
117
118#[derive(Debug, Clone)]
120pub struct ParsedGRL {
121 pub rules: Vec<Rule>,
123 pub module_manager: ModuleManager,
125 pub rule_modules: HashMap<String, String>,
127}
128
129impl Default for ParsedGRL {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl ParsedGRL {
136 pub fn new() -> Self {
137 Self {
138 rules: Vec::new(),
139 module_manager: ModuleManager::new(),
140 rule_modules: HashMap::new(),
141 }
142 }
143}
144
145impl GRLParser {
146 pub fn parse_rule(grl_text: &str) -> Result<Rule> {
159 let mut parser = GRLParser;
160 parser.parse_single_rule(grl_text)
161 }
162
163 pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
165 let mut parser = GRLParser;
166 parser.parse_multiple_rules(grl_text)
167 }
168
169 pub fn parse_with_modules(grl_text: &str) -> Result<ParsedGRL> {
187 let mut parser = GRLParser;
188 parser.parse_grl_with_modules(grl_text)
189 }
190
191 fn parse_grl_with_modules(&mut self, grl_text: &str) -> Result<ParsedGRL> {
192 let mut result = ParsedGRL::new();
193
194 for module_match in DEFMODULE_SPLIT_REGEX.find_iter(grl_text) {
196 let module_def = module_match.as_str();
197 self.parse_and_register_module(module_def, &mut result.module_manager)?;
198 }
199
200 let rules_text = DEFMODULE_SPLIT_REGEX.replace_all(grl_text, "");
202
203 let rules = self.parse_multiple_rules(&rules_text)?;
205
206 for rule in rules {
208 let module_name = self.extract_module_from_context(grl_text, &rule.name);
209 result
210 .rule_modules
211 .insert(rule.name.clone(), module_name.clone());
212
213 if let Ok(module) = result.module_manager.get_module_mut(&module_name) {
215 module.add_rule(&rule.name);
216 }
217
218 result.rules.push(rule);
219 }
220
221 Ok(result)
222 }
223
224 fn parse_and_register_module(
225 &self,
226 module_def: &str,
227 manager: &mut ModuleManager,
228 ) -> Result<()> {
229 if let Some(captures) = DEFMODULE_REGEX.captures(module_def) {
231 let module_name = captures.get(1).unwrap().as_str().to_string();
232 let module_body = captures.get(2).unwrap().as_str();
233
234 let _ = manager.create_module(&module_name);
236 let module = manager.get_module_mut(&module_name)?;
237
238 if let Some(export_type) = self.extract_directive(module_body, "export:") {
240 let exports = if export_type.trim() == "all" {
241 ExportList::All
242 } else if export_type.trim() == "none" {
243 ExportList::None
244 } else {
245 ExportList::Specific(vec![ExportItem {
247 item_type: ItemType::All,
248 pattern: export_type.trim().to_string(),
249 }])
250 };
251 module.set_exports(exports);
252 }
253
254 let import_lines: Vec<&str> = module_body
256 .lines()
257 .filter(|line| line.trim().starts_with("import:"))
258 .collect();
259
260 for import_line in import_lines {
261 if let Some(import_spec) = self.extract_directive(import_line, "import:") {
262 self.parse_import_spec(&module_name, &import_spec, manager)?;
264 }
265 }
266 }
267
268 Ok(())
269 }
270
271 fn extract_directive(&self, text: &str, directive: &str) -> Option<String> {
272 if let Some(pos) = text.find(directive) {
273 let after_directive = &text[pos + directive.len()..];
274
275 let end = after_directive
277 .find("import:")
278 .or_else(|| after_directive.find("export:"))
279 .unwrap_or(after_directive.len());
280
281 Some(after_directive[..end].trim().to_string())
282 } else {
283 None
284 }
285 }
286
287 fn parse_import_spec(
288 &self,
289 importing_module: &str,
290 spec: &str,
291 manager: &mut ModuleManager,
292 ) -> Result<()> {
293 let parts: Vec<&str> = spec.splitn(2, '(').collect();
295 if parts.is_empty() {
296 return Ok(());
297 }
298
299 let source_module = parts[0].trim().to_string();
300 let rest = if parts.len() > 1 { parts[1] } else { "" };
301
302 if rest.contains("rules") {
304 manager.import_from(importing_module, &source_module, ImportType::AllRules, "*")?;
305 }
306
307 if rest.contains("templates") {
308 manager.import_from(
309 importing_module,
310 &source_module,
311 ImportType::AllTemplates,
312 "*",
313 )?;
314 }
315
316 Ok(())
317 }
318
319 fn extract_module_from_context(&self, grl_text: &str, rule_name: &str) -> String {
320 if let Some(rule_pos) = grl_text
322 .find(&format!("rule \"{}\"", rule_name))
323 .or_else(|| grl_text.find(&format!("rule {}", rule_name)))
324 {
325 let before = &grl_text[..rule_pos];
327 if let Some(module_pos) = before.rfind(";; MODULE:") {
328 let after_module_marker = &before[module_pos + 10..];
329 if let Some(end_of_line) = after_module_marker.find('\n') {
330 let module_line = &after_module_marker[..end_of_line].trim();
331 if let Some(first_word) = module_line.split_whitespace().next() {
333 return first_word.to_string();
334 }
335 }
336 }
337 }
338
339 "MAIN".to_string()
341 }
342
343 fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
344 let cleaned = self.clean_text(grl_text);
345
346 let captures =
348 RULE_REGEX
349 .captures(&cleaned)
350 .ok_or_else(|| RuleEngineError::ParseError {
351 message: format!("Invalid GRL rule format. Input: {}", cleaned),
352 })?;
353
354 let rule_name = if let Some(quoted_name) = captures.get(1) {
356 quoted_name.as_str().to_string()
357 } else if let Some(unquoted_name) = captures.get(2) {
358 unquoted_name.as_str().to_string()
359 } else {
360 return Err(RuleEngineError::ParseError {
361 message: "Could not extract rule name".to_string(),
362 });
363 };
364
365 let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
367
368 let rule_body = captures.get(4).unwrap().as_str();
370
371 let salience = self.extract_salience(attributes_section)?;
373
374 let when_then_captures =
376 WHEN_THEN_REGEX
377 .captures(rule_body)
378 .ok_or_else(|| RuleEngineError::ParseError {
379 message: "Missing when or then clause".to_string(),
380 })?;
381
382 let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
383 let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
384
385 let conditions = self.parse_when_clause(when_clause)?;
387 let actions = self.parse_then_clause(then_clause)?;
388
389 let attributes = self.parse_rule_attributes(attributes_section)?;
391
392 let mut rule = Rule::new(rule_name, conditions, actions);
394 rule = rule.with_priority(salience);
395
396 if attributes.no_loop {
398 rule = rule.with_no_loop(true);
399 }
400 if attributes.lock_on_active {
401 rule = rule.with_lock_on_active(true);
402 }
403 if let Some(agenda_group) = attributes.agenda_group {
404 rule = rule.with_agenda_group(agenda_group);
405 }
406 if let Some(activation_group) = attributes.activation_group {
407 rule = rule.with_activation_group(activation_group);
408 }
409 if let Some(date_effective) = attributes.date_effective {
410 rule = rule.with_date_effective(date_effective);
411 }
412 if let Some(date_expires) = attributes.date_expires {
413 rule = rule.with_date_expires(date_expires);
414 }
415
416 Ok(rule)
417 }
418
419 fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
420 let mut rules = Vec::new();
423
424 for rule_match in RULE_SPLIT_REGEX.find_iter(grl_text) {
425 let rule_text = rule_match.as_str();
426 let rule = self.parse_single_rule(rule_text)?;
427 rules.push(rule);
428 }
429
430 Ok(rules)
431 }
432
433 fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
435 let mut attributes = RuleAttributes::default();
436
437 let mut attrs_section = rule_header.to_string();
441
442 let quoted_regex = Regex::new(r#""[^"]*""#).map_err(|e| RuleEngineError::ParseError {
444 message: format!("Invalid quoted string regex: {}", e),
445 })?;
446 attrs_section = quoted_regex.replace_all(&attrs_section, "").to_string();
447
448 if let Some(rule_pos) = attrs_section.find("rule") {
450 let after_rule = &attrs_section[rule_pos + 4..];
452 if let Some(first_keyword) = after_rule
453 .find("salience")
454 .or_else(|| after_rule.find("no-loop"))
455 .or_else(|| after_rule.find("lock-on-active"))
456 .or_else(|| after_rule.find("agenda-group"))
457 .or_else(|| after_rule.find("activation-group"))
458 .or_else(|| after_rule.find("date-effective"))
459 .or_else(|| after_rule.find("date-expires"))
460 {
461 attrs_section = after_rule[first_keyword..].to_string();
462 }
463 }
464
465 let no_loop_regex =
467 Regex::new(r"\bno-loop\b").map_err(|e| RuleEngineError::ParseError {
468 message: format!("Invalid no-loop regex: {}", e),
469 })?;
470 let lock_on_active_regex =
471 Regex::new(r"\block-on-active\b").map_err(|e| RuleEngineError::ParseError {
472 message: format!("Invalid lock-on-active regex: {}", e),
473 })?;
474
475 if no_loop_regex.is_match(&attrs_section) {
476 attributes.no_loop = true;
477 }
478 if lock_on_active_regex.is_match(&attrs_section) {
479 attributes.lock_on_active = true;
480 }
481
482 if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
484 attributes.agenda_group = Some(agenda_group);
485 }
486
487 if let Some(activation_group) =
489 self.extract_quoted_attribute(rule_header, "activation-group")?
490 {
491 attributes.activation_group = Some(activation_group);
492 }
493
494 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
496 attributes.date_effective = Some(self.parse_date_string(&date_str)?);
497 }
498
499 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
501 attributes.date_expires = Some(self.parse_date_string(&date_str)?);
502 }
503
504 Ok(attributes)
505 }
506
507 fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
509 let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
510 let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
511 message: format!("Invalid attribute regex for {}: {}", attribute, e),
512 })?;
513
514 if let Some(captures) = regex.captures(header) {
515 if let Some(value) = captures.get(1) {
516 return Ok(Some(value.as_str().to_string()));
517 }
518 }
519
520 Ok(None)
521 }
522
523 fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
525 if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
527 return Ok(date.with_timezone(&Utc));
528 }
529
530 let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
532
533 for format in &formats {
534 if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
535 return Ok(naive_date.and_utc());
536 }
537 if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
538 return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
539 }
540 }
541
542 Err(RuleEngineError::ParseError {
543 message: format!("Unable to parse date: {}", date_str),
544 })
545 }
546
547 fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
549 if let Some(captures) = SALIENCE_REGEX.captures(attributes_section) {
550 if let Some(salience_match) = captures.get(1) {
551 return salience_match.as_str().parse::<i32>().map_err(|e| {
552 RuleEngineError::ParseError {
553 message: format!("Invalid salience value: {}", e),
554 }
555 });
556 }
557 }
558
559 Ok(0) }
561
562 fn clean_text(&self, text: &str) -> String {
563 text.lines()
564 .map(|line| line.trim())
565 .filter(|line| !line.is_empty() && !line.starts_with("//"))
566 .collect::<Vec<_>>()
567 .join(" ")
568 }
569
570 fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
571 let trimmed = when_clause.trim();
573
574 let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
576 let inner = &trimmed[1..trimmed.len() - 1];
578 if self.is_balanced_parentheses(inner) {
579 inner
580 } else {
581 trimmed
582 }
583 } else {
584 trimmed
585 };
586
587 if let Some(parts) = self.split_logical_operator(clause, "||") {
589 return self.parse_or_parts(parts);
590 }
591
592 if let Some(parts) = self.split_logical_operator(clause, "&&") {
594 return self.parse_and_parts(parts);
595 }
596
597 if clause.trim_start().starts_with("!") {
599 return self.parse_not_condition(clause);
600 }
601
602 if clause.trim_start().starts_with("exists(") {
604 return self.parse_exists_condition(clause);
605 }
606
607 if clause.trim_start().starts_with("forall(") {
609 return self.parse_forall_condition(clause);
610 }
611
612 if clause.trim_start().starts_with("accumulate(") {
614 return self.parse_accumulate_condition(clause);
615 }
616
617 self.parse_single_condition(clause)
619 }
620
621 fn is_balanced_parentheses(&self, text: &str) -> bool {
622 let mut count = 0;
623 for ch in text.chars() {
624 match ch {
625 '(' => count += 1,
626 ')' => {
627 count -= 1;
628 if count < 0 {
629 return false;
630 }
631 }
632 _ => {}
633 }
634 }
635 count == 0
636 }
637
638 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
639 let mut parts = Vec::new();
640 let mut current_part = String::new();
641 let mut paren_count = 0;
642 let mut chars = clause.chars().peekable();
643
644 while let Some(ch) = chars.next() {
645 match ch {
646 '(' => {
647 paren_count += 1;
648 current_part.push(ch);
649 }
650 ')' => {
651 paren_count -= 1;
652 current_part.push(ch);
653 }
654 '&' if operator == "&&" && paren_count == 0 => {
655 if chars.peek() == Some(&'&') {
656 chars.next(); parts.push(current_part.trim().to_string());
658 current_part.clear();
659 } else {
660 current_part.push(ch);
661 }
662 }
663 '|' if operator == "||" && paren_count == 0 => {
664 if chars.peek() == Some(&'|') {
665 chars.next(); parts.push(current_part.trim().to_string());
667 current_part.clear();
668 } else {
669 current_part.push(ch);
670 }
671 }
672 _ => {
673 current_part.push(ch);
674 }
675 }
676 }
677
678 if !current_part.trim().is_empty() {
679 parts.push(current_part.trim().to_string());
680 }
681
682 if parts.len() > 1 {
683 Some(parts)
684 } else {
685 None
686 }
687 }
688
689 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
690 let mut conditions = Vec::new();
691 for part in parts {
692 let condition = self.parse_when_clause(&part)?;
693 conditions.push(condition);
694 }
695
696 if conditions.is_empty() {
697 return Err(RuleEngineError::ParseError {
698 message: "No conditions found in OR".to_string(),
699 });
700 }
701
702 let mut iter = conditions.into_iter();
703 let mut result = iter.next().unwrap();
704 for condition in iter {
705 result = ConditionGroup::or(result, condition);
706 }
707
708 Ok(result)
709 }
710
711 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
712 let mut conditions = Vec::new();
713 for part in parts {
714 let condition = self.parse_when_clause(&part)?;
715 conditions.push(condition);
716 }
717
718 if conditions.is_empty() {
719 return Err(RuleEngineError::ParseError {
720 message: "No conditions found in AND".to_string(),
721 });
722 }
723
724 let mut iter = conditions.into_iter();
725 let mut result = iter.next().unwrap();
726 for condition in iter {
727 result = ConditionGroup::and(result, condition);
728 }
729
730 Ok(result)
731 }
732
733 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
734 let inner_clause = clause.strip_prefix("!").unwrap().trim();
735 let inner_condition = self.parse_when_clause(inner_clause)?;
736 Ok(ConditionGroup::not(inner_condition))
737 }
738
739 fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
740 let clause = clause.trim_start();
741 if !clause.starts_with("exists(") || !clause.ends_with(")") {
742 return Err(RuleEngineError::ParseError {
743 message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
744 });
745 }
746
747 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
750 Ok(ConditionGroup::exists(inner_condition))
751 }
752
753 fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
754 let clause = clause.trim_start();
755 if !clause.starts_with("forall(") || !clause.ends_with(")") {
756 return Err(RuleEngineError::ParseError {
757 message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
758 });
759 }
760
761 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
764 Ok(ConditionGroup::forall(inner_condition))
765 }
766
767 fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
768 let clause = clause.trim_start();
769 if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
770 return Err(RuleEngineError::ParseError {
771 message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)"
772 .to_string(),
773 });
774 }
775
776 let inner = &clause[11..clause.len() - 1]; let parts = self.split_accumulate_parts(inner)?;
781
782 if parts.len() != 2 {
783 return Err(RuleEngineError::ParseError {
784 message: format!(
785 "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
786 parts.len()
787 ),
788 });
789 }
790
791 let pattern_part = parts[0].trim();
792 let function_part = parts[1].trim();
793
794 let (source_pattern, extract_field, source_conditions) =
796 self.parse_accumulate_pattern(pattern_part)?;
797
798 let (function, function_arg) = self.parse_accumulate_function(function_part)?;
800
801 let result_var = "$result".to_string();
805
806 Ok(ConditionGroup::accumulate(
807 result_var,
808 source_pattern,
809 extract_field,
810 source_conditions,
811 function,
812 function_arg,
813 ))
814 }
815
816 fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
817 let mut parts = Vec::new();
818 let mut current = String::new();
819 let mut paren_depth = 0;
820
821 for ch in content.chars() {
822 match ch {
823 '(' => {
824 paren_depth += 1;
825 current.push(ch);
826 }
827 ')' => {
828 paren_depth -= 1;
829 current.push(ch);
830 }
831 ',' if paren_depth == 0 => {
832 parts.push(current.trim().to_string());
833 current.clear();
834 }
835 _ => {
836 current.push(ch);
837 }
838 }
839 }
840
841 if !current.trim().is_empty() {
842 parts.push(current.trim().to_string());
843 }
844
845 Ok(parts)
846 }
847
848 fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
849 let pattern = pattern.trim();
856
857 let paren_pos = pattern
859 .find('(')
860 .ok_or_else(|| RuleEngineError::ParseError {
861 message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
862 })?;
863
864 let source_pattern = pattern[..paren_pos].trim().to_string();
865
866 if !pattern.ends_with(')') {
868 return Err(RuleEngineError::ParseError {
869 message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
870 });
871 }
872
873 let inner = &pattern[paren_pos + 1..pattern.len() - 1];
874
875 let parts = self.split_pattern_parts(inner)?;
877
878 let mut extract_field = String::new();
879 let mut source_conditions = Vec::new();
880
881 for part in parts {
882 let part = part.trim();
883
884 if part.contains(':') && part.starts_with('$') {
886 let colon_pos = part.find(':').unwrap();
887 let _var_name = part[..colon_pos].trim();
888 let field_name = part[colon_pos + 1..].trim();
889 extract_field = field_name.to_string();
890 } else if part.contains("==")
891 || part.contains("!=")
892 || part.contains(">=")
893 || part.contains("<=")
894 || part.contains('>')
895 || part.contains('<')
896 {
897 source_conditions.push(part.to_string());
899 }
900 }
901
902 Ok((source_pattern, extract_field, source_conditions))
903 }
904
905 fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
906 let mut parts = Vec::new();
907 let mut current = String::new();
908 let mut paren_depth = 0;
909 let mut in_quotes = false;
910 let mut quote_char = ' ';
911
912 for ch in content.chars() {
913 match ch {
914 '"' | '\'' if !in_quotes => {
915 in_quotes = true;
916 quote_char = ch;
917 current.push(ch);
918 }
919 '"' | '\'' if in_quotes && ch == quote_char => {
920 in_quotes = false;
921 current.push(ch);
922 }
923 '(' if !in_quotes => {
924 paren_depth += 1;
925 current.push(ch);
926 }
927 ')' if !in_quotes => {
928 paren_depth -= 1;
929 current.push(ch);
930 }
931 ',' if !in_quotes && paren_depth == 0 => {
932 parts.push(current.trim().to_string());
933 current.clear();
934 }
935 _ => {
936 current.push(ch);
937 }
938 }
939 }
940
941 if !current.trim().is_empty() {
942 parts.push(current.trim().to_string());
943 }
944
945 Ok(parts)
946 }
947
948 fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
949 let function_str = function_str.trim();
952
953 let paren_pos = function_str
954 .find('(')
955 .ok_or_else(|| RuleEngineError::ParseError {
956 message: format!(
957 "Invalid accumulate function: missing '(' in '{}'",
958 function_str
959 ),
960 })?;
961
962 let function_name = function_str[..paren_pos].trim().to_string();
963
964 if !function_str.ends_with(')') {
965 return Err(RuleEngineError::ParseError {
966 message: format!(
967 "Invalid accumulate function: missing ')' in '{}'",
968 function_str
969 ),
970 });
971 }
972
973 let args = &function_str[paren_pos + 1..function_str.len() - 1];
974 let function_arg = args.trim().to_string();
975
976 Ok((function_name, function_arg))
977 }
978
979 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
980 let trimmed_clause = clause.trim();
982 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
983 trimmed_clause[1..trimmed_clause.len() - 1].trim()
984 } else {
985 trimmed_clause
986 };
987
988 #[cfg(feature = "streaming")]
991 if clause_to_parse.contains("from stream(") {
992 return self.parse_stream_pattern_condition(clause_to_parse);
993 }
994
995 if let Some(captures) = MULTIFIELD_COLLECT_REGEX.captures(clause_to_parse) {
1002 let field = captures.get(1).unwrap().as_str().to_string();
1003 let variable = captures.get(2).unwrap().as_str().to_string();
1004
1005 let condition = Condition::with_multifield_collect(field, variable);
1008 return Ok(ConditionGroup::single(condition));
1009 }
1010
1011 if let Some(captures) = MULTIFIELD_COUNT_REGEX.captures(clause_to_parse) {
1018 let field = captures.get(1).unwrap().as_str().to_string();
1019 let operator_str = captures.get(2).unwrap().as_str();
1020 let value_str = captures.get(3).unwrap().as_str().trim();
1021
1022 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1023 RuleEngineError::InvalidOperator {
1024 operator: operator_str.to_string(),
1025 }
1026 })?;
1027
1028 let value = self.parse_value(value_str)?;
1029
1030 let condition = Condition::with_multifield_count(field, operator, value);
1031 return Ok(ConditionGroup::single(condition));
1032 }
1033
1034 if let Some(captures) = MULTIFIELD_FIRST_REGEX.captures(clause_to_parse) {
1037 let field = captures.get(1).unwrap().as_str().to_string();
1038 let variable = captures.get(2).map(|m| m.as_str().to_string());
1039
1040 let condition = Condition::with_multifield_first(field, variable);
1041 return Ok(ConditionGroup::single(condition));
1042 }
1043
1044 if let Some(captures) = MULTIFIELD_LAST_REGEX.captures(clause_to_parse) {
1047 let field = captures.get(1).unwrap().as_str().to_string();
1048 let variable = captures.get(2).map(|m| m.as_str().to_string());
1049
1050 let condition = Condition::with_multifield_last(field, variable);
1051 return Ok(ConditionGroup::single(condition));
1052 }
1053
1054 if let Some(captures) = MULTIFIELD_EMPTY_REGEX.captures(clause_to_parse) {
1057 let field = captures.get(1).unwrap().as_str().to_string();
1058
1059 let condition = Condition::with_multifield_empty(field);
1060 return Ok(ConditionGroup::single(condition));
1061 }
1062
1063 if let Some(captures) = MULTIFIELD_NOT_EMPTY_REGEX.captures(clause_to_parse) {
1066 let field = captures.get(1).unwrap().as_str().to_string();
1067
1068 let condition = Condition::with_multifield_not_empty(field);
1069 return Ok(ConditionGroup::single(condition));
1070 }
1071
1072 if let Some(captures) = TEST_CONDITION_REGEX.captures(clause_to_parse) {
1077 let function_name = captures.get(1).unwrap().as_str().to_string();
1078 let args_str = captures.get(2).unwrap().as_str();
1079
1080 let args: Vec<String> = if args_str.trim().is_empty() {
1082 Vec::new()
1083 } else {
1084 args_str
1085 .split(',')
1086 .map(|arg| arg.trim().to_string())
1087 .collect()
1088 };
1089
1090 let condition = Condition::with_test(function_name, args);
1091 return Ok(ConditionGroup::single(condition));
1092 }
1093
1094 if let Some(captures) = TYPED_TEST_CONDITION_REGEX.captures(clause_to_parse) {
1096 let _object_name = captures.get(1).unwrap().as_str();
1097 let _object_type = captures.get(2).unwrap().as_str();
1098 let conditions_str = captures.get(3).unwrap().as_str();
1099
1100 return self.parse_conditions_within_object(conditions_str);
1102 }
1103
1104 if let Some(captures) = FUNCTION_CALL_REGEX.captures(clause_to_parse) {
1106 let function_name = captures.get(1).unwrap().as_str().to_string();
1107 let args_str = captures.get(2).unwrap().as_str();
1108 let operator_str = captures.get(3).unwrap().as_str();
1109 let value_str = captures.get(4).unwrap().as_str().trim();
1110
1111 let args: Vec<String> = if args_str.trim().is_empty() {
1113 Vec::new()
1114 } else {
1115 args_str
1116 .split(',')
1117 .map(|arg| arg.trim().to_string())
1118 .collect()
1119 };
1120
1121 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1122 RuleEngineError::InvalidOperator {
1123 operator: operator_str.to_string(),
1124 }
1125 })?;
1126
1127 let value = self.parse_value(value_str)?;
1128
1129 let condition = Condition::with_function(function_name, args, operator, value);
1130 return Ok(ConditionGroup::single(condition));
1131 }
1132
1133 let captures = CONDITION_REGEX.captures(clause_to_parse).ok_or_else(|| {
1137 RuleEngineError::ParseError {
1138 message: format!("Invalid condition format: {}", clause_to_parse),
1139 }
1140 })?;
1141
1142 let left_side = captures.get(1).unwrap().as_str().trim().to_string();
1143 let operator_str = captures.get(2).unwrap().as_str();
1144 let value_str = captures.get(3).unwrap().as_str().trim();
1145
1146 let operator =
1147 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1148 operator: operator_str.to_string(),
1149 })?;
1150
1151 let value = self.parse_value(value_str)?;
1152
1153 if left_side.contains('+')
1155 || left_side.contains('-')
1156 || left_side.contains('*')
1157 || left_side.contains('/')
1158 || left_side.contains('%')
1159 {
1160 let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
1163 let condition = Condition::with_test(test_expr, vec![]);
1164 Ok(ConditionGroup::single(condition))
1165 } else {
1166 let condition = Condition::new(left_side, operator, value);
1168 Ok(ConditionGroup::single(condition))
1169 }
1170 }
1171
1172 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
1173 let parts: Vec<&str> = conditions_str.split("&&").collect();
1175
1176 let mut conditions = Vec::new();
1177 for part in parts {
1178 let trimmed = part.trim();
1179 let condition = self.parse_simple_condition(trimmed)?;
1180 conditions.push(condition);
1181 }
1182
1183 if conditions.is_empty() {
1185 return Err(RuleEngineError::ParseError {
1186 message: "No conditions found".to_string(),
1187 });
1188 }
1189
1190 let mut iter = conditions.into_iter();
1191 let mut result = iter.next().unwrap();
1192 for condition in iter {
1193 result = ConditionGroup::and(result, condition);
1194 }
1195
1196 Ok(result)
1197 }
1198
1199 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
1200 let captures =
1202 SIMPLE_CONDITION_REGEX
1203 .captures(clause)
1204 .ok_or_else(|| RuleEngineError::ParseError {
1205 message: format!("Invalid simple condition format: {}", clause),
1206 })?;
1207
1208 let field = captures.get(1).unwrap().as_str().to_string();
1209 let operator_str = captures.get(2).unwrap().as_str();
1210 let value_str = captures.get(3).unwrap().as_str().trim();
1211
1212 let operator =
1213 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1214 operator: operator_str.to_string(),
1215 })?;
1216
1217 let value = self.parse_value(value_str)?;
1218
1219 let condition = Condition::new(field, operator, value);
1220 Ok(ConditionGroup::single(condition))
1221 }
1222
1223 fn parse_value(&self, value_str: &str) -> Result<Value> {
1224 let trimmed = value_str.trim();
1225
1226 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
1228 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
1229 {
1230 let unquoted = &trimmed[1..trimmed.len() - 1];
1231 return Ok(Value::String(unquoted.to_string()));
1232 }
1233
1234 if trimmed.eq_ignore_ascii_case("true") {
1236 return Ok(Value::Boolean(true));
1237 }
1238 if trimmed.eq_ignore_ascii_case("false") {
1239 return Ok(Value::Boolean(false));
1240 }
1241
1242 if trimmed.eq_ignore_ascii_case("null") {
1244 return Ok(Value::Null);
1245 }
1246
1247 if let Ok(int_val) = trimmed.parse::<i64>() {
1249 return Ok(Value::Integer(int_val));
1250 }
1251
1252 if let Ok(float_val) = trimmed.parse::<f64>() {
1253 return Ok(Value::Number(float_val));
1254 }
1255
1256 if self.is_expression(trimmed) {
1259 return Ok(Value::Expression(trimmed.to_string()));
1260 }
1261
1262 if trimmed.contains('.') {
1264 return Ok(Value::String(trimmed.to_string()));
1265 }
1266
1267 if self.is_identifier(trimmed) {
1271 return Ok(Value::Expression(trimmed.to_string()));
1272 }
1273
1274 Ok(Value::String(trimmed.to_string()))
1276 }
1277
1278 fn is_identifier(&self, s: &str) -> bool {
1281 if s.is_empty() {
1282 return false;
1283 }
1284
1285 let first_char = s.chars().next().unwrap();
1287 if !first_char.is_alphabetic() && first_char != '_' {
1288 return false;
1289 }
1290
1291 s.chars().all(|c| c.is_alphanumeric() || c == '_')
1293 }
1294
1295 fn is_expression(&self, s: &str) -> bool {
1297 let has_operator = s.contains('+')
1299 || s.contains('-')
1300 || s.contains('*')
1301 || s.contains('/')
1302 || s.contains('%');
1303
1304 let has_field_ref = s.contains('.');
1306
1307 let has_spaces = s.contains(' ');
1309
1310 has_operator && (has_field_ref || has_spaces)
1312 }
1313
1314 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1315 let statements: Vec<&str> = then_clause
1316 .split(';')
1317 .map(|s| s.trim())
1318 .filter(|s| !s.is_empty())
1319 .collect();
1320
1321 let mut actions = Vec::new();
1322
1323 for statement in statements {
1324 let action = self.parse_action_statement(statement)?;
1325 actions.push(action);
1326 }
1327
1328 Ok(actions)
1329 }
1330
1331 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1332 let trimmed = statement.trim();
1333
1334 if let Some(captures) = METHOD_CALL_REGEX.captures(trimmed) {
1336 let object = captures.get(1).unwrap().as_str().to_string();
1337 let method = captures.get(2).unwrap().as_str().to_string();
1338 let args_str = captures.get(3).unwrap().as_str();
1339
1340 let args = if args_str.trim().is_empty() {
1341 Vec::new()
1342 } else {
1343 self.parse_method_args(args_str)?
1344 };
1345
1346 return Ok(ActionType::MethodCall {
1347 object,
1348 method,
1349 args,
1350 });
1351 }
1352
1353 if let Some(plus_eq_pos) = trimmed.find("+=") {
1355 let field = trimmed[..plus_eq_pos].trim().to_string();
1357 let value_str = trimmed[plus_eq_pos + 2..].trim();
1358 let value = self.parse_value(value_str)?;
1359
1360 return Ok(ActionType::Append { field, value });
1361 }
1362
1363 if let Some(eq_pos) = trimmed.find('=') {
1365 let field = trimmed[..eq_pos].trim().to_string();
1366 let value_str = trimmed[eq_pos + 1..].trim();
1367 let value = self.parse_value(value_str)?;
1368
1369 return Ok(ActionType::Set { field, value });
1370 }
1371
1372 if let Some(captures) = FUNCTION_BINDING_REGEX.captures(trimmed) {
1374 let function_name = captures.get(1).unwrap().as_str();
1375 let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
1376
1377 match function_name.to_lowercase().as_str() {
1378 "retract" => {
1379 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1381 stripped.to_string()
1382 } else {
1383 args_str.to_string()
1384 };
1385 Ok(ActionType::Retract {
1386 object: object_name,
1387 })
1388 }
1389 "log" => {
1390 let message = if args_str.is_empty() {
1391 "Log message".to_string()
1392 } else {
1393 let value = self.parse_value(args_str.trim())?;
1394 value.to_string()
1395 };
1396 Ok(ActionType::Log { message })
1397 }
1398 "activateagendagroup" | "activate_agenda_group" => {
1399 let agenda_group = if args_str.is_empty() {
1400 return Err(RuleEngineError::ParseError {
1401 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1402 });
1403 } else {
1404 let value = self.parse_value(args_str.trim())?;
1405 match value {
1406 Value::String(s) => s,
1407 _ => value.to_string(),
1408 }
1409 };
1410 Ok(ActionType::ActivateAgendaGroup {
1411 group: agenda_group,
1412 })
1413 }
1414 "schedulerule" | "schedule_rule" => {
1415 let parts: Vec<&str> = args_str.split(',').collect();
1417 if parts.len() != 2 {
1418 return Err(RuleEngineError::ParseError {
1419 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1420 });
1421 }
1422
1423 let delay_ms = self.parse_value(parts[0].trim())?;
1424 let rule_name = self.parse_value(parts[1].trim())?;
1425
1426 let delay_ms = match delay_ms {
1427 Value::Integer(i) => i as u64,
1428 Value::Number(f) => f as u64,
1429 _ => {
1430 return Err(RuleEngineError::ParseError {
1431 message: "ScheduleRule delay_ms must be a number".to_string(),
1432 })
1433 }
1434 };
1435
1436 let rule_name = match rule_name {
1437 Value::String(s) => s,
1438 _ => rule_name.to_string(),
1439 };
1440
1441 Ok(ActionType::ScheduleRule {
1442 delay_ms,
1443 rule_name,
1444 })
1445 }
1446 "completeworkflow" | "complete_workflow" => {
1447 let workflow_id = if args_str.is_empty() {
1448 return Err(RuleEngineError::ParseError {
1449 message: "CompleteWorkflow requires workflow_id".to_string(),
1450 });
1451 } else {
1452 let value = self.parse_value(args_str.trim())?;
1453 match value {
1454 Value::String(s) => s,
1455 _ => value.to_string(),
1456 }
1457 };
1458 Ok(ActionType::CompleteWorkflow {
1459 workflow_name: workflow_id,
1460 })
1461 }
1462 "setworkflowdata" | "set_workflow_data" => {
1463 let data_str = args_str.trim();
1465
1466 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1468 let key = data_str[..eq_pos].trim().trim_matches('"');
1469 let value_str = data_str[eq_pos + 1..].trim();
1470 let value = self.parse_value(value_str)?;
1471 (key.to_string(), value)
1472 } else {
1473 return Err(RuleEngineError::ParseError {
1474 message: "SetWorkflowData data must be in key=value format".to_string(),
1475 });
1476 };
1477
1478 Ok(ActionType::SetWorkflowData { key, value })
1479 }
1480 _ => {
1481 let params = if args_str.is_empty() {
1483 HashMap::new()
1484 } else {
1485 self.parse_function_args_as_params(args_str)?
1486 };
1487
1488 Ok(ActionType::Custom {
1489 action_type: function_name.to_string(),
1490 params,
1491 })
1492 }
1493 }
1494 } else {
1495 Ok(ActionType::Custom {
1497 action_type: "statement".to_string(),
1498 params: {
1499 let mut params = HashMap::new();
1500 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1501 params
1502 },
1503 })
1504 }
1505 }
1506
1507 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1508 if args_str.trim().is_empty() {
1509 return Ok(Vec::new());
1510 }
1511
1512 let mut args = Vec::new();
1514 let parts: Vec<&str> = args_str.split(',').collect();
1515
1516 for part in parts {
1517 let trimmed = part.trim();
1518
1519 if trimmed.contains('+')
1521 || trimmed.contains('-')
1522 || trimmed.contains('*')
1523 || trimmed.contains('/')
1524 {
1525 args.push(Value::String(trimmed.to_string()));
1527 } else {
1528 args.push(self.parse_value(trimmed)?);
1529 }
1530 }
1531
1532 Ok(args)
1533 }
1534
1535 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1537 let mut params = HashMap::new();
1538
1539 if args_str.trim().is_empty() {
1540 return Ok(params);
1541 }
1542
1543 let parts: Vec<&str> = args_str.split(',').collect();
1545 for (i, part) in parts.iter().enumerate() {
1546 let trimmed = part.trim();
1547 let value = self.parse_value(trimmed)?;
1548
1549 params.insert(i.to_string(), value);
1551 }
1552
1553 Ok(params)
1554 }
1555
1556 #[cfg(feature = "streaming")]
1559 fn parse_stream_pattern_condition(&self, clause: &str) -> Result<ConditionGroup> {
1560 use crate::engine::rule::{StreamWindow, StreamWindowType};
1561 use crate::parser::grl::stream_syntax::parse_stream_pattern;
1562
1563 let parse_result =
1565 parse_stream_pattern(clause).map_err(|e| RuleEngineError::ParseError {
1566 message: format!("Failed to parse stream pattern: {:?}", e),
1567 })?;
1568
1569 let (_, pattern) = parse_result;
1570
1571 let window = pattern.source.window.map(|w| StreamWindow {
1573 duration: w.duration,
1574 window_type: match w.window_type {
1575 crate::parser::grl::stream_syntax::WindowType::Sliding => StreamWindowType::Sliding,
1576 crate::parser::grl::stream_syntax::WindowType::Tumbling => {
1577 StreamWindowType::Tumbling
1578 }
1579 crate::parser::grl::stream_syntax::WindowType::Session { timeout } => {
1580 StreamWindowType::Session { timeout }
1581 }
1582 },
1583 });
1584
1585 Ok(ConditionGroup::stream_pattern(
1586 pattern.var_name,
1587 pattern.event_type,
1588 pattern.source.stream_name,
1589 window,
1590 ))
1591 }
1592}
1593
1594#[cfg(test)]
1595mod tests {
1596 use super::GRLParser;
1597
1598 #[test]
1599 fn test_parse_simple_rule() {
1600 let grl = r#"
1601 rule "CheckAge" salience 10 {
1602 when
1603 User.Age >= 18
1604 then
1605 log("User is adult");
1606 }
1607 "#;
1608
1609 let rules = GRLParser::parse_rules(grl).unwrap();
1610 assert_eq!(rules.len(), 1);
1611 let rule = &rules[0];
1612 assert_eq!(rule.name, "CheckAge");
1613 assert_eq!(rule.salience, 10);
1614 assert_eq!(rule.actions.len(), 1);
1615 }
1616
1617 #[test]
1618 fn test_parse_complex_condition() {
1619 let grl = r#"
1620 rule "ComplexRule" {
1621 when
1622 User.Age >= 18 && User.Country == "US"
1623 then
1624 User.Qualified = true;
1625 }
1626 "#;
1627
1628 let rules = GRLParser::parse_rules(grl).unwrap();
1629 assert_eq!(rules.len(), 1);
1630 let rule = &rules[0];
1631 assert_eq!(rule.name, "ComplexRule");
1632 }
1633
1634 #[test]
1635 fn test_parse_new_syntax_with_parentheses() {
1636 let grl = r#"
1637 rule "Default Rule" salience 10 {
1638 when
1639 (user.age >= 18)
1640 then
1641 set(user.status, "approved");
1642 }
1643 "#;
1644
1645 let rules = GRLParser::parse_rules(grl).unwrap();
1646 assert_eq!(rules.len(), 1);
1647 let rule = &rules[0];
1648 assert_eq!(rule.name, "Default Rule");
1649 assert_eq!(rule.salience, 10);
1650 assert_eq!(rule.actions.len(), 1);
1651
1652 match &rule.actions[0] {
1654 crate::types::ActionType::Custom {
1655 action_type,
1656 params,
1657 } => {
1658 assert_eq!(action_type, "set");
1659 assert_eq!(
1660 params.get("0"),
1661 Some(&crate::types::Value::String("user.status".to_string()))
1662 );
1663 assert_eq!(
1664 params.get("1"),
1665 Some(&crate::types::Value::String("approved".to_string()))
1666 );
1667 }
1668 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1669 }
1670 }
1671
1672 #[test]
1673 fn test_parse_complex_nested_conditions() {
1674 let grl = r#"
1675 rule "Complex Business Rule" salience 10 {
1676 when
1677 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1678 then
1679 apply_discount(20000);
1680 }
1681 "#;
1682
1683 let rules = GRLParser::parse_rules(grl).unwrap();
1684 assert_eq!(rules.len(), 1);
1685 let rule = &rules[0];
1686 assert_eq!(rule.name, "Complex Business Rule");
1687 assert_eq!(rule.salience, 10);
1688 assert_eq!(rule.actions.len(), 1);
1689
1690 match &rule.actions[0] {
1692 crate::types::ActionType::Custom {
1693 action_type,
1694 params,
1695 } => {
1696 assert_eq!(action_type, "apply_discount");
1697 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1698 }
1699 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1700 }
1701 }
1702
1703 #[test]
1704 fn test_parse_no_loop_attribute() {
1705 let grl = r#"
1706 rule "NoLoopRule" no-loop salience 15 {
1707 when
1708 User.Score < 100
1709 then
1710 set(User.Score, User.Score + 10);
1711 }
1712 "#;
1713
1714 let rules = GRLParser::parse_rules(grl).unwrap();
1715 assert_eq!(rules.len(), 1);
1716 let rule = &rules[0];
1717 assert_eq!(rule.name, "NoLoopRule");
1718 assert_eq!(rule.salience, 15);
1719 assert!(rule.no_loop, "Rule should have no-loop=true");
1720 }
1721
1722 #[test]
1723 fn test_parse_no_loop_different_positions() {
1724 let grl1 = r#"
1726 rule "Rule1" no-loop salience 10 {
1727 when User.Age >= 18
1728 then log("adult");
1729 }
1730 "#;
1731
1732 let grl2 = r#"
1734 rule "Rule2" salience 10 no-loop {
1735 when User.Age >= 18
1736 then log("adult");
1737 }
1738 "#;
1739
1740 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1741 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1742
1743 assert_eq!(rules1.len(), 1);
1744 assert_eq!(rules2.len(), 1);
1745
1746 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1747 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1748
1749 assert_eq!(rules1[0].salience, 10);
1750 assert_eq!(rules2[0].salience, 10);
1751 }
1752
1753 #[test]
1754 fn test_parse_without_no_loop() {
1755 let grl = r#"
1756 rule "RegularRule" salience 5 {
1757 when
1758 User.Active == true
1759 then
1760 log("active user");
1761 }
1762 "#;
1763
1764 let rules = GRLParser::parse_rules(grl).unwrap();
1765 assert_eq!(rules.len(), 1);
1766 let rule = &rules[0];
1767 assert_eq!(rule.name, "RegularRule");
1768 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1769 }
1770
1771 #[test]
1772 fn test_parse_exists_pattern() {
1773 let grl = r#"
1774 rule "ExistsRule" salience 20 {
1775 when
1776 exists(Customer.tier == "VIP")
1777 then
1778 System.premiumActive = true;
1779 }
1780 "#;
1781
1782 let rules = GRLParser::parse_rules(grl).unwrap();
1783 assert_eq!(rules.len(), 1);
1784 let rule = &rules[0];
1785 assert_eq!(rule.name, "ExistsRule");
1786 assert_eq!(rule.salience, 20);
1787
1788 match &rule.conditions {
1790 crate::engine::rule::ConditionGroup::Exists(_) => {
1791 }
1793 _ => panic!(
1794 "Expected EXISTS condition group, got: {:?}",
1795 rule.conditions
1796 ),
1797 }
1798 }
1799
1800 #[test]
1801 fn test_parse_forall_pattern() {
1802 let grl = r#"
1803 rule "ForallRule" salience 15 {
1804 when
1805 forall(Order.status == "processed")
1806 then
1807 Shipping.enabled = true;
1808 }
1809 "#;
1810
1811 let rules = GRLParser::parse_rules(grl).unwrap();
1812 assert_eq!(rules.len(), 1);
1813 let rule = &rules[0];
1814 assert_eq!(rule.name, "ForallRule");
1815
1816 match &rule.conditions {
1818 crate::engine::rule::ConditionGroup::Forall(_) => {
1819 }
1821 _ => panic!(
1822 "Expected FORALL condition group, got: {:?}",
1823 rule.conditions
1824 ),
1825 }
1826 }
1827
1828 #[test]
1829 fn test_parse_combined_patterns() {
1830 let grl = r#"
1831 rule "CombinedRule" salience 25 {
1832 when
1833 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1834 then
1835 System.vipMode = true;
1836 }
1837 "#;
1838
1839 let rules = GRLParser::parse_rules(grl).unwrap();
1840 assert_eq!(rules.len(), 1);
1841 let rule = &rules[0];
1842 assert_eq!(rule.name, "CombinedRule");
1843
1844 match &rule.conditions {
1846 crate::engine::rule::ConditionGroup::Compound {
1847 left,
1848 operator,
1849 right,
1850 } => {
1851 assert_eq!(*operator, crate::types::LogicalOperator::And);
1852
1853 match left.as_ref() {
1855 crate::engine::rule::ConditionGroup::Exists(_) => {
1856 }
1858 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1859 }
1860
1861 match right.as_ref() {
1863 crate::engine::rule::ConditionGroup::Not(inner) => {
1864 match inner.as_ref() {
1865 crate::engine::rule::ConditionGroup::Exists(_) => {
1866 }
1868 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1869 }
1870 }
1871 _ => panic!("Expected NOT in right side, got: {:?}", right),
1872 }
1873 }
1874 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1875 }
1876 }
1877}