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 if let Some(captures) = MULTIFIELD_COLLECT_REGEX.captures(clause_to_parse) {
995 let field = captures.get(1).unwrap().as_str().to_string();
996 let variable = captures.get(2).unwrap().as_str().to_string();
997
998 let condition = Condition::with_multifield_collect(field, variable);
1001 return Ok(ConditionGroup::single(condition));
1002 }
1003
1004 if let Some(captures) = MULTIFIELD_COUNT_REGEX.captures(clause_to_parse) {
1011 let field = captures.get(1).unwrap().as_str().to_string();
1012 let operator_str = captures.get(2).unwrap().as_str();
1013 let value_str = captures.get(3).unwrap().as_str().trim();
1014
1015 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1016 RuleEngineError::InvalidOperator {
1017 operator: operator_str.to_string(),
1018 }
1019 })?;
1020
1021 let value = self.parse_value(value_str)?;
1022
1023 let condition = Condition::with_multifield_count(field, operator, value);
1024 return Ok(ConditionGroup::single(condition));
1025 }
1026
1027 if let Some(captures) = MULTIFIELD_FIRST_REGEX.captures(clause_to_parse) {
1030 let field = captures.get(1).unwrap().as_str().to_string();
1031 let variable = captures.get(2).map(|m| m.as_str().to_string());
1032
1033 let condition = Condition::with_multifield_first(field, variable);
1034 return Ok(ConditionGroup::single(condition));
1035 }
1036
1037 if let Some(captures) = MULTIFIELD_LAST_REGEX.captures(clause_to_parse) {
1040 let field = captures.get(1).unwrap().as_str().to_string();
1041 let variable = captures.get(2).map(|m| m.as_str().to_string());
1042
1043 let condition = Condition::with_multifield_last(field, variable);
1044 return Ok(ConditionGroup::single(condition));
1045 }
1046
1047 if let Some(captures) = MULTIFIELD_EMPTY_REGEX.captures(clause_to_parse) {
1050 let field = captures.get(1).unwrap().as_str().to_string();
1051
1052 let condition = Condition::with_multifield_empty(field);
1053 return Ok(ConditionGroup::single(condition));
1054 }
1055
1056 if let Some(captures) = MULTIFIELD_NOT_EMPTY_REGEX.captures(clause_to_parse) {
1059 let field = captures.get(1).unwrap().as_str().to_string();
1060
1061 let condition = Condition::with_multifield_not_empty(field);
1062 return Ok(ConditionGroup::single(condition));
1063 }
1064
1065 if let Some(captures) = TEST_CONDITION_REGEX.captures(clause_to_parse) {
1070 let function_name = captures.get(1).unwrap().as_str().to_string();
1071 let args_str = captures.get(2).unwrap().as_str();
1072
1073 let args: Vec<String> = if args_str.trim().is_empty() {
1075 Vec::new()
1076 } else {
1077 args_str
1078 .split(',')
1079 .map(|arg| arg.trim().to_string())
1080 .collect()
1081 };
1082
1083 let condition = Condition::with_test(function_name, args);
1084 return Ok(ConditionGroup::single(condition));
1085 }
1086
1087 if let Some(captures) = TYPED_TEST_CONDITION_REGEX.captures(clause_to_parse) {
1089 let _object_name = captures.get(1).unwrap().as_str();
1090 let _object_type = captures.get(2).unwrap().as_str();
1091 let conditions_str = captures.get(3).unwrap().as_str();
1092
1093 return self.parse_conditions_within_object(conditions_str);
1095 }
1096
1097 if let Some(captures) = FUNCTION_CALL_REGEX.captures(clause_to_parse) {
1099 let function_name = captures.get(1).unwrap().as_str().to_string();
1100 let args_str = captures.get(2).unwrap().as_str();
1101 let operator_str = captures.get(3).unwrap().as_str();
1102 let value_str = captures.get(4).unwrap().as_str().trim();
1103
1104 let args: Vec<String> = if args_str.trim().is_empty() {
1106 Vec::new()
1107 } else {
1108 args_str
1109 .split(',')
1110 .map(|arg| arg.trim().to_string())
1111 .collect()
1112 };
1113
1114 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1115 RuleEngineError::InvalidOperator {
1116 operator: operator_str.to_string(),
1117 }
1118 })?;
1119
1120 let value = self.parse_value(value_str)?;
1121
1122 let condition = Condition::with_function(function_name, args, operator, value);
1123 return Ok(ConditionGroup::single(condition));
1124 }
1125
1126 let captures = CONDITION_REGEX.captures(clause_to_parse).ok_or_else(|| {
1130 RuleEngineError::ParseError {
1131 message: format!("Invalid condition format: {}", clause_to_parse),
1132 }
1133 })?;
1134
1135 let left_side = captures.get(1).unwrap().as_str().trim().to_string();
1136 let operator_str = captures.get(2).unwrap().as_str();
1137 let value_str = captures.get(3).unwrap().as_str().trim();
1138
1139 let operator =
1140 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1141 operator: operator_str.to_string(),
1142 })?;
1143
1144 let value = self.parse_value(value_str)?;
1145
1146 if left_side.contains('+')
1148 || left_side.contains('-')
1149 || left_side.contains('*')
1150 || left_side.contains('/')
1151 || left_side.contains('%')
1152 {
1153 let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
1156 let condition = Condition::with_test(test_expr, vec![]);
1157 Ok(ConditionGroup::single(condition))
1158 } else {
1159 let condition = Condition::new(left_side, operator, value);
1161 Ok(ConditionGroup::single(condition))
1162 }
1163 }
1164
1165 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
1166 let parts: Vec<&str> = conditions_str.split("&&").collect();
1168
1169 let mut conditions = Vec::new();
1170 for part in parts {
1171 let trimmed = part.trim();
1172 let condition = self.parse_simple_condition(trimmed)?;
1173 conditions.push(condition);
1174 }
1175
1176 if conditions.is_empty() {
1178 return Err(RuleEngineError::ParseError {
1179 message: "No conditions found".to_string(),
1180 });
1181 }
1182
1183 let mut iter = conditions.into_iter();
1184 let mut result = iter.next().unwrap();
1185 for condition in iter {
1186 result = ConditionGroup::and(result, condition);
1187 }
1188
1189 Ok(result)
1190 }
1191
1192 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
1193 let captures =
1195 SIMPLE_CONDITION_REGEX
1196 .captures(clause)
1197 .ok_or_else(|| RuleEngineError::ParseError {
1198 message: format!("Invalid simple condition format: {}", clause),
1199 })?;
1200
1201 let field = captures.get(1).unwrap().as_str().to_string();
1202 let operator_str = captures.get(2).unwrap().as_str();
1203 let value_str = captures.get(3).unwrap().as_str().trim();
1204
1205 let operator =
1206 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1207 operator: operator_str.to_string(),
1208 })?;
1209
1210 let value = self.parse_value(value_str)?;
1211
1212 let condition = Condition::new(field, operator, value);
1213 Ok(ConditionGroup::single(condition))
1214 }
1215
1216 fn parse_value(&self, value_str: &str) -> Result<Value> {
1217 let trimmed = value_str.trim();
1218
1219 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
1221 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
1222 {
1223 let unquoted = &trimmed[1..trimmed.len() - 1];
1224 return Ok(Value::String(unquoted.to_string()));
1225 }
1226
1227 if trimmed.eq_ignore_ascii_case("true") {
1229 return Ok(Value::Boolean(true));
1230 }
1231 if trimmed.eq_ignore_ascii_case("false") {
1232 return Ok(Value::Boolean(false));
1233 }
1234
1235 if trimmed.eq_ignore_ascii_case("null") {
1237 return Ok(Value::Null);
1238 }
1239
1240 if let Ok(int_val) = trimmed.parse::<i64>() {
1242 return Ok(Value::Integer(int_val));
1243 }
1244
1245 if let Ok(float_val) = trimmed.parse::<f64>() {
1246 return Ok(Value::Number(float_val));
1247 }
1248
1249 if self.is_expression(trimmed) {
1252 return Ok(Value::Expression(trimmed.to_string()));
1253 }
1254
1255 if trimmed.contains('.') {
1257 return Ok(Value::String(trimmed.to_string()));
1258 }
1259
1260 if self.is_identifier(trimmed) {
1264 return Ok(Value::Expression(trimmed.to_string()));
1265 }
1266
1267 Ok(Value::String(trimmed.to_string()))
1269 }
1270
1271 fn is_identifier(&self, s: &str) -> bool {
1274 if s.is_empty() {
1275 return false;
1276 }
1277
1278 let first_char = s.chars().next().unwrap();
1280 if !first_char.is_alphabetic() && first_char != '_' {
1281 return false;
1282 }
1283
1284 s.chars().all(|c| c.is_alphanumeric() || c == '_')
1286 }
1287
1288 fn is_expression(&self, s: &str) -> bool {
1290 let has_operator = s.contains('+')
1292 || s.contains('-')
1293 || s.contains('*')
1294 || s.contains('/')
1295 || s.contains('%');
1296
1297 let has_field_ref = s.contains('.');
1299
1300 let has_spaces = s.contains(' ');
1302
1303 has_operator && (has_field_ref || has_spaces)
1305 }
1306
1307 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1308 let statements: Vec<&str> = then_clause
1309 .split(';')
1310 .map(|s| s.trim())
1311 .filter(|s| !s.is_empty())
1312 .collect();
1313
1314 let mut actions = Vec::new();
1315
1316 for statement in statements {
1317 let action = self.parse_action_statement(statement)?;
1318 actions.push(action);
1319 }
1320
1321 Ok(actions)
1322 }
1323
1324 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1325 let trimmed = statement.trim();
1326
1327 if let Some(captures) = METHOD_CALL_REGEX.captures(trimmed) {
1329 let object = captures.get(1).unwrap().as_str().to_string();
1330 let method = captures.get(2).unwrap().as_str().to_string();
1331 let args_str = captures.get(3).unwrap().as_str();
1332
1333 let args = if args_str.trim().is_empty() {
1334 Vec::new()
1335 } else {
1336 self.parse_method_args(args_str)?
1337 };
1338
1339 return Ok(ActionType::MethodCall {
1340 object,
1341 method,
1342 args,
1343 });
1344 }
1345
1346 if let Some(eq_pos) = trimmed.find('=') {
1348 let field = trimmed[..eq_pos].trim().to_string();
1349 let value_str = trimmed[eq_pos + 1..].trim();
1350 let value = self.parse_value(value_str)?;
1351
1352 return Ok(ActionType::Set { field, value });
1353 }
1354
1355 if let Some(captures) = FUNCTION_BINDING_REGEX.captures(trimmed) {
1357 let function_name = captures.get(1).unwrap().as_str();
1358 let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
1359
1360 match function_name.to_lowercase().as_str() {
1361 "retract" => {
1362 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1364 stripped.to_string()
1365 } else {
1366 args_str.to_string()
1367 };
1368 Ok(ActionType::Retract {
1369 object: object_name,
1370 })
1371 }
1372 "log" => {
1373 let message = if args_str.is_empty() {
1374 "Log message".to_string()
1375 } else {
1376 let value = self.parse_value(args_str.trim())?;
1377 value.to_string()
1378 };
1379 Ok(ActionType::Log { message })
1380 }
1381 "activateagendagroup" | "activate_agenda_group" => {
1382 let agenda_group = if args_str.is_empty() {
1383 return Err(RuleEngineError::ParseError {
1384 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1385 });
1386 } else {
1387 let value = self.parse_value(args_str.trim())?;
1388 match value {
1389 Value::String(s) => s,
1390 _ => value.to_string(),
1391 }
1392 };
1393 Ok(ActionType::ActivateAgendaGroup {
1394 group: agenda_group,
1395 })
1396 }
1397 "schedulerule" | "schedule_rule" => {
1398 let parts: Vec<&str> = args_str.split(',').collect();
1400 if parts.len() != 2 {
1401 return Err(RuleEngineError::ParseError {
1402 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1403 });
1404 }
1405
1406 let delay_ms = self.parse_value(parts[0].trim())?;
1407 let rule_name = self.parse_value(parts[1].trim())?;
1408
1409 let delay_ms = match delay_ms {
1410 Value::Integer(i) => i as u64,
1411 Value::Number(f) => f as u64,
1412 _ => {
1413 return Err(RuleEngineError::ParseError {
1414 message: "ScheduleRule delay_ms must be a number".to_string(),
1415 })
1416 }
1417 };
1418
1419 let rule_name = match rule_name {
1420 Value::String(s) => s,
1421 _ => rule_name.to_string(),
1422 };
1423
1424 Ok(ActionType::ScheduleRule {
1425 delay_ms,
1426 rule_name,
1427 })
1428 }
1429 "completeworkflow" | "complete_workflow" => {
1430 let workflow_id = if args_str.is_empty() {
1431 return Err(RuleEngineError::ParseError {
1432 message: "CompleteWorkflow requires workflow_id".to_string(),
1433 });
1434 } else {
1435 let value = self.parse_value(args_str.trim())?;
1436 match value {
1437 Value::String(s) => s,
1438 _ => value.to_string(),
1439 }
1440 };
1441 Ok(ActionType::CompleteWorkflow {
1442 workflow_name: workflow_id,
1443 })
1444 }
1445 "setworkflowdata" | "set_workflow_data" => {
1446 let data_str = args_str.trim();
1448
1449 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1451 let key = data_str[..eq_pos].trim().trim_matches('"');
1452 let value_str = data_str[eq_pos + 1..].trim();
1453 let value = self.parse_value(value_str)?;
1454 (key.to_string(), value)
1455 } else {
1456 return Err(RuleEngineError::ParseError {
1457 message: "SetWorkflowData data must be in key=value format".to_string(),
1458 });
1459 };
1460
1461 Ok(ActionType::SetWorkflowData { key, value })
1462 }
1463 _ => {
1464 let params = if args_str.is_empty() {
1466 HashMap::new()
1467 } else {
1468 self.parse_function_args_as_params(args_str)?
1469 };
1470
1471 Ok(ActionType::Custom {
1472 action_type: function_name.to_string(),
1473 params,
1474 })
1475 }
1476 }
1477 } else {
1478 Ok(ActionType::Custom {
1480 action_type: "statement".to_string(),
1481 params: {
1482 let mut params = HashMap::new();
1483 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1484 params
1485 },
1486 })
1487 }
1488 }
1489
1490 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1491 if args_str.trim().is_empty() {
1492 return Ok(Vec::new());
1493 }
1494
1495 let mut args = Vec::new();
1497 let parts: Vec<&str> = args_str.split(',').collect();
1498
1499 for part in parts {
1500 let trimmed = part.trim();
1501
1502 if trimmed.contains('+')
1504 || trimmed.contains('-')
1505 || trimmed.contains('*')
1506 || trimmed.contains('/')
1507 {
1508 args.push(Value::String(trimmed.to_string()));
1510 } else {
1511 args.push(self.parse_value(trimmed)?);
1512 }
1513 }
1514
1515 Ok(args)
1516 }
1517
1518 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1520 let mut params = HashMap::new();
1521
1522 if args_str.trim().is_empty() {
1523 return Ok(params);
1524 }
1525
1526 let parts: Vec<&str> = args_str.split(',').collect();
1528 for (i, part) in parts.iter().enumerate() {
1529 let trimmed = part.trim();
1530 let value = self.parse_value(trimmed)?;
1531
1532 params.insert(i.to_string(), value);
1534 }
1535
1536 Ok(params)
1537 }
1538}
1539
1540#[cfg(test)]
1541mod tests {
1542 use super::GRLParser;
1543
1544 #[test]
1545 fn test_parse_simple_rule() {
1546 let grl = r#"
1547 rule "CheckAge" salience 10 {
1548 when
1549 User.Age >= 18
1550 then
1551 log("User is adult");
1552 }
1553 "#;
1554
1555 let rules = GRLParser::parse_rules(grl).unwrap();
1556 assert_eq!(rules.len(), 1);
1557 let rule = &rules[0];
1558 assert_eq!(rule.name, "CheckAge");
1559 assert_eq!(rule.salience, 10);
1560 assert_eq!(rule.actions.len(), 1);
1561 }
1562
1563 #[test]
1564 fn test_parse_complex_condition() {
1565 let grl = r#"
1566 rule "ComplexRule" {
1567 when
1568 User.Age >= 18 && User.Country == "US"
1569 then
1570 User.Qualified = true;
1571 }
1572 "#;
1573
1574 let rules = GRLParser::parse_rules(grl).unwrap();
1575 assert_eq!(rules.len(), 1);
1576 let rule = &rules[0];
1577 assert_eq!(rule.name, "ComplexRule");
1578 }
1579
1580 #[test]
1581 fn test_parse_new_syntax_with_parentheses() {
1582 let grl = r#"
1583 rule "Default Rule" salience 10 {
1584 when
1585 (user.age >= 18)
1586 then
1587 set(user.status, "approved");
1588 }
1589 "#;
1590
1591 let rules = GRLParser::parse_rules(grl).unwrap();
1592 assert_eq!(rules.len(), 1);
1593 let rule = &rules[0];
1594 assert_eq!(rule.name, "Default Rule");
1595 assert_eq!(rule.salience, 10);
1596 assert_eq!(rule.actions.len(), 1);
1597
1598 match &rule.actions[0] {
1600 crate::types::ActionType::Custom {
1601 action_type,
1602 params,
1603 } => {
1604 assert_eq!(action_type, "set");
1605 assert_eq!(
1606 params.get("0"),
1607 Some(&crate::types::Value::String("user.status".to_string()))
1608 );
1609 assert_eq!(
1610 params.get("1"),
1611 Some(&crate::types::Value::String("approved".to_string()))
1612 );
1613 }
1614 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1615 }
1616 }
1617
1618 #[test]
1619 fn test_parse_complex_nested_conditions() {
1620 let grl = r#"
1621 rule "Complex Business Rule" salience 10 {
1622 when
1623 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1624 then
1625 apply_discount(20000);
1626 }
1627 "#;
1628
1629 let rules = GRLParser::parse_rules(grl).unwrap();
1630 assert_eq!(rules.len(), 1);
1631 let rule = &rules[0];
1632 assert_eq!(rule.name, "Complex Business Rule");
1633 assert_eq!(rule.salience, 10);
1634 assert_eq!(rule.actions.len(), 1);
1635
1636 match &rule.actions[0] {
1638 crate::types::ActionType::Custom {
1639 action_type,
1640 params,
1641 } => {
1642 assert_eq!(action_type, "apply_discount");
1643 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1644 }
1645 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1646 }
1647 }
1648
1649 #[test]
1650 fn test_parse_no_loop_attribute() {
1651 let grl = r#"
1652 rule "NoLoopRule" no-loop salience 15 {
1653 when
1654 User.Score < 100
1655 then
1656 set(User.Score, User.Score + 10);
1657 }
1658 "#;
1659
1660 let rules = GRLParser::parse_rules(grl).unwrap();
1661 assert_eq!(rules.len(), 1);
1662 let rule = &rules[0];
1663 assert_eq!(rule.name, "NoLoopRule");
1664 assert_eq!(rule.salience, 15);
1665 assert!(rule.no_loop, "Rule should have no-loop=true");
1666 }
1667
1668 #[test]
1669 fn test_parse_no_loop_different_positions() {
1670 let grl1 = r#"
1672 rule "Rule1" no-loop salience 10 {
1673 when User.Age >= 18
1674 then log("adult");
1675 }
1676 "#;
1677
1678 let grl2 = r#"
1680 rule "Rule2" salience 10 no-loop {
1681 when User.Age >= 18
1682 then log("adult");
1683 }
1684 "#;
1685
1686 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1687 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1688
1689 assert_eq!(rules1.len(), 1);
1690 assert_eq!(rules2.len(), 1);
1691
1692 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1693 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1694
1695 assert_eq!(rules1[0].salience, 10);
1696 assert_eq!(rules2[0].salience, 10);
1697 }
1698
1699 #[test]
1700 fn test_parse_without_no_loop() {
1701 let grl = r#"
1702 rule "RegularRule" salience 5 {
1703 when
1704 User.Active == true
1705 then
1706 log("active user");
1707 }
1708 "#;
1709
1710 let rules = GRLParser::parse_rules(grl).unwrap();
1711 assert_eq!(rules.len(), 1);
1712 let rule = &rules[0];
1713 assert_eq!(rule.name, "RegularRule");
1714 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1715 }
1716
1717 #[test]
1718 fn test_parse_exists_pattern() {
1719 let grl = r#"
1720 rule "ExistsRule" salience 20 {
1721 when
1722 exists(Customer.tier == "VIP")
1723 then
1724 System.premiumActive = true;
1725 }
1726 "#;
1727
1728 let rules = GRLParser::parse_rules(grl).unwrap();
1729 assert_eq!(rules.len(), 1);
1730 let rule = &rules[0];
1731 assert_eq!(rule.name, "ExistsRule");
1732 assert_eq!(rule.salience, 20);
1733
1734 match &rule.conditions {
1736 crate::engine::rule::ConditionGroup::Exists(_) => {
1737 }
1739 _ => panic!(
1740 "Expected EXISTS condition group, got: {:?}",
1741 rule.conditions
1742 ),
1743 }
1744 }
1745
1746 #[test]
1747 fn test_parse_forall_pattern() {
1748 let grl = r#"
1749 rule "ForallRule" salience 15 {
1750 when
1751 forall(Order.status == "processed")
1752 then
1753 Shipping.enabled = true;
1754 }
1755 "#;
1756
1757 let rules = GRLParser::parse_rules(grl).unwrap();
1758 assert_eq!(rules.len(), 1);
1759 let rule = &rules[0];
1760 assert_eq!(rule.name, "ForallRule");
1761
1762 match &rule.conditions {
1764 crate::engine::rule::ConditionGroup::Forall(_) => {
1765 }
1767 _ => panic!(
1768 "Expected FORALL condition group, got: {:?}",
1769 rule.conditions
1770 ),
1771 }
1772 }
1773
1774 #[test]
1775 fn test_parse_combined_patterns() {
1776 let grl = r#"
1777 rule "CombinedRule" salience 25 {
1778 when
1779 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1780 then
1781 System.vipMode = true;
1782 }
1783 "#;
1784
1785 let rules = GRLParser::parse_rules(grl).unwrap();
1786 assert_eq!(rules.len(), 1);
1787 let rule = &rules[0];
1788 assert_eq!(rule.name, "CombinedRule");
1789
1790 match &rule.conditions {
1792 crate::engine::rule::ConditionGroup::Compound {
1793 left,
1794 operator,
1795 right,
1796 } => {
1797 assert_eq!(*operator, crate::types::LogicalOperator::And);
1798
1799 match left.as_ref() {
1801 crate::engine::rule::ConditionGroup::Exists(_) => {
1802 }
1804 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1805 }
1806
1807 match right.as_ref() {
1809 crate::engine::rule::ConditionGroup::Not(inner) => {
1810 match inner.as_ref() {
1811 crate::engine::rule::ConditionGroup::Exists(_) => {
1812 }
1814 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1815 }
1816 }
1817 _ => panic!("Expected NOT in right side, got: {:?}", right),
1818 }
1819 }
1820 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1821 }
1822 }
1823}