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 rexile::Pattern;
7use std::collections::HashMap;
8use std::sync::OnceLock;
9
10#[cfg(feature = "streaming")]
12pub mod stream_syntax;
13
14static RULE_REGEX: OnceLock<Pattern> = OnceLock::new();
16static RULE_SPLIT_REGEX: OnceLock<Pattern> = OnceLock::new();
17static DEFMODULE_REGEX: OnceLock<Pattern> = OnceLock::new();
18static DEFMODULE_SPLIT_REGEX: OnceLock<Pattern> = OnceLock::new();
19static WHEN_THEN_REGEX: OnceLock<Pattern> = OnceLock::new();
20static SALIENCE_REGEX: OnceLock<Pattern> = OnceLock::new();
21static TEST_CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
22static TYPED_TEST_CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
23static FUNCTION_CALL_REGEX: OnceLock<Pattern> = OnceLock::new();
24static CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
25static METHOD_CALL_REGEX: OnceLock<Pattern> = OnceLock::new();
26static FUNCTION_BINDING_REGEX: OnceLock<Pattern> = OnceLock::new();
27static MULTIFIELD_COLLECT_REGEX: OnceLock<Pattern> = OnceLock::new();
28static MULTIFIELD_COUNT_REGEX: OnceLock<Pattern> = OnceLock::new();
29static MULTIFIELD_FIRST_REGEX: OnceLock<Pattern> = OnceLock::new();
30static MULTIFIELD_LAST_REGEX: OnceLock<Pattern> = OnceLock::new();
31static MULTIFIELD_EMPTY_REGEX: OnceLock<Pattern> = OnceLock::new();
32static MULTIFIELD_NOT_EMPTY_REGEX: OnceLock<Pattern> = OnceLock::new();
33static SIMPLE_CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
34
35fn rule_regex() -> &'static Pattern {
37 RULE_REGEX.get_or_init(|| {
38 Pattern::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
39 .expect("Invalid rule regex pattern")
40 })
41}
42
43fn rule_split_regex() -> &'static Pattern {
44 RULE_SPLIT_REGEX.get_or_init(|| {
45 Pattern::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
46 .expect("Invalid rule split regex pattern")
47 })
48}
49
50fn defmodule_regex() -> &'static Pattern {
51 DEFMODULE_REGEX.get_or_init(|| {
52 Pattern::new(r#"defmodule\s+([A-Z_]\w*)\s*\{([^}]*)\}"#)
53 .expect("Invalid defmodule regex pattern")
54 })
55}
56
57fn defmodule_split_regex() -> &'static Pattern {
58 DEFMODULE_SPLIT_REGEX.get_or_init(|| {
59 Pattern::new(r#"(?s)defmodule\s+[A-Z_]\w*\s*\{[^}]*\}"#)
60 .expect("Invalid defmodule split regex pattern")
61 })
62}
63
64fn when_then_regex() -> &'static Pattern {
65 WHEN_THEN_REGEX.get_or_init(|| {
66 Pattern::new(r"when\s+(.+?)\s+then\s+(.+)").expect("Invalid when-then regex pattern")
67 })
68}
69
70fn salience_regex() -> &'static Pattern {
71 SALIENCE_REGEX
72 .get_or_init(|| Pattern::new(r"salience\s+(\d+)").expect("Invalid salience regex pattern"))
73}
74
75fn test_condition_regex() -> &'static Pattern {
76 TEST_CONDITION_REGEX.get_or_init(|| {
77 Pattern::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
78 .expect("Invalid test condition regex")
79 })
80}
81
82fn typed_test_condition_regex() -> &'static Pattern {
83 TYPED_TEST_CONDITION_REGEX.get_or_init(|| {
84 Pattern::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
85 .expect("Invalid typed test condition regex")
86 })
87}
88
89fn function_call_regex() -> &'static Pattern {
90 FUNCTION_CALL_REGEX.get_or_init(|| {
91 Pattern::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|startsWith|endsWith|matches|in)\s*(.+)"#)
92 .expect("Invalid function call regex")
93 })
94}
95
96fn condition_regex() -> &'static Pattern {
97 CONDITION_REGEX.get_or_init(|| {
98 Pattern::new(r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*(?:\s*[+\-*/%]\s*[a-zA-Z0-9_\.]+)*)\s*(>=|<=|==|!=|>|<|contains|startsWith|endsWith|matches|in)\s*(.+)"#)
99 .expect("Invalid condition regex")
100 })
101}
102
103fn method_call_regex() -> &'static Pattern {
104 METHOD_CALL_REGEX.get_or_init(|| {
105 Pattern::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).expect("Invalid method call regex")
106 })
107}
108
109fn function_binding_regex() -> &'static Pattern {
110 FUNCTION_BINDING_REGEX.get_or_init(|| {
111 Pattern::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).expect("Invalid function binding regex")
112 })
113}
114
115fn multifield_collect_regex() -> &'static Pattern {
116 MULTIFIELD_COLLECT_REGEX.get_or_init(|| {
117 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
118 .expect("Invalid multifield collect regex")
119 })
120}
121
122fn multifield_count_regex() -> &'static Pattern {
123 MULTIFIELD_COUNT_REGEX.get_or_init(|| {
124 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
125 .expect("Invalid multifield count regex")
126 })
127}
128
129fn multifield_first_regex() -> &'static Pattern {
130 MULTIFIELD_FIRST_REGEX.get_or_init(|| {
131 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
132 .expect("Invalid multifield first regex")
133 })
134}
135
136fn multifield_last_regex() -> &'static Pattern {
137 MULTIFIELD_LAST_REGEX.get_or_init(|| {
138 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
139 .expect("Invalid multifield last regex")
140 })
141}
142
143fn multifield_empty_regex() -> &'static Pattern {
144 MULTIFIELD_EMPTY_REGEX.get_or_init(|| {
145 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#)
146 .expect("Invalid multifield empty regex")
147 })
148}
149
150fn multifield_not_empty_regex() -> &'static Pattern {
151 MULTIFIELD_NOT_EMPTY_REGEX.get_or_init(|| {
152 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
153 .expect("Invalid multifield not_empty regex")
154 })
155}
156
157fn simple_condition_regex() -> &'static Pattern {
158 SIMPLE_CONDITION_REGEX.get_or_init(|| {
159 Pattern::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).expect("Invalid simple condition regex")
160 })
161}
162
163pub struct GRLParser;
166
167#[derive(Debug, Default)]
169struct RuleAttributes {
170 pub no_loop: bool,
171 pub lock_on_active: bool,
172 pub agenda_group: Option<String>,
173 pub activation_group: Option<String>,
174 pub date_effective: Option<DateTime<Utc>>,
175 pub date_expires: Option<DateTime<Utc>>,
176}
177
178#[derive(Debug, Clone)]
180pub struct ParsedGRL {
181 pub rules: Vec<Rule>,
183 pub module_manager: ModuleManager,
185 pub rule_modules: HashMap<String, String>,
187}
188
189impl Default for ParsedGRL {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195impl ParsedGRL {
196 pub fn new() -> Self {
197 Self {
198 rules: Vec::new(),
199 module_manager: ModuleManager::new(),
200 rule_modules: HashMap::new(),
201 }
202 }
203}
204
205impl GRLParser {
206 pub fn parse_rule(grl_text: &str) -> Result<Rule> {
219 let mut parser = GRLParser;
220 parser.parse_single_rule(grl_text)
221 }
222
223 pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
225 let mut parser = GRLParser;
226 parser.parse_multiple_rules(grl_text)
227 }
228
229 pub fn parse_with_modules(grl_text: &str) -> Result<ParsedGRL> {
247 let mut parser = GRLParser;
248 parser.parse_grl_with_modules(grl_text)
249 }
250
251 fn parse_grl_with_modules(&mut self, grl_text: &str) -> Result<ParsedGRL> {
252 let mut result = ParsedGRL::new();
253
254 for module_match in defmodule_split_regex().find_iter(grl_text) {
256 let module_def = module_match.as_str();
257 self.parse_and_register_module(module_def, &mut result.module_manager)?;
258 }
259
260 let rules_text = defmodule_split_regex().replace_all(grl_text, "");
262
263 let rules = self.parse_multiple_rules(&rules_text)?;
265
266 for rule in rules {
268 let module_name = self.extract_module_from_context(grl_text, &rule.name);
269 result
270 .rule_modules
271 .insert(rule.name.clone(), module_name.clone());
272
273 if let Ok(module) = result.module_manager.get_module_mut(&module_name) {
275 module.add_rule(&rule.name);
276 }
277
278 result.rules.push(rule);
279 }
280
281 Ok(result)
282 }
283
284 fn parse_and_register_module(
285 &self,
286 module_def: &str,
287 manager: &mut ModuleManager,
288 ) -> Result<()> {
289 if let Some(captures) = defmodule_regex().captures(module_def) {
291 let module_name = captures.get(1).unwrap().to_string();
292 let module_body = captures.get(2).unwrap();
293
294 let _ = manager.create_module(&module_name);
296 let module = manager.get_module_mut(&module_name)?;
297
298 if let Some(export_type) = self.extract_directive(module_body, "export:") {
300 let exports = if export_type.trim() == "all" {
301 ExportList::All
302 } else if export_type.trim() == "none" {
303 ExportList::None
304 } else {
305 ExportList::Specific(vec![ExportItem {
307 item_type: ItemType::All,
308 pattern: export_type.trim().to_string(),
309 }])
310 };
311 module.set_exports(exports);
312 }
313
314 let import_lines: Vec<&str> = module_body
316 .lines()
317 .filter(|line| line.trim().starts_with("import:"))
318 .collect();
319
320 for import_line in import_lines {
321 if let Some(import_spec) = self.extract_directive(import_line, "import:") {
322 self.parse_import_spec(&module_name, &import_spec, manager)?;
324 }
325 }
326 }
327
328 Ok(())
329 }
330
331 fn extract_directive(&self, text: &str, directive: &str) -> Option<String> {
332 if let Some(pos) = text.find(directive) {
333 let after_directive = &text[pos + directive.len()..];
334
335 let end = after_directive
337 .find("import:")
338 .or_else(|| after_directive.find("export:"))
339 .unwrap_or(after_directive.len());
340
341 Some(after_directive[..end].trim().to_string())
342 } else {
343 None
344 }
345 }
346
347 fn parse_import_spec(
348 &self,
349 importing_module: &str,
350 spec: &str,
351 manager: &mut ModuleManager,
352 ) -> Result<()> {
353 let parts: Vec<&str> = spec.splitn(2, '(').collect();
355 if parts.is_empty() {
356 return Ok(());
357 }
358
359 let source_module = parts[0].trim().to_string();
360 let rest = if parts.len() > 1 { parts[1] } else { "" };
361
362 if rest.contains("rules") {
364 manager.import_from(importing_module, &source_module, ImportType::AllRules, "*")?;
365 }
366
367 if rest.contains("templates") {
368 manager.import_from(
369 importing_module,
370 &source_module,
371 ImportType::AllTemplates,
372 "*",
373 )?;
374 }
375
376 Ok(())
377 }
378
379 fn extract_module_from_context(&self, grl_text: &str, rule_name: &str) -> String {
380 if let Some(rule_pos) = grl_text
382 .find(&format!("rule \"{}\"", rule_name))
383 .or_else(|| grl_text.find(&format!("rule {}", rule_name)))
384 {
385 let before = &grl_text[..rule_pos];
387 if let Some(module_pos) = before.rfind(";; MODULE:") {
388 let after_module_marker = &before[module_pos + 10..];
389 if let Some(end_of_line) = after_module_marker.find('\n') {
390 let module_line = &after_module_marker[..end_of_line].trim();
391 if let Some(first_word) = module_line.split_whitespace().next() {
393 return first_word.to_string();
394 }
395 }
396 }
397 }
398
399 "MAIN".to_string()
401 }
402
403 fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
404 let cleaned = self.clean_text(grl_text);
405
406 let captures =
408 rule_regex()
409 .captures(&cleaned)
410 .ok_or_else(|| RuleEngineError::ParseError {
411 message: format!("Invalid GRL rule format. Input: {}", cleaned),
412 })?;
413
414 let rule_name = if let Some(quoted_name) = captures.get(1) {
416 quoted_name.to_string()
417 } else if let Some(unquoted_name) = captures.get(2) {
418 unquoted_name.to_string()
419 } else {
420 return Err(RuleEngineError::ParseError {
421 message: "Could not extract rule name".to_string(),
422 });
423 };
424
425 let attributes_section = captures.get(3).unwrap_or("");
427
428 let rule_body = captures.get(4).unwrap();
430
431 let salience = self.extract_salience(attributes_section)?;
433
434 let when_then_captures =
436 when_then_regex()
437 .captures(rule_body)
438 .ok_or_else(|| RuleEngineError::ParseError {
439 message: "Missing when or then clause".to_string(),
440 })?;
441
442 let when_clause = when_then_captures.get(1).unwrap().trim();
443 let then_clause = when_then_captures.get(2).unwrap().trim();
444
445 let conditions = self.parse_when_clause(when_clause)?;
447 let actions = self.parse_then_clause(then_clause)?;
448
449 let attributes = self.parse_rule_attributes(attributes_section)?;
451
452 let mut rule = Rule::new(rule_name, conditions, actions);
454 rule = rule.with_priority(salience);
455
456 if attributes.no_loop {
458 rule = rule.with_no_loop(true);
459 }
460 if attributes.lock_on_active {
461 rule = rule.with_lock_on_active(true);
462 }
463 if let Some(agenda_group) = attributes.agenda_group {
464 rule = rule.with_agenda_group(agenda_group);
465 }
466 if let Some(activation_group) = attributes.activation_group {
467 rule = rule.with_activation_group(activation_group);
468 }
469 if let Some(date_effective) = attributes.date_effective {
470 rule = rule.with_date_effective(date_effective);
471 }
472 if let Some(date_expires) = attributes.date_expires {
473 rule = rule.with_date_expires(date_expires);
474 }
475
476 Ok(rule)
477 }
478
479 fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
480 let mut rules = Vec::new();
483
484 for rule_match in rule_split_regex().find_iter(grl_text) {
485 let rule_text = rule_match.as_str();
486 let rule = self.parse_single_rule(rule_text)?;
487 rules.push(rule);
488 }
489
490 Ok(rules)
491 }
492
493 fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
495 let mut attributes = RuleAttributes::default();
496
497 let mut attrs_section = rule_header.to_string();
501
502 let quoted_regex = Pattern::new(r#""[^"]*""#).map_err(|e| RuleEngineError::ParseError {
504 message: format!("Invalid quoted string regex: {}", e),
505 })?;
506 attrs_section = quoted_regex.replace_all(&attrs_section, "").to_string();
507
508 if let Some(rule_pos) = attrs_section.find("rule") {
510 let after_rule = &attrs_section[rule_pos + 4..];
512 if let Some(first_keyword) = after_rule
513 .find("salience")
514 .or_else(|| after_rule.find("no-loop"))
515 .or_else(|| after_rule.find("lock-on-active"))
516 .or_else(|| after_rule.find("agenda-group"))
517 .or_else(|| after_rule.find("activation-group"))
518 .or_else(|| after_rule.find("date-effective"))
519 .or_else(|| after_rule.find("date-expires"))
520 {
521 attrs_section = after_rule[first_keyword..].to_string();
522 }
523 }
524
525 let no_loop_regex =
527 Pattern::new(r"\bno-loop\b").map_err(|e| RuleEngineError::ParseError {
528 message: format!("Invalid no-loop regex: {}", e),
529 })?;
530 let lock_on_active_regex =
531 Pattern::new(r"\block-on-active\b").map_err(|e| RuleEngineError::ParseError {
532 message: format!("Invalid lock-on-active regex: {}", e),
533 })?;
534
535 if no_loop_regex.is_match(&attrs_section) {
536 attributes.no_loop = true;
537 }
538 if lock_on_active_regex.is_match(&attrs_section) {
539 attributes.lock_on_active = true;
540 }
541
542 if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
544 attributes.agenda_group = Some(agenda_group);
545 }
546
547 if let Some(activation_group) =
549 self.extract_quoted_attribute(rule_header, "activation-group")?
550 {
551 attributes.activation_group = Some(activation_group);
552 }
553
554 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
556 attributes.date_effective = Some(self.parse_date_string(&date_str)?);
557 }
558
559 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
561 attributes.date_expires = Some(self.parse_date_string(&date_str)?);
562 }
563
564 Ok(attributes)
565 }
566
567 fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
569 let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
570 let regex = Pattern::new(&pattern).map_err(|e| RuleEngineError::ParseError {
571 message: format!("Invalid attribute regex for {}: {}", attribute, e),
572 })?;
573
574 if let Some(captures) = regex.captures(header) {
575 if let Some(value) = captures.get(1) {
576 return Ok(Some(value.to_string()));
577 }
578 }
579
580 Ok(None)
581 }
582
583 fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
585 if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
587 return Ok(date.with_timezone(&Utc));
588 }
589
590 let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
592
593 for format in &formats {
594 if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
595 return Ok(naive_date.and_utc());
596 }
597 if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
598 return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
599 }
600 }
601
602 Err(RuleEngineError::ParseError {
603 message: format!("Unable to parse date: {}", date_str),
604 })
605 }
606
607 fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
609 if let Some(captures) = salience_regex().captures(attributes_section) {
610 if let Some(salience_match) = captures.get(1) {
611 return salience_match
612 .parse::<i32>()
613 .map_err(|e| RuleEngineError::ParseError {
614 message: format!("Invalid salience value: {}", e),
615 });
616 }
617 }
618
619 Ok(0) }
621
622 fn clean_text(&self, text: &str) -> String {
623 text.lines()
624 .map(|line| line.trim())
625 .filter(|line| !line.is_empty() && !line.starts_with("//"))
626 .collect::<Vec<_>>()
627 .join(" ")
628 }
629
630 fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
631 let trimmed = when_clause.trim();
633
634 let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
636 let inner = &trimmed[1..trimmed.len() - 1];
638 if self.is_balanced_parentheses(inner) {
639 inner
640 } else {
641 trimmed
642 }
643 } else {
644 trimmed
645 };
646
647 if let Some(parts) = self.split_logical_operator(clause, "||") {
649 return self.parse_or_parts(parts);
650 }
651
652 if let Some(parts) = self.split_logical_operator(clause, "&&") {
654 return self.parse_and_parts(parts);
655 }
656
657 if clause.trim_start().starts_with("!") {
659 return self.parse_not_condition(clause);
660 }
661
662 if clause.trim_start().starts_with("exists(") {
664 return self.parse_exists_condition(clause);
665 }
666
667 if clause.trim_start().starts_with("forall(") {
669 return self.parse_forall_condition(clause);
670 }
671
672 if clause.trim_start().starts_with("accumulate(") {
674 return self.parse_accumulate_condition(clause);
675 }
676
677 self.parse_single_condition(clause)
679 }
680
681 fn is_balanced_parentheses(&self, text: &str) -> bool {
682 let mut count = 0;
683 for ch in text.chars() {
684 match ch {
685 '(' => count += 1,
686 ')' => {
687 count -= 1;
688 if count < 0 {
689 return false;
690 }
691 }
692 _ => {}
693 }
694 }
695 count == 0
696 }
697
698 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
699 let mut parts = Vec::new();
700 let mut current_part = String::new();
701 let mut paren_count = 0;
702 let mut chars = clause.chars().peekable();
703
704 while let Some(ch) = chars.next() {
705 match ch {
706 '(' => {
707 paren_count += 1;
708 current_part.push(ch);
709 }
710 ')' => {
711 paren_count -= 1;
712 current_part.push(ch);
713 }
714 '&' if operator == "&&" && paren_count == 0 => {
715 if chars.peek() == Some(&'&') {
716 chars.next(); parts.push(current_part.trim().to_string());
718 current_part.clear();
719 } else {
720 current_part.push(ch);
721 }
722 }
723 '|' if operator == "||" && paren_count == 0 => {
724 if chars.peek() == Some(&'|') {
725 chars.next(); parts.push(current_part.trim().to_string());
727 current_part.clear();
728 } else {
729 current_part.push(ch);
730 }
731 }
732 _ => {
733 current_part.push(ch);
734 }
735 }
736 }
737
738 if !current_part.trim().is_empty() {
739 parts.push(current_part.trim().to_string());
740 }
741
742 if parts.len() > 1 {
743 Some(parts)
744 } else {
745 None
746 }
747 }
748
749 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
750 let mut conditions = Vec::new();
751 for part in parts {
752 let condition = self.parse_when_clause(&part)?;
753 conditions.push(condition);
754 }
755
756 if conditions.is_empty() {
757 return Err(RuleEngineError::ParseError {
758 message: "No conditions found in OR".to_string(),
759 });
760 }
761
762 let mut iter = conditions.into_iter();
763 let mut result = iter.next().unwrap();
764 for condition in iter {
765 result = ConditionGroup::or(result, condition);
766 }
767
768 Ok(result)
769 }
770
771 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
772 let mut conditions = Vec::new();
773 for part in parts {
774 let condition = self.parse_when_clause(&part)?;
775 conditions.push(condition);
776 }
777
778 if conditions.is_empty() {
779 return Err(RuleEngineError::ParseError {
780 message: "No conditions found in AND".to_string(),
781 });
782 }
783
784 let mut iter = conditions.into_iter();
785 let mut result = iter.next().unwrap();
786 for condition in iter {
787 result = ConditionGroup::and(result, condition);
788 }
789
790 Ok(result)
791 }
792
793 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
794 let inner_clause = clause.strip_prefix("!").unwrap().trim();
795 let inner_condition = self.parse_when_clause(inner_clause)?;
796 Ok(ConditionGroup::not(inner_condition))
797 }
798
799 fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
800 let clause = clause.trim_start();
801 if !clause.starts_with("exists(") || !clause.ends_with(")") {
802 return Err(RuleEngineError::ParseError {
803 message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
804 });
805 }
806
807 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
810 Ok(ConditionGroup::exists(inner_condition))
811 }
812
813 fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
814 let clause = clause.trim_start();
815 if !clause.starts_with("forall(") || !clause.ends_with(")") {
816 return Err(RuleEngineError::ParseError {
817 message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
818 });
819 }
820
821 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
824 Ok(ConditionGroup::forall(inner_condition))
825 }
826
827 fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
828 let clause = clause.trim_start();
829 if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
830 return Err(RuleEngineError::ParseError {
831 message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)"
832 .to_string(),
833 });
834 }
835
836 let inner = &clause[11..clause.len() - 1]; let parts = self.split_accumulate_parts(inner)?;
841
842 if parts.len() != 2 {
843 return Err(RuleEngineError::ParseError {
844 message: format!(
845 "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
846 parts.len()
847 ),
848 });
849 }
850
851 let pattern_part = parts[0].trim();
852 let function_part = parts[1].trim();
853
854 let (source_pattern, extract_field, source_conditions) =
856 self.parse_accumulate_pattern(pattern_part)?;
857
858 let (function, function_arg) = self.parse_accumulate_function(function_part)?;
860
861 let result_var = "$result".to_string();
865
866 Ok(ConditionGroup::accumulate(
867 result_var,
868 source_pattern,
869 extract_field,
870 source_conditions,
871 function,
872 function_arg,
873 ))
874 }
875
876 fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
877 let mut parts = Vec::new();
878 let mut current = String::new();
879 let mut paren_depth = 0;
880
881 for ch in content.chars() {
882 match ch {
883 '(' => {
884 paren_depth += 1;
885 current.push(ch);
886 }
887 ')' => {
888 paren_depth -= 1;
889 current.push(ch);
890 }
891 ',' if paren_depth == 0 => {
892 parts.push(current.trim().to_string());
893 current.clear();
894 }
895 _ => {
896 current.push(ch);
897 }
898 }
899 }
900
901 if !current.trim().is_empty() {
902 parts.push(current.trim().to_string());
903 }
904
905 Ok(parts)
906 }
907
908 fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
909 let pattern = pattern.trim();
916
917 let paren_pos = pattern
919 .find('(')
920 .ok_or_else(|| RuleEngineError::ParseError {
921 message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
922 })?;
923
924 let source_pattern = pattern[..paren_pos].trim().to_string();
925
926 if !pattern.ends_with(')') {
928 return Err(RuleEngineError::ParseError {
929 message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
930 });
931 }
932
933 let inner = &pattern[paren_pos + 1..pattern.len() - 1];
934
935 let parts = self.split_pattern_parts(inner)?;
937
938 let mut extract_field = String::new();
939 let mut source_conditions = Vec::new();
940
941 for part in parts {
942 let part = part.trim();
943
944 if part.contains(':') && part.starts_with('$') {
946 let colon_pos = part.find(':').unwrap();
947 let _var_name = part[..colon_pos].trim();
948 let field_name = part[colon_pos + 1..].trim();
949 extract_field = field_name.to_string();
950 } else if part.contains("==")
951 || part.contains("!=")
952 || part.contains(">=")
953 || part.contains("<=")
954 || part.contains('>')
955 || part.contains('<')
956 {
957 source_conditions.push(part.to_string());
959 }
960 }
961
962 Ok((source_pattern, extract_field, source_conditions))
963 }
964
965 fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
966 let mut parts = Vec::new();
967 let mut current = String::new();
968 let mut paren_depth = 0;
969 let mut in_quotes = false;
970 let mut quote_char = ' ';
971
972 for ch in content.chars() {
973 match ch {
974 '"' | '\'' if !in_quotes => {
975 in_quotes = true;
976 quote_char = ch;
977 current.push(ch);
978 }
979 '"' | '\'' if in_quotes && ch == quote_char => {
980 in_quotes = false;
981 current.push(ch);
982 }
983 '(' if !in_quotes => {
984 paren_depth += 1;
985 current.push(ch);
986 }
987 ')' if !in_quotes => {
988 paren_depth -= 1;
989 current.push(ch);
990 }
991 ',' if !in_quotes && paren_depth == 0 => {
992 parts.push(current.trim().to_string());
993 current.clear();
994 }
995 _ => {
996 current.push(ch);
997 }
998 }
999 }
1000
1001 if !current.trim().is_empty() {
1002 parts.push(current.trim().to_string());
1003 }
1004
1005 Ok(parts)
1006 }
1007
1008 fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
1009 let function_str = function_str.trim();
1012
1013 let paren_pos = function_str
1014 .find('(')
1015 .ok_or_else(|| RuleEngineError::ParseError {
1016 message: format!(
1017 "Invalid accumulate function: missing '(' in '{}'",
1018 function_str
1019 ),
1020 })?;
1021
1022 let function_name = function_str[..paren_pos].trim().to_string();
1023
1024 if !function_str.ends_with(')') {
1025 return Err(RuleEngineError::ParseError {
1026 message: format!(
1027 "Invalid accumulate function: missing ')' in '{}'",
1028 function_str
1029 ),
1030 });
1031 }
1032
1033 let args = &function_str[paren_pos + 1..function_str.len() - 1];
1034 let function_arg = args.trim().to_string();
1035
1036 Ok((function_name, function_arg))
1037 }
1038
1039 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
1040 let trimmed_clause = clause.trim();
1042 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
1043 trimmed_clause[1..trimmed_clause.len() - 1].trim()
1044 } else {
1045 trimmed_clause
1046 };
1047
1048 #[cfg(feature = "streaming")]
1051 if clause_to_parse.contains("from stream(") {
1052 return self.parse_stream_pattern_condition(clause_to_parse);
1053 }
1054
1055 if let Some(captures) = multifield_collect_regex().captures(clause_to_parse) {
1062 let field = captures.get(1).unwrap().to_string();
1063 let variable = captures.get(2).unwrap().to_string();
1064
1065 let condition = Condition::with_multifield_collect(field, variable);
1068 return Ok(ConditionGroup::single(condition));
1069 }
1070
1071 if let Some(captures) = multifield_count_regex().captures(clause_to_parse) {
1078 let field = captures.get(1).unwrap().to_string();
1079 let operator_str = captures.get(2).unwrap();
1080 let value_str = captures.get(3).unwrap().trim();
1081
1082 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1083 RuleEngineError::InvalidOperator {
1084 operator: operator_str.to_string(),
1085 }
1086 })?;
1087
1088 let value = self.parse_value(value_str)?;
1089
1090 let condition = Condition::with_multifield_count(field, operator, value);
1091 return Ok(ConditionGroup::single(condition));
1092 }
1093
1094 if let Some(captures) = multifield_first_regex().captures(clause_to_parse) {
1097 let field = captures.get(1).unwrap().to_string();
1098 let variable = captures.get(2).map(|m| m.to_string());
1099
1100 let condition = Condition::with_multifield_first(field, variable);
1101 return Ok(ConditionGroup::single(condition));
1102 }
1103
1104 if let Some(captures) = multifield_last_regex().captures(clause_to_parse) {
1107 let field = captures.get(1).unwrap().to_string();
1108 let variable = captures.get(2).map(|m| m.to_string());
1109
1110 let condition = Condition::with_multifield_last(field, variable);
1111 return Ok(ConditionGroup::single(condition));
1112 }
1113
1114 if let Some(captures) = multifield_empty_regex().captures(clause_to_parse) {
1117 let field = captures.get(1).unwrap().to_string();
1118
1119 let condition = Condition::with_multifield_empty(field);
1120 return Ok(ConditionGroup::single(condition));
1121 }
1122
1123 if let Some(captures) = multifield_not_empty_regex().captures(clause_to_parse) {
1126 let field = captures.get(1).unwrap().to_string();
1127
1128 let condition = Condition::with_multifield_not_empty(field);
1129 return Ok(ConditionGroup::single(condition));
1130 }
1131
1132 if let Some(captures) = test_condition_regex().captures(clause_to_parse) {
1137 let function_name = captures.get(1).unwrap().to_string();
1138 let args_str = captures.get(2).unwrap();
1139
1140 let args: Vec<String> = if args_str.trim().is_empty() {
1142 Vec::new()
1143 } else {
1144 args_str
1145 .split(',')
1146 .map(|arg| arg.trim().to_string())
1147 .collect()
1148 };
1149
1150 let condition = Condition::with_test(function_name, args);
1151 return Ok(ConditionGroup::single(condition));
1152 }
1153
1154 if let Some(captures) = typed_test_condition_regex().captures(clause_to_parse) {
1156 let _object_name = captures.get(1).unwrap();
1157 let _object_type = captures.get(2).unwrap();
1158 let conditions_str = captures.get(3).unwrap();
1159
1160 return self.parse_conditions_within_object(conditions_str);
1162 }
1163
1164 if let Some(captures) = function_call_regex().captures(clause_to_parse) {
1166 let function_name = captures.get(1).unwrap().to_string();
1167 let args_str = captures.get(2).unwrap();
1168 let operator_str = captures.get(3).unwrap();
1169 let value_str = captures.get(4).unwrap().trim();
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 operator = Operator::from_str(operator_str).ok_or_else(|| {
1182 RuleEngineError::InvalidOperator {
1183 operator: operator_str.to_string(),
1184 }
1185 })?;
1186
1187 let value = self.parse_value(value_str)?;
1188
1189 let condition = Condition::with_function(function_name, args, operator, value);
1190 return Ok(ConditionGroup::single(condition));
1191 }
1192
1193 let captures = condition_regex().captures(clause_to_parse).ok_or_else(|| {
1197 RuleEngineError::ParseError {
1198 message: format!("Invalid condition format: {}", clause_to_parse),
1199 }
1200 })?;
1201
1202 let left_side = captures.get(1).unwrap().trim().to_string();
1203 let operator_str = captures.get(2).unwrap();
1204 let value_str = captures.get(3).unwrap().trim();
1205
1206 let operator =
1207 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1208 operator: operator_str.to_string(),
1209 })?;
1210
1211 let value = self.parse_value(value_str)?;
1212
1213 if left_side.contains('+')
1215 || left_side.contains('-')
1216 || left_side.contains('*')
1217 || left_side.contains('/')
1218 || left_side.contains('%')
1219 {
1220 let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
1223 let condition = Condition::with_test(test_expr, vec![]);
1224 Ok(ConditionGroup::single(condition))
1225 } else {
1226 let condition = Condition::new(left_side, operator, value);
1228 Ok(ConditionGroup::single(condition))
1229 }
1230 }
1231
1232 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
1233 let parts: Vec<&str> = conditions_str.split("&&").collect();
1235
1236 let mut conditions = Vec::new();
1237 for part in parts {
1238 let trimmed = part.trim();
1239 let condition = self.parse_simple_condition(trimmed)?;
1240 conditions.push(condition);
1241 }
1242
1243 if conditions.is_empty() {
1245 return Err(RuleEngineError::ParseError {
1246 message: "No conditions found".to_string(),
1247 });
1248 }
1249
1250 let mut iter = conditions.into_iter();
1251 let mut result = iter.next().unwrap();
1252 for condition in iter {
1253 result = ConditionGroup::and(result, condition);
1254 }
1255
1256 Ok(result)
1257 }
1258
1259 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
1260 let captures = simple_condition_regex().captures(clause).ok_or_else(|| {
1262 RuleEngineError::ParseError {
1263 message: format!("Invalid simple condition format: {}", clause),
1264 }
1265 })?;
1266
1267 let field = captures.get(1).unwrap().to_string();
1268 let operator_str = captures.get(2).unwrap();
1269 let value_str = captures.get(3).unwrap().trim();
1270
1271 let operator =
1272 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1273 operator: operator_str.to_string(),
1274 })?;
1275
1276 let value = self.parse_value(value_str)?;
1277
1278 let condition = Condition::new(field, operator, value);
1279 Ok(ConditionGroup::single(condition))
1280 }
1281
1282 fn parse_value(&self, value_str: &str) -> Result<Value> {
1283 let trimmed = value_str.trim();
1284
1285 if trimmed.starts_with('[') && trimmed.ends_with(']') {
1287 return self.parse_array_literal(trimmed);
1288 }
1289
1290 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
1292 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
1293 {
1294 let unquoted = &trimmed[1..trimmed.len() - 1];
1295 return Ok(Value::String(unquoted.to_string()));
1296 }
1297
1298 if trimmed.eq_ignore_ascii_case("true") {
1300 return Ok(Value::Boolean(true));
1301 }
1302 if trimmed.eq_ignore_ascii_case("false") {
1303 return Ok(Value::Boolean(false));
1304 }
1305
1306 if trimmed.eq_ignore_ascii_case("null") {
1308 return Ok(Value::Null);
1309 }
1310
1311 if let Ok(int_val) = trimmed.parse::<i64>() {
1313 return Ok(Value::Integer(int_val));
1314 }
1315
1316 if let Ok(float_val) = trimmed.parse::<f64>() {
1317 return Ok(Value::Number(float_val));
1318 }
1319
1320 if self.is_expression(trimmed) {
1323 return Ok(Value::Expression(trimmed.to_string()));
1324 }
1325
1326 if trimmed.contains('.') {
1328 return Ok(Value::String(trimmed.to_string()));
1329 }
1330
1331 if self.is_identifier(trimmed) {
1335 return Ok(Value::Expression(trimmed.to_string()));
1336 }
1337
1338 Ok(Value::String(trimmed.to_string()))
1340 }
1341
1342 fn is_identifier(&self, s: &str) -> bool {
1345 if s.is_empty() {
1346 return false;
1347 }
1348
1349 let first_char = s.chars().next().unwrap();
1351 if !first_char.is_alphabetic() && first_char != '_' {
1352 return false;
1353 }
1354
1355 s.chars().all(|c| c.is_alphanumeric() || c == '_')
1357 }
1358
1359 fn is_expression(&self, s: &str) -> bool {
1361 let has_operator = s.contains('+')
1363 || s.contains('-')
1364 || s.contains('*')
1365 || s.contains('/')
1366 || s.contains('%');
1367
1368 let has_field_ref = s.contains('.');
1370
1371 let has_spaces = s.contains(' ');
1373
1374 has_operator && (has_field_ref || has_spaces)
1376 }
1377
1378 fn parse_array_literal(&self, array_str: &str) -> Result<Value> {
1380 let content = array_str.trim();
1381 if !content.starts_with('[') || !content.ends_with(']') {
1382 return Err(RuleEngineError::ParseError {
1383 message: format!("Invalid array literal: {}", array_str),
1384 });
1385 }
1386
1387 let inner = content[1..content.len() - 1].trim();
1388 if inner.is_empty() {
1389 return Ok(Value::Array(vec![]));
1390 }
1391
1392 let mut elements = Vec::new();
1394 let mut current_element = String::new();
1395 let mut in_quotes = false;
1396 let mut quote_char = ' ';
1397
1398 for ch in inner.chars() {
1399 match ch {
1400 '"' | '\'' if !in_quotes => {
1401 in_quotes = true;
1402 quote_char = ch;
1403 current_element.push(ch);
1404 }
1405 c if in_quotes && c == quote_char => {
1406 in_quotes = false;
1407 current_element.push(ch);
1408 }
1409 ',' if !in_quotes => {
1410 if !current_element.trim().is_empty() {
1411 elements.push(current_element.trim().to_string());
1412 }
1413 current_element.clear();
1414 }
1415 _ => {
1416 current_element.push(ch);
1417 }
1418 }
1419 }
1420
1421 if !current_element.trim().is_empty() {
1423 elements.push(current_element.trim().to_string());
1424 }
1425
1426 let mut array_values = Vec::new();
1428 for elem in elements {
1429 let value = self.parse_value(&elem)?;
1430 array_values.push(value);
1431 }
1432
1433 Ok(Value::Array(array_values))
1434 }
1435
1436 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1437 let statements: Vec<&str> = then_clause
1438 .split(';')
1439 .map(|s| s.trim())
1440 .filter(|s| !s.is_empty())
1441 .collect();
1442
1443 let mut actions = Vec::new();
1444
1445 for statement in statements {
1446 let action = self.parse_action_statement(statement)?;
1447 actions.push(action);
1448 }
1449
1450 Ok(actions)
1451 }
1452
1453 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1454 let trimmed = statement.trim();
1455
1456 if let Some(captures) = method_call_regex().captures(trimmed) {
1458 let object = captures.get(1).unwrap().to_string();
1459 let method = captures.get(2).unwrap().to_string();
1460 let args_str = captures.get(3).unwrap();
1461
1462 let args = if args_str.trim().is_empty() {
1463 Vec::new()
1464 } else {
1465 self.parse_method_args(args_str)?
1466 };
1467
1468 return Ok(ActionType::MethodCall {
1469 object,
1470 method,
1471 args,
1472 });
1473 }
1474
1475 if let Some(plus_eq_pos) = trimmed.find("+=") {
1477 let field = trimmed[..plus_eq_pos].trim().to_string();
1479 let value_str = trimmed[plus_eq_pos + 2..].trim();
1480 let value = self.parse_value(value_str)?;
1481
1482 return Ok(ActionType::Append { field, value });
1483 }
1484
1485 if let Some(eq_pos) = trimmed.find('=') {
1487 let field = trimmed[..eq_pos].trim().to_string();
1488 let value_str = trimmed[eq_pos + 1..].trim();
1489 let value = self.parse_value(value_str)?;
1490
1491 return Ok(ActionType::Set { field, value });
1492 }
1493
1494 if let Some(captures) = function_binding_regex().captures(trimmed) {
1496 let function_name = captures.get(1).unwrap();
1497 let args_str = captures.get(2).unwrap_or("");
1498
1499 match function_name.to_lowercase().as_str() {
1500 "retract" => {
1501 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1503 stripped.to_string()
1504 } else {
1505 args_str.to_string()
1506 };
1507 Ok(ActionType::Retract {
1508 object: object_name,
1509 })
1510 }
1511 "log" => {
1512 let message = if args_str.is_empty() {
1513 "Log message".to_string()
1514 } else {
1515 let value = self.parse_value(args_str.trim())?;
1516 value.to_string()
1517 };
1518 Ok(ActionType::Log { message })
1519 }
1520 "activateagendagroup" | "activate_agenda_group" => {
1521 let agenda_group = if args_str.is_empty() {
1522 return Err(RuleEngineError::ParseError {
1523 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1524 });
1525 } else {
1526 let value = self.parse_value(args_str.trim())?;
1527 match value {
1528 Value::String(s) => s,
1529 _ => value.to_string(),
1530 }
1531 };
1532 Ok(ActionType::ActivateAgendaGroup {
1533 group: agenda_group,
1534 })
1535 }
1536 "schedulerule" | "schedule_rule" => {
1537 let parts: Vec<&str> = args_str.split(',').collect();
1539 if parts.len() != 2 {
1540 return Err(RuleEngineError::ParseError {
1541 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1542 });
1543 }
1544
1545 let delay_ms = self.parse_value(parts[0].trim())?;
1546 let rule_name = self.parse_value(parts[1].trim())?;
1547
1548 let delay_ms = match delay_ms {
1549 Value::Integer(i) => i as u64,
1550 Value::Number(f) => f as u64,
1551 _ => {
1552 return Err(RuleEngineError::ParseError {
1553 message: "ScheduleRule delay_ms must be a number".to_string(),
1554 })
1555 }
1556 };
1557
1558 let rule_name = match rule_name {
1559 Value::String(s) => s,
1560 _ => rule_name.to_string(),
1561 };
1562
1563 Ok(ActionType::ScheduleRule {
1564 delay_ms,
1565 rule_name,
1566 })
1567 }
1568 "completeworkflow" | "complete_workflow" => {
1569 let workflow_id = if args_str.is_empty() {
1570 return Err(RuleEngineError::ParseError {
1571 message: "CompleteWorkflow requires workflow_id".to_string(),
1572 });
1573 } else {
1574 let value = self.parse_value(args_str.trim())?;
1575 match value {
1576 Value::String(s) => s,
1577 _ => value.to_string(),
1578 }
1579 };
1580 Ok(ActionType::CompleteWorkflow {
1581 workflow_name: workflow_id,
1582 })
1583 }
1584 "setworkflowdata" | "set_workflow_data" => {
1585 let data_str = args_str.trim();
1587
1588 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1590 let key = data_str[..eq_pos].trim().trim_matches('"');
1591 let value_str = data_str[eq_pos + 1..].trim();
1592 let value = self.parse_value(value_str)?;
1593 (key.to_string(), value)
1594 } else {
1595 return Err(RuleEngineError::ParseError {
1596 message: "SetWorkflowData data must be in key=value format".to_string(),
1597 });
1598 };
1599
1600 Ok(ActionType::SetWorkflowData { key, value })
1601 }
1602 _ => {
1603 let params = if args_str.is_empty() {
1605 HashMap::new()
1606 } else {
1607 self.parse_function_args_as_params(args_str)?
1608 };
1609
1610 Ok(ActionType::Custom {
1611 action_type: function_name.to_string(),
1612 params,
1613 })
1614 }
1615 }
1616 } else {
1617 Ok(ActionType::Custom {
1619 action_type: "statement".to_string(),
1620 params: {
1621 let mut params = HashMap::new();
1622 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1623 params
1624 },
1625 })
1626 }
1627 }
1628
1629 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1630 if args_str.trim().is_empty() {
1631 return Ok(Vec::new());
1632 }
1633
1634 let mut args = Vec::new();
1636 let parts: Vec<&str> = args_str.split(',').collect();
1637
1638 for part in parts {
1639 let trimmed = part.trim();
1640
1641 if trimmed.contains('+')
1643 || trimmed.contains('-')
1644 || trimmed.contains('*')
1645 || trimmed.contains('/')
1646 {
1647 args.push(Value::String(trimmed.to_string()));
1649 } else {
1650 args.push(self.parse_value(trimmed)?);
1651 }
1652 }
1653
1654 Ok(args)
1655 }
1656
1657 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1659 let mut params = HashMap::new();
1660
1661 if args_str.trim().is_empty() {
1662 return Ok(params);
1663 }
1664
1665 let parts: Vec<&str> = args_str.split(',').collect();
1667 for (i, part) in parts.iter().enumerate() {
1668 let trimmed = part.trim();
1669 let value = self.parse_value(trimmed)?;
1670
1671 params.insert(i.to_string(), value);
1673 }
1674
1675 Ok(params)
1676 }
1677
1678 #[cfg(feature = "streaming")]
1681 fn parse_stream_pattern_condition(&self, clause: &str) -> Result<ConditionGroup> {
1682 use crate::engine::rule::{StreamWindow, StreamWindowType};
1683 use crate::parser::grl::stream_syntax::parse_stream_pattern;
1684
1685 let parse_result =
1687 parse_stream_pattern(clause).map_err(|e| RuleEngineError::ParseError {
1688 message: format!("Failed to parse stream pattern: {:?}", e),
1689 })?;
1690
1691 let (_, pattern) = parse_result;
1692
1693 let window = pattern.source.window.map(|w| StreamWindow {
1695 duration: w.duration,
1696 window_type: match w.window_type {
1697 crate::parser::grl::stream_syntax::WindowType::Sliding => StreamWindowType::Sliding,
1698 crate::parser::grl::stream_syntax::WindowType::Tumbling => {
1699 StreamWindowType::Tumbling
1700 }
1701 crate::parser::grl::stream_syntax::WindowType::Session { timeout } => {
1702 StreamWindowType::Session { timeout }
1703 }
1704 },
1705 });
1706
1707 Ok(ConditionGroup::stream_pattern(
1708 pattern.var_name,
1709 pattern.event_type,
1710 pattern.source.stream_name,
1711 window,
1712 ))
1713 }
1714}
1715
1716#[cfg(test)]
1717mod tests {
1718 use super::GRLParser;
1719
1720 #[test]
1721 fn test_parse_simple_rule() {
1722 let grl = r#"
1723 rule "CheckAge" salience 10 {
1724 when
1725 User.Age >= 18
1726 then
1727 log("User is adult");
1728 }
1729 "#;
1730
1731 let rules = GRLParser::parse_rules(grl).unwrap();
1732 assert_eq!(rules.len(), 1);
1733 let rule = &rules[0];
1734 assert_eq!(rule.name, "CheckAge");
1735 assert_eq!(rule.salience, 10);
1736 assert_eq!(rule.actions.len(), 1);
1737 }
1738
1739 #[test]
1740 fn test_parse_complex_condition() {
1741 let grl = r#"
1742 rule "ComplexRule" {
1743 when
1744 User.Age >= 18 && User.Country == "US"
1745 then
1746 User.Qualified = true;
1747 }
1748 "#;
1749
1750 let rules = GRLParser::parse_rules(grl).unwrap();
1751 assert_eq!(rules.len(), 1);
1752 let rule = &rules[0];
1753 assert_eq!(rule.name, "ComplexRule");
1754 }
1755
1756 #[test]
1757 fn test_parse_new_syntax_with_parentheses() {
1758 let grl = r#"
1759 rule "Default Rule" salience 10 {
1760 when
1761 (user.age >= 18)
1762 then
1763 set(user.status, "approved");
1764 }
1765 "#;
1766
1767 let rules = GRLParser::parse_rules(grl).unwrap();
1768 assert_eq!(rules.len(), 1);
1769 let rule = &rules[0];
1770 assert_eq!(rule.name, "Default Rule");
1771 assert_eq!(rule.salience, 10);
1772 assert_eq!(rule.actions.len(), 1);
1773
1774 match &rule.actions[0] {
1776 crate::types::ActionType::Custom {
1777 action_type,
1778 params,
1779 } => {
1780 assert_eq!(action_type, "set");
1781 assert_eq!(
1782 params.get("0"),
1783 Some(&crate::types::Value::String("user.status".to_string()))
1784 );
1785 assert_eq!(
1786 params.get("1"),
1787 Some(&crate::types::Value::String("approved".to_string()))
1788 );
1789 }
1790 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1791 }
1792 }
1793
1794 #[test]
1795 fn test_parse_complex_nested_conditions() {
1796 let grl = r#"
1797 rule "Complex Business Rule" salience 10 {
1798 when
1799 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1800 then
1801 apply_discount(20000);
1802 }
1803 "#;
1804
1805 let rules = GRLParser::parse_rules(grl).unwrap();
1806 assert_eq!(rules.len(), 1);
1807 let rule = &rules[0];
1808 assert_eq!(rule.name, "Complex Business Rule");
1809 assert_eq!(rule.salience, 10);
1810 assert_eq!(rule.actions.len(), 1);
1811
1812 match &rule.actions[0] {
1814 crate::types::ActionType::Custom {
1815 action_type,
1816 params,
1817 } => {
1818 assert_eq!(action_type, "apply_discount");
1819 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1820 }
1821 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1822 }
1823 }
1824
1825 #[test]
1826 fn test_parse_no_loop_attribute() {
1827 let grl = r#"
1828 rule "NoLoopRule" no-loop salience 15 {
1829 when
1830 User.Score < 100
1831 then
1832 set(User.Score, User.Score + 10);
1833 }
1834 "#;
1835
1836 let rules = GRLParser::parse_rules(grl).unwrap();
1837 assert_eq!(rules.len(), 1);
1838 let rule = &rules[0];
1839 assert_eq!(rule.name, "NoLoopRule");
1840 assert_eq!(rule.salience, 15);
1841 assert!(rule.no_loop, "Rule should have no-loop=true");
1842 }
1843
1844 #[test]
1845 fn test_parse_no_loop_different_positions() {
1846 let grl1 = r#"
1848 rule "Rule1" no-loop salience 10 {
1849 when User.Age >= 18
1850 then log("adult");
1851 }
1852 "#;
1853
1854 let grl2 = r#"
1856 rule "Rule2" salience 10 no-loop {
1857 when User.Age >= 18
1858 then log("adult");
1859 }
1860 "#;
1861
1862 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1863 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1864
1865 assert_eq!(rules1.len(), 1);
1866 assert_eq!(rules2.len(), 1);
1867
1868 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1869 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1870
1871 assert_eq!(rules1[0].salience, 10);
1872 assert_eq!(rules2[0].salience, 10);
1873 }
1874
1875 #[test]
1876 fn test_parse_without_no_loop() {
1877 let grl = r#"
1878 rule "RegularRule" salience 5 {
1879 when
1880 User.Active == true
1881 then
1882 log("active user");
1883 }
1884 "#;
1885
1886 let rules = GRLParser::parse_rules(grl).unwrap();
1887 assert_eq!(rules.len(), 1);
1888 let rule = &rules[0];
1889 assert_eq!(rule.name, "RegularRule");
1890 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1891 }
1892
1893 #[test]
1894 fn test_parse_exists_pattern() {
1895 let grl = r#"
1896 rule "ExistsRule" salience 20 {
1897 when
1898 exists(Customer.tier == "VIP")
1899 then
1900 System.premiumActive = true;
1901 }
1902 "#;
1903
1904 let rules = GRLParser::parse_rules(grl).unwrap();
1905 assert_eq!(rules.len(), 1);
1906 let rule = &rules[0];
1907 assert_eq!(rule.name, "ExistsRule");
1908 assert_eq!(rule.salience, 20);
1909
1910 match &rule.conditions {
1912 crate::engine::rule::ConditionGroup::Exists(_) => {
1913 }
1915 _ => panic!(
1916 "Expected EXISTS condition group, got: {:?}",
1917 rule.conditions
1918 ),
1919 }
1920 }
1921
1922 #[test]
1923 fn test_parse_forall_pattern() {
1924 let grl = r#"
1925 rule "ForallRule" salience 15 {
1926 when
1927 forall(Order.status == "processed")
1928 then
1929 Shipping.enabled = true;
1930 }
1931 "#;
1932
1933 let rules = GRLParser::parse_rules(grl).unwrap();
1934 assert_eq!(rules.len(), 1);
1935 let rule = &rules[0];
1936 assert_eq!(rule.name, "ForallRule");
1937
1938 match &rule.conditions {
1940 crate::engine::rule::ConditionGroup::Forall(_) => {
1941 }
1943 _ => panic!(
1944 "Expected FORALL condition group, got: {:?}",
1945 rule.conditions
1946 ),
1947 }
1948 }
1949
1950 #[test]
1951 fn test_parse_combined_patterns() {
1952 let grl = r#"
1953 rule "CombinedRule" salience 25 {
1954 when
1955 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1956 then
1957 System.vipMode = true;
1958 }
1959 "#;
1960
1961 let rules = GRLParser::parse_rules(grl).unwrap();
1962 assert_eq!(rules.len(), 1);
1963 let rule = &rules[0];
1964 assert_eq!(rule.name, "CombinedRule");
1965
1966 match &rule.conditions {
1968 crate::engine::rule::ConditionGroup::Compound {
1969 left,
1970 operator,
1971 right,
1972 } => {
1973 assert_eq!(*operator, crate::types::LogicalOperator::And);
1974
1975 match left.as_ref() {
1977 crate::engine::rule::ConditionGroup::Exists(_) => {
1978 }
1980 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1981 }
1982
1983 match right.as_ref() {
1985 crate::engine::rule::ConditionGroup::Not(inner) => {
1986 match inner.as_ref() {
1987 crate::engine::rule::ConditionGroup::Exists(_) => {
1988 }
1990 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1991 }
1992 }
1993 _ => panic!("Expected NOT in right side, got: {:?}", right),
1994 }
1995 }
1996 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1997 }
1998 }
1999
2000 #[test]
2001 fn test_parse_in_operator() {
2002 let grl = r#"
2003 rule "TestInOperator" salience 75 {
2004 when
2005 User.role in ["admin", "moderator", "vip"]
2006 then
2007 User.access = "granted";
2008 }
2009 "#;
2010
2011 let rules = GRLParser::parse_rules(grl).unwrap();
2012 assert_eq!(rules.len(), 1);
2013 let rule = &rules[0];
2014 assert_eq!(rule.name, "TestInOperator");
2015 assert_eq!(rule.salience, 75);
2016
2017 match &rule.conditions {
2019 crate::engine::rule::ConditionGroup::Single(cond) => {
2020 println!("Condition: {:?}", cond);
2022 assert_eq!(cond.operator, crate::types::Operator::In);
2023
2024 match &cond.value {
2026 crate::types::Value::Array(arr) => {
2027 assert_eq!(arr.len(), 3);
2028 assert_eq!(arr[0], crate::types::Value::String("admin".to_string()));
2029 assert_eq!(arr[1], crate::types::Value::String("moderator".to_string()));
2030 assert_eq!(arr[2], crate::types::Value::String("vip".to_string()));
2031 }
2032 _ => panic!("Expected Array value, got {:?}", cond.value),
2033 }
2034 }
2035 _ => panic!("Expected Single condition, got: {:?}", rule.conditions),
2036 }
2037 }
2038
2039 #[test]
2040 fn test_parse_startswith_endswith_operators() {
2041 let grl = r#"
2042 rule "StringMethods" salience 50 {
2043 when
2044 User.email startsWith "admin@" &&
2045 User.filename endsWith ".txt"
2046 then
2047 User.validated = true;
2048 }
2049 "#;
2050
2051 let rules = GRLParser::parse_rules(grl).unwrap();
2052 assert_eq!(rules.len(), 1);
2053 let rule = &rules[0];
2054 assert_eq!(rule.name, "StringMethods");
2055 assert_eq!(rule.salience, 50);
2056
2057 match &rule.conditions {
2059 crate::engine::rule::ConditionGroup::Compound {
2060 left,
2061 operator,
2062 right,
2063 } => {
2064 assert_eq!(*operator, crate::types::LogicalOperator::And);
2065
2066 match left.as_ref() {
2068 crate::engine::rule::ConditionGroup::Single(cond) => {
2069 assert_eq!(cond.operator, crate::types::Operator::StartsWith);
2070 }
2071 _ => panic!("Expected Single condition for startsWith, got: {:?}", left),
2072 }
2073
2074 match right.as_ref() {
2076 crate::engine::rule::ConditionGroup::Single(cond) => {
2077 assert_eq!(cond.operator, crate::types::Operator::EndsWith);
2078 }
2079 _ => panic!("Expected Single condition for endsWith, got: {:?}", right),
2080 }
2081 }
2082 _ => panic!("Expected Compound condition, got: {:?}", rule.conditions),
2083 }
2084 }
2085}