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