1use crate::engine::rule::{Condition, ConditionGroup, Rule};
2use crate::errors::{Result, RuleEngineError};
3use crate::types::{ActionType, Operator, Value};
4use chrono::{DateTime, Utc};
5use regex::Regex;
6use once_cell::sync::Lazy;
7use std::collections::HashMap;
8
9static RULE_REGEX: Lazy<Regex> = Lazy::new(|| {
11 Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
12 .expect("Invalid rule regex pattern")
13});
14
15static RULE_SPLIT_REGEX: Lazy<Regex> = Lazy::new(|| {
16 Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
17 .expect("Invalid rule split regex pattern")
18});
19
20static WHEN_THEN_REGEX: Lazy<Regex> = Lazy::new(|| {
21 Regex::new(r"when\s+(.+?)\s+then\s+(.+)")
22 .expect("Invalid when-then regex pattern")
23});
24
25static SALIENCE_REGEX: Lazy<Regex> = Lazy::new(|| {
26 Regex::new(r"salience\s+(\d+)")
27 .expect("Invalid salience regex pattern")
28});
29
30static TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
31 Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
32 .expect("Invalid test condition regex")
33});
34
35static TYPED_TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
36 Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
37 .expect("Invalid typed test condition regex")
38});
39
40static FUNCTION_CALL_REGEX: Lazy<Regex> = Lazy::new(|| {
41 Regex::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
42 .expect("Invalid function call regex")
43});
44
45static CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
46 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*(.+)"#)
47 .expect("Invalid condition regex")
48});
49
50static METHOD_CALL_REGEX: Lazy<Regex> = Lazy::new(|| {
51 Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#)
52 .expect("Invalid method call regex")
53});
54
55static FUNCTION_BINDING_REGEX: Lazy<Regex> = Lazy::new(|| {
56 Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#)
57 .expect("Invalid function binding regex")
58});
59
60static MULTIFIELD_COLLECT_REGEX: Lazy<Regex> = Lazy::new(|| {
62 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
63 .expect("Invalid multifield collect regex")
64});
65
66static MULTIFIELD_COUNT_REGEX: Lazy<Regex> = Lazy::new(|| {
67 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
68 .expect("Invalid multifield count regex")
69});
70
71static MULTIFIELD_FIRST_REGEX: Lazy<Regex> = Lazy::new(|| {
72 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
73 .expect("Invalid multifield first regex")
74});
75
76static MULTIFIELD_LAST_REGEX: Lazy<Regex> = Lazy::new(|| {
77 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
78 .expect("Invalid multifield last regex")
79});
80
81static MULTIFIELD_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
82 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#)
83 .expect("Invalid multifield empty regex")
84});
85
86static MULTIFIELD_NOT_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
87 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
88 .expect("Invalid multifield not_empty regex")
89});
90
91static SIMPLE_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
92 Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#)
93 .expect("Invalid simple condition regex")
94});
95
96pub struct GRLParser;
99
100#[derive(Debug, Default)]
102struct RuleAttributes {
103 pub no_loop: bool,
104 pub lock_on_active: bool,
105 pub agenda_group: Option<String>,
106 pub activation_group: Option<String>,
107 pub date_effective: Option<DateTime<Utc>>,
108 pub date_expires: Option<DateTime<Utc>>,
109}
110
111impl GRLParser {
112 pub fn parse_rule(grl_text: &str) -> Result<Rule> {
125 let mut parser = GRLParser;
126 parser.parse_single_rule(grl_text)
127 }
128
129 pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
131 let mut parser = GRLParser;
132 parser.parse_multiple_rules(grl_text)
133 }
134
135 fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
136 let cleaned = self.clean_text(grl_text);
137
138 let captures =
140 RULE_REGEX
141 .captures(&cleaned)
142 .ok_or_else(|| RuleEngineError::ParseError {
143 message: format!("Invalid GRL rule format. Input: {}", cleaned),
144 })?;
145
146 let rule_name = if let Some(quoted_name) = captures.get(1) {
148 quoted_name.as_str().to_string()
149 } else if let Some(unquoted_name) = captures.get(2) {
150 unquoted_name.as_str().to_string()
151 } else {
152 return Err(RuleEngineError::ParseError {
153 message: "Could not extract rule name".to_string(),
154 });
155 };
156
157 let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
159
160 let rule_body = captures.get(4).unwrap().as_str();
162
163 let salience = self.extract_salience(attributes_section)?;
165
166 let when_then_captures =
168 WHEN_THEN_REGEX
169 .captures(rule_body)
170 .ok_or_else(|| RuleEngineError::ParseError {
171 message: "Missing when or then clause".to_string(),
172 })?;
173
174 let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
175 let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
176
177 let conditions = self.parse_when_clause(when_clause)?;
179 let actions = self.parse_then_clause(then_clause)?;
180
181 let attributes = self.parse_rule_attributes(attributes_section)?;
183
184 let mut rule = Rule::new(rule_name, conditions, actions);
186 rule = rule.with_priority(salience);
187
188 if attributes.no_loop {
190 rule = rule.with_no_loop(true);
191 }
192 if attributes.lock_on_active {
193 rule = rule.with_lock_on_active(true);
194 }
195 if let Some(agenda_group) = attributes.agenda_group {
196 rule = rule.with_agenda_group(agenda_group);
197 }
198 if let Some(activation_group) = attributes.activation_group {
199 rule = rule.with_activation_group(activation_group);
200 }
201 if let Some(date_effective) = attributes.date_effective {
202 rule = rule.with_date_effective(date_effective);
203 }
204 if let Some(date_expires) = attributes.date_expires {
205 rule = rule.with_date_expires(date_expires);
206 }
207
208 Ok(rule)
209 }
210
211 fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
212 let mut rules = Vec::new();
215
216 for rule_match in RULE_SPLIT_REGEX.find_iter(grl_text) {
217 let rule_text = rule_match.as_str();
218 let rule = self.parse_single_rule(rule_text)?;
219 rules.push(rule);
220 }
221
222 Ok(rules)
223 }
224
225 fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
227 let mut attributes = RuleAttributes::default();
228
229 if rule_header.contains("no-loop") {
231 attributes.no_loop = true;
232 }
233 if rule_header.contains("lock-on-active") {
234 attributes.lock_on_active = true;
235 }
236
237 if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
239 attributes.agenda_group = Some(agenda_group);
240 }
241
242 if let Some(activation_group) =
244 self.extract_quoted_attribute(rule_header, "activation-group")?
245 {
246 attributes.activation_group = Some(activation_group);
247 }
248
249 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
251 attributes.date_effective = Some(self.parse_date_string(&date_str)?);
252 }
253
254 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
256 attributes.date_expires = Some(self.parse_date_string(&date_str)?);
257 }
258
259 Ok(attributes)
260 }
261
262 fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
264 let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
265 let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
266 message: format!("Invalid attribute regex for {}: {}", attribute, e),
267 })?;
268
269 if let Some(captures) = regex.captures(header) {
270 if let Some(value) = captures.get(1) {
271 return Ok(Some(value.as_str().to_string()));
272 }
273 }
274
275 Ok(None)
276 }
277
278 fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
280 if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
282 return Ok(date.with_timezone(&Utc));
283 }
284
285 let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
287
288 for format in &formats {
289 if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
290 return Ok(naive_date.and_utc());
291 }
292 if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
293 return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
294 }
295 }
296
297 Err(RuleEngineError::ParseError {
298 message: format!("Unable to parse date: {}", date_str),
299 })
300 }
301
302 fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
304 if let Some(captures) = SALIENCE_REGEX.captures(attributes_section) {
305 if let Some(salience_match) = captures.get(1) {
306 return salience_match.as_str().parse::<i32>().map_err(|e| {
307 RuleEngineError::ParseError {
308 message: format!("Invalid salience value: {}", e),
309 }
310 });
311 }
312 }
313
314 Ok(0) }
316
317 fn clean_text(&self, text: &str) -> String {
318 text.lines()
319 .map(|line| line.trim())
320 .filter(|line| !line.is_empty() && !line.starts_with("//"))
321 .collect::<Vec<_>>()
322 .join(" ")
323 }
324
325 fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
326 let trimmed = when_clause.trim();
328
329 let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
331 let inner = &trimmed[1..trimmed.len() - 1];
333 if self.is_balanced_parentheses(inner) {
334 inner
335 } else {
336 trimmed
337 }
338 } else {
339 trimmed
340 };
341
342 if let Some(parts) = self.split_logical_operator(clause, "||") {
344 return self.parse_or_parts(parts);
345 }
346
347 if let Some(parts) = self.split_logical_operator(clause, "&&") {
349 return self.parse_and_parts(parts);
350 }
351
352 if clause.trim_start().starts_with("!") {
354 return self.parse_not_condition(clause);
355 }
356
357 if clause.trim_start().starts_with("exists(") {
359 return self.parse_exists_condition(clause);
360 }
361
362 if clause.trim_start().starts_with("forall(") {
364 return self.parse_forall_condition(clause);
365 }
366
367 if clause.trim_start().starts_with("accumulate(") {
369 return self.parse_accumulate_condition(clause);
370 }
371
372 self.parse_single_condition(clause)
374 }
375
376 fn is_balanced_parentheses(&self, text: &str) -> bool {
377 let mut count = 0;
378 for ch in text.chars() {
379 match ch {
380 '(' => count += 1,
381 ')' => {
382 count -= 1;
383 if count < 0 {
384 return false;
385 }
386 }
387 _ => {}
388 }
389 }
390 count == 0
391 }
392
393 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
394 let mut parts = Vec::new();
395 let mut current_part = String::new();
396 let mut paren_count = 0;
397 let mut chars = clause.chars().peekable();
398
399 while let Some(ch) = chars.next() {
400 match ch {
401 '(' => {
402 paren_count += 1;
403 current_part.push(ch);
404 }
405 ')' => {
406 paren_count -= 1;
407 current_part.push(ch);
408 }
409 '&' if operator == "&&" && paren_count == 0 => {
410 if chars.peek() == Some(&'&') {
411 chars.next(); parts.push(current_part.trim().to_string());
413 current_part.clear();
414 } else {
415 current_part.push(ch);
416 }
417 }
418 '|' if operator == "||" && paren_count == 0 => {
419 if chars.peek() == Some(&'|') {
420 chars.next(); parts.push(current_part.trim().to_string());
422 current_part.clear();
423 } else {
424 current_part.push(ch);
425 }
426 }
427 _ => {
428 current_part.push(ch);
429 }
430 }
431 }
432
433 if !current_part.trim().is_empty() {
434 parts.push(current_part.trim().to_string());
435 }
436
437 if parts.len() > 1 {
438 Some(parts)
439 } else {
440 None
441 }
442 }
443
444 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
445 let mut conditions = Vec::new();
446 for part in parts {
447 let condition = self.parse_when_clause(&part)?;
448 conditions.push(condition);
449 }
450
451 if conditions.is_empty() {
452 return Err(RuleEngineError::ParseError {
453 message: "No conditions found in OR".to_string(),
454 });
455 }
456
457 let mut iter = conditions.into_iter();
458 let mut result = iter.next().unwrap();
459 for condition in iter {
460 result = ConditionGroup::or(result, condition);
461 }
462
463 Ok(result)
464 }
465
466 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
467 let mut conditions = Vec::new();
468 for part in parts {
469 let condition = self.parse_when_clause(&part)?;
470 conditions.push(condition);
471 }
472
473 if conditions.is_empty() {
474 return Err(RuleEngineError::ParseError {
475 message: "No conditions found in AND".to_string(),
476 });
477 }
478
479 let mut iter = conditions.into_iter();
480 let mut result = iter.next().unwrap();
481 for condition in iter {
482 result = ConditionGroup::and(result, condition);
483 }
484
485 Ok(result)
486 }
487
488 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
489 let inner_clause = clause.strip_prefix("!").unwrap().trim();
490 let inner_condition = self.parse_when_clause(inner_clause)?;
491 Ok(ConditionGroup::not(inner_condition))
492 }
493
494 fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
495 let clause = clause.trim_start();
496 if !clause.starts_with("exists(") || !clause.ends_with(")") {
497 return Err(RuleEngineError::ParseError {
498 message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
499 });
500 }
501
502 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
505 Ok(ConditionGroup::exists(inner_condition))
506 }
507
508 fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
509 let clause = clause.trim_start();
510 if !clause.starts_with("forall(") || !clause.ends_with(")") {
511 return Err(RuleEngineError::ParseError {
512 message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
513 });
514 }
515
516 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
519 Ok(ConditionGroup::forall(inner_condition))
520 }
521
522 fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
523 let clause = clause.trim_start();
524 if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
525 return Err(RuleEngineError::ParseError {
526 message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)".to_string(),
527 });
528 }
529
530 let inner = &clause[11..clause.len() - 1]; let parts = self.split_accumulate_parts(inner)?;
535
536 if parts.len() != 2 {
537 return Err(RuleEngineError::ParseError {
538 message: format!(
539 "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
540 parts.len()
541 ),
542 });
543 }
544
545 let pattern_part = parts[0].trim();
546 let function_part = parts[1].trim();
547
548 let (source_pattern, extract_field, source_conditions) =
550 self.parse_accumulate_pattern(pattern_part)?;
551
552 let (function, function_arg) = self.parse_accumulate_function(function_part)?;
554
555 let result_var = "$result".to_string();
559
560 Ok(ConditionGroup::accumulate(
561 result_var,
562 source_pattern,
563 extract_field,
564 source_conditions,
565 function,
566 function_arg,
567 ))
568 }
569
570 fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
571 let mut parts = Vec::new();
572 let mut current = String::new();
573 let mut paren_depth = 0;
574
575 for ch in content.chars() {
576 match ch {
577 '(' => {
578 paren_depth += 1;
579 current.push(ch);
580 }
581 ')' => {
582 paren_depth -= 1;
583 current.push(ch);
584 }
585 ',' if paren_depth == 0 => {
586 parts.push(current.trim().to_string());
587 current.clear();
588 }
589 _ => {
590 current.push(ch);
591 }
592 }
593 }
594
595 if !current.trim().is_empty() {
596 parts.push(current.trim().to_string());
597 }
598
599 Ok(parts)
600 }
601
602 fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
603 let pattern = pattern.trim();
610
611 let paren_pos = pattern.find('(').ok_or_else(|| RuleEngineError::ParseError {
613 message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
614 })?;
615
616 let source_pattern = pattern[..paren_pos].trim().to_string();
617
618 if !pattern.ends_with(')') {
620 return Err(RuleEngineError::ParseError {
621 message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
622 });
623 }
624
625 let inner = &pattern[paren_pos + 1..pattern.len() - 1];
626
627 let parts = self.split_pattern_parts(inner)?;
629
630 let mut extract_field = String::new();
631 let mut source_conditions = Vec::new();
632
633 for part in parts {
634 let part = part.trim();
635
636 if part.contains(':') && part.starts_with('$') {
638 let colon_pos = part.find(':').unwrap();
639 let _var_name = part[..colon_pos].trim();
640 let field_name = part[colon_pos + 1..].trim();
641 extract_field = field_name.to_string();
642 } else if part.contains("==") || part.contains("!=") ||
643 part.contains(">=") || part.contains("<=") ||
644 part.contains('>') || part.contains('<') {
645 source_conditions.push(part.to_string());
647 }
648 }
649
650 Ok((source_pattern, extract_field, source_conditions))
651 }
652
653 fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
654 let mut parts = Vec::new();
655 let mut current = String::new();
656 let mut paren_depth = 0;
657 let mut in_quotes = false;
658 let mut quote_char = ' ';
659
660 for ch in content.chars() {
661 match ch {
662 '"' | '\'' if !in_quotes => {
663 in_quotes = true;
664 quote_char = ch;
665 current.push(ch);
666 }
667 '"' | '\'' if in_quotes && ch == quote_char => {
668 in_quotes = false;
669 current.push(ch);
670 }
671 '(' if !in_quotes => {
672 paren_depth += 1;
673 current.push(ch);
674 }
675 ')' if !in_quotes => {
676 paren_depth -= 1;
677 current.push(ch);
678 }
679 ',' if !in_quotes && paren_depth == 0 => {
680 parts.push(current.trim().to_string());
681 current.clear();
682 }
683 _ => {
684 current.push(ch);
685 }
686 }
687 }
688
689 if !current.trim().is_empty() {
690 parts.push(current.trim().to_string());
691 }
692
693 Ok(parts)
694 }
695
696 fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
697 let function_str = function_str.trim();
700
701 let paren_pos = function_str.find('(').ok_or_else(|| RuleEngineError::ParseError {
702 message: format!("Invalid accumulate function: missing '(' in '{}'", function_str),
703 })?;
704
705 let function_name = function_str[..paren_pos].trim().to_string();
706
707 if !function_str.ends_with(')') {
708 return Err(RuleEngineError::ParseError {
709 message: format!("Invalid accumulate function: missing ')' in '{}'", function_str),
710 });
711 }
712
713 let args = &function_str[paren_pos + 1..function_str.len() - 1];
714 let function_arg = args.trim().to_string();
715
716 Ok((function_name, function_arg))
717 }
718
719 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
720 let trimmed_clause = clause.trim();
722 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
723 trimmed_clause[1..trimmed_clause.len() - 1].trim()
724 } else {
725 trimmed_clause
726 };
727
728 if let Some(captures) = MULTIFIELD_COLLECT_REGEX.captures(clause_to_parse) {
735 let field = captures.get(1).unwrap().as_str().to_string();
736 let variable = captures.get(2).unwrap().as_str().to_string();
737
738 let condition = Condition::with_multifield_collect(field, variable);
741 return Ok(ConditionGroup::single(condition));
742 }
743
744 if let Some(captures) = MULTIFIELD_COUNT_REGEX.captures(clause_to_parse) {
751 let field = captures.get(1).unwrap().as_str().to_string();
752 let operator_str = captures.get(2).unwrap().as_str();
753 let value_str = captures.get(3).unwrap().as_str().trim();
754
755 let operator = Operator::from_str(operator_str)
756 .ok_or_else(|| RuleEngineError::InvalidOperator {
757 operator: operator_str.to_string(),
758 })?;
759
760 let value = self.parse_value(value_str)?;
761
762 let condition = Condition::with_multifield_count(field, operator, value);
763 return Ok(ConditionGroup::single(condition));
764 }
765
766 if let Some(captures) = MULTIFIELD_FIRST_REGEX.captures(clause_to_parse) {
769 let field = captures.get(1).unwrap().as_str().to_string();
770 let variable = captures.get(2).map(|m| m.as_str().to_string());
771
772 let condition = Condition::with_multifield_first(field, variable);
773 return Ok(ConditionGroup::single(condition));
774 }
775
776 if let Some(captures) = MULTIFIELD_LAST_REGEX.captures(clause_to_parse) {
779 let field = captures.get(1).unwrap().as_str().to_string();
780 let variable = captures.get(2).map(|m| m.as_str().to_string());
781
782 let condition = Condition::with_multifield_last(field, variable);
783 return Ok(ConditionGroup::single(condition));
784 }
785
786 if let Some(captures) = MULTIFIELD_EMPTY_REGEX.captures(clause_to_parse) {
789 let field = captures.get(1).unwrap().as_str().to_string();
790
791 let condition = Condition::with_multifield_empty(field);
792 return Ok(ConditionGroup::single(condition));
793 }
794
795 if let Some(captures) = MULTIFIELD_NOT_EMPTY_REGEX.captures(clause_to_parse) {
798 let field = captures.get(1).unwrap().as_str().to_string();
799
800 let condition = Condition::with_multifield_not_empty(field);
801 return Ok(ConditionGroup::single(condition));
802 }
803
804 if let Some(captures) = TEST_CONDITION_REGEX.captures(clause_to_parse) {
809 let function_name = captures.get(1).unwrap().as_str().to_string();
810 let args_str = captures.get(2).unwrap().as_str();
811
812 let args: Vec<String> = if args_str.trim().is_empty() {
814 Vec::new()
815 } else {
816 args_str
817 .split(',')
818 .map(|arg| arg.trim().to_string())
819 .collect()
820 };
821
822 let condition = Condition::with_test(function_name, args);
823 return Ok(ConditionGroup::single(condition));
824 }
825
826 if let Some(captures) = TYPED_TEST_CONDITION_REGEX.captures(clause_to_parse) {
828 let _object_name = captures.get(1).unwrap().as_str();
829 let _object_type = captures.get(2).unwrap().as_str();
830 let conditions_str = captures.get(3).unwrap().as_str();
831
832 return self.parse_conditions_within_object(conditions_str);
834 }
835
836 if let Some(captures) = FUNCTION_CALL_REGEX.captures(clause_to_parse) {
838 let function_name = captures.get(1).unwrap().as_str().to_string();
839 let args_str = captures.get(2).unwrap().as_str();
840 let operator_str = captures.get(3).unwrap().as_str();
841 let value_str = captures.get(4).unwrap().as_str().trim();
842
843 let args: Vec<String> = if args_str.trim().is_empty() {
845 Vec::new()
846 } else {
847 args_str
848 .split(',')
849 .map(|arg| arg.trim().to_string())
850 .collect()
851 };
852
853 let operator =
854 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
855 operator: operator_str.to_string(),
856 })?;
857
858 let value = self.parse_value(value_str)?;
859
860 let condition = Condition::with_function(function_name, args, operator, value);
861 return Ok(ConditionGroup::single(condition));
862 }
863
864 let captures = CONDITION_REGEX.captures(clause_to_parse).ok_or_else(|| {
868 RuleEngineError::ParseError {
869 message: format!("Invalid condition format: {}", clause_to_parse),
870 }
871 })?;
872
873 let left_side = captures.get(1).unwrap().as_str().trim().to_string();
874 let operator_str = captures.get(2).unwrap().as_str();
875 let value_str = captures.get(3).unwrap().as_str().trim();
876
877 let operator =
878 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
879 operator: operator_str.to_string(),
880 })?;
881
882 let value = self.parse_value(value_str)?;
883
884 if left_side.contains('+') || left_side.contains('-') || left_side.contains('*')
886 || left_side.contains('/') || left_side.contains('%') {
887 let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
890 let condition = Condition::with_test(test_expr, vec![]);
891 Ok(ConditionGroup::single(condition))
892 } else {
893 let condition = Condition::new(left_side, operator, value);
895 Ok(ConditionGroup::single(condition))
896 }
897 }
898
899 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
900 let parts: Vec<&str> = conditions_str.split("&&").collect();
902
903 let mut conditions = Vec::new();
904 for part in parts {
905 let trimmed = part.trim();
906 let condition = self.parse_simple_condition(trimmed)?;
907 conditions.push(condition);
908 }
909
910 if conditions.is_empty() {
912 return Err(RuleEngineError::ParseError {
913 message: "No conditions found".to_string(),
914 });
915 }
916
917 let mut iter = conditions.into_iter();
918 let mut result = iter.next().unwrap();
919 for condition in iter {
920 result = ConditionGroup::and(result, condition);
921 }
922
923 Ok(result)
924 }
925
926 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
927 let captures =
929 SIMPLE_CONDITION_REGEX
930 .captures(clause)
931 .ok_or_else(|| RuleEngineError::ParseError {
932 message: format!("Invalid simple condition format: {}", clause),
933 })?;
934
935 let field = captures.get(1).unwrap().as_str().to_string();
936 let operator_str = captures.get(2).unwrap().as_str();
937 let value_str = captures.get(3).unwrap().as_str().trim();
938
939 let operator =
940 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
941 operator: operator_str.to_string(),
942 })?;
943
944 let value = self.parse_value(value_str)?;
945
946 let condition = Condition::new(field, operator, value);
947 Ok(ConditionGroup::single(condition))
948 }
949
950 fn parse_value(&self, value_str: &str) -> Result<Value> {
951 let trimmed = value_str.trim();
952
953 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
955 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
956 {
957 let unquoted = &trimmed[1..trimmed.len() - 1];
958 return Ok(Value::String(unquoted.to_string()));
959 }
960
961 if trimmed.eq_ignore_ascii_case("true") {
963 return Ok(Value::Boolean(true));
964 }
965 if trimmed.eq_ignore_ascii_case("false") {
966 return Ok(Value::Boolean(false));
967 }
968
969 if trimmed.eq_ignore_ascii_case("null") {
971 return Ok(Value::Null);
972 }
973
974 if let Ok(int_val) = trimmed.parse::<i64>() {
976 return Ok(Value::Integer(int_val));
977 }
978
979 if let Ok(float_val) = trimmed.parse::<f64>() {
980 return Ok(Value::Number(float_val));
981 }
982
983 if self.is_expression(trimmed) {
986 return Ok(Value::Expression(trimmed.to_string()));
987 }
988
989 if trimmed.contains('.') {
991 return Ok(Value::String(trimmed.to_string()));
992 }
993
994 if self.is_identifier(trimmed) {
998 return Ok(Value::Expression(trimmed.to_string()));
999 }
1000
1001 Ok(Value::String(trimmed.to_string()))
1003 }
1004
1005 fn is_identifier(&self, s: &str) -> bool {
1008 if s.is_empty() {
1009 return false;
1010 }
1011
1012 let first_char = s.chars().next().unwrap();
1014 if !first_char.is_alphabetic() && first_char != '_' {
1015 return false;
1016 }
1017
1018 s.chars().all(|c| c.is_alphanumeric() || c == '_')
1020 }
1021
1022 fn is_expression(&self, s: &str) -> bool {
1024 let has_operator = s.contains('+') || s.contains('-') || s.contains('*') || s.contains('/') || s.contains('%');
1026
1027 let has_field_ref = s.contains('.');
1029
1030 let has_spaces = s.contains(' ');
1032
1033 has_operator && (has_field_ref || has_spaces)
1035 }
1036
1037 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1038 let statements: Vec<&str> = then_clause
1039 .split(';')
1040 .map(|s| s.trim())
1041 .filter(|s| !s.is_empty())
1042 .collect();
1043
1044 let mut actions = Vec::new();
1045
1046 for statement in statements {
1047 let action = self.parse_action_statement(statement)?;
1048 actions.push(action);
1049 }
1050
1051 Ok(actions)
1052 }
1053
1054 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1055 let trimmed = statement.trim();
1056
1057 if let Some(captures) = METHOD_CALL_REGEX.captures(trimmed) {
1059 let object = captures.get(1).unwrap().as_str().to_string();
1060 let method = captures.get(2).unwrap().as_str().to_string();
1061 let args_str = captures.get(3).unwrap().as_str();
1062
1063 let args = if args_str.trim().is_empty() {
1064 Vec::new()
1065 } else {
1066 self.parse_method_args(args_str)?
1067 };
1068
1069 return Ok(ActionType::MethodCall {
1070 object,
1071 method,
1072 args,
1073 });
1074 }
1075
1076 if let Some(eq_pos) = trimmed.find('=') {
1078 let field = trimmed[..eq_pos].trim().to_string();
1079 let value_str = trimmed[eq_pos + 1..].trim();
1080 let value = self.parse_value(value_str)?;
1081
1082 return Ok(ActionType::Set { field, value });
1083 }
1084
1085 if let Some(captures) = FUNCTION_BINDING_REGEX.captures(trimmed) {
1087 let function_name = captures.get(1).unwrap().as_str();
1088 let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
1089
1090 match function_name.to_lowercase().as_str() {
1091 "retract" => {
1092 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1094 stripped.to_string()
1095 } else {
1096 args_str.to_string()
1097 };
1098 Ok(ActionType::Retract {
1099 object: object_name,
1100 })
1101 }
1102 "log" => {
1103 let message = if args_str.is_empty() {
1104 "Log message".to_string()
1105 } else {
1106 let value = self.parse_value(args_str.trim())?;
1107 value.to_string()
1108 };
1109 Ok(ActionType::Log { message })
1110 }
1111 "activateagendagroup" | "activate_agenda_group" => {
1112 let agenda_group = if args_str.is_empty() {
1113 return Err(RuleEngineError::ParseError {
1114 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1115 });
1116 } else {
1117 let value = self.parse_value(args_str.trim())?;
1118 match value {
1119 Value::String(s) => s,
1120 _ => value.to_string(),
1121 }
1122 };
1123 Ok(ActionType::ActivateAgendaGroup {
1124 group: agenda_group,
1125 })
1126 }
1127 "schedulerule" | "schedule_rule" => {
1128 let parts: Vec<&str> = args_str.split(',').collect();
1130 if parts.len() != 2 {
1131 return Err(RuleEngineError::ParseError {
1132 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1133 });
1134 }
1135
1136 let delay_ms = self.parse_value(parts[0].trim())?;
1137 let rule_name = self.parse_value(parts[1].trim())?;
1138
1139 let delay_ms = match delay_ms {
1140 Value::Integer(i) => i as u64,
1141 Value::Number(f) => f as u64,
1142 _ => {
1143 return Err(RuleEngineError::ParseError {
1144 message: "ScheduleRule delay_ms must be a number".to_string(),
1145 })
1146 }
1147 };
1148
1149 let rule_name = match rule_name {
1150 Value::String(s) => s,
1151 _ => rule_name.to_string(),
1152 };
1153
1154 Ok(ActionType::ScheduleRule {
1155 delay_ms,
1156 rule_name,
1157 })
1158 }
1159 "completeworkflow" | "complete_workflow" => {
1160 let workflow_id = if args_str.is_empty() {
1161 return Err(RuleEngineError::ParseError {
1162 message: "CompleteWorkflow requires workflow_id".to_string(),
1163 });
1164 } else {
1165 let value = self.parse_value(args_str.trim())?;
1166 match value {
1167 Value::String(s) => s,
1168 _ => value.to_string(),
1169 }
1170 };
1171 Ok(ActionType::CompleteWorkflow {
1172 workflow_name: workflow_id,
1173 })
1174 }
1175 "setworkflowdata" | "set_workflow_data" => {
1176 let data_str = args_str.trim();
1178
1179 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1181 let key = data_str[..eq_pos].trim().trim_matches('"');
1182 let value_str = data_str[eq_pos + 1..].trim();
1183 let value = self.parse_value(value_str)?;
1184 (key.to_string(), value)
1185 } else {
1186 return Err(RuleEngineError::ParseError {
1187 message: "SetWorkflowData data must be in key=value format".to_string(),
1188 });
1189 };
1190
1191 Ok(ActionType::SetWorkflowData { key, value })
1192 }
1193 _ => {
1194 let params = if args_str.is_empty() {
1196 HashMap::new()
1197 } else {
1198 self.parse_function_args_as_params(args_str)?
1199 };
1200
1201 Ok(ActionType::Custom {
1202 action_type: function_name.to_string(),
1203 params,
1204 })
1205 }
1206 }
1207 } else {
1208 Ok(ActionType::Custom {
1210 action_type: "statement".to_string(),
1211 params: {
1212 let mut params = HashMap::new();
1213 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1214 params
1215 },
1216 })
1217 }
1218 }
1219
1220 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1221 if args_str.trim().is_empty() {
1222 return Ok(Vec::new());
1223 }
1224
1225 let mut args = Vec::new();
1227 let parts: Vec<&str> = args_str.split(',').collect();
1228
1229 for part in parts {
1230 let trimmed = part.trim();
1231
1232 if trimmed.contains('+')
1234 || trimmed.contains('-')
1235 || trimmed.contains('*')
1236 || trimmed.contains('/')
1237 {
1238 args.push(Value::String(trimmed.to_string()));
1240 } else {
1241 args.push(self.parse_value(trimmed)?);
1242 }
1243 }
1244
1245 Ok(args)
1246 }
1247
1248 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1250 let mut params = HashMap::new();
1251
1252 if args_str.trim().is_empty() {
1253 return Ok(params);
1254 }
1255
1256 let parts: Vec<&str> = args_str.split(',').collect();
1258 for (i, part) in parts.iter().enumerate() {
1259 let trimmed = part.trim();
1260 let value = self.parse_value(trimmed)?;
1261
1262 params.insert(i.to_string(), value);
1264 }
1265
1266 Ok(params)
1267 }
1268}
1269
1270#[cfg(test)]
1271mod tests {
1272 use super::GRLParser;
1273
1274 #[test]
1275 fn test_parse_simple_rule() {
1276 let grl = r#"
1277 rule "CheckAge" salience 10 {
1278 when
1279 User.Age >= 18
1280 then
1281 log("User is adult");
1282 }
1283 "#;
1284
1285 let rules = GRLParser::parse_rules(grl).unwrap();
1286 assert_eq!(rules.len(), 1);
1287 let rule = &rules[0];
1288 assert_eq!(rule.name, "CheckAge");
1289 assert_eq!(rule.salience, 10);
1290 assert_eq!(rule.actions.len(), 1);
1291 }
1292
1293 #[test]
1294 fn test_parse_complex_condition() {
1295 let grl = r#"
1296 rule "ComplexRule" {
1297 when
1298 User.Age >= 18 && User.Country == "US"
1299 then
1300 User.Qualified = true;
1301 }
1302 "#;
1303
1304 let rules = GRLParser::parse_rules(grl).unwrap();
1305 assert_eq!(rules.len(), 1);
1306 let rule = &rules[0];
1307 assert_eq!(rule.name, "ComplexRule");
1308 }
1309
1310 #[test]
1311 fn test_parse_new_syntax_with_parentheses() {
1312 let grl = r#"
1313 rule "Default Rule" salience 10 {
1314 when
1315 (user.age >= 18)
1316 then
1317 set(user.status, "approved");
1318 }
1319 "#;
1320
1321 let rules = GRLParser::parse_rules(grl).unwrap();
1322 assert_eq!(rules.len(), 1);
1323 let rule = &rules[0];
1324 assert_eq!(rule.name, "Default Rule");
1325 assert_eq!(rule.salience, 10);
1326 assert_eq!(rule.actions.len(), 1);
1327
1328 match &rule.actions[0] {
1330 crate::types::ActionType::Custom {
1331 action_type,
1332 params,
1333 } => {
1334 assert_eq!(action_type, "set");
1335 assert_eq!(
1336 params.get("0"),
1337 Some(&crate::types::Value::String("user.status".to_string()))
1338 );
1339 assert_eq!(
1340 params.get("1"),
1341 Some(&crate::types::Value::String("approved".to_string()))
1342 );
1343 }
1344 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1345 }
1346 }
1347
1348 #[test]
1349 fn test_parse_complex_nested_conditions() {
1350 let grl = r#"
1351 rule "Complex Business Rule" salience 10 {
1352 when
1353 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1354 then
1355 apply_discount(20000);
1356 }
1357 "#;
1358
1359 let rules = GRLParser::parse_rules(grl).unwrap();
1360 assert_eq!(rules.len(), 1);
1361 let rule = &rules[0];
1362 assert_eq!(rule.name, "Complex Business Rule");
1363 assert_eq!(rule.salience, 10);
1364 assert_eq!(rule.actions.len(), 1);
1365
1366 match &rule.actions[0] {
1368 crate::types::ActionType::Custom {
1369 action_type,
1370 params,
1371 } => {
1372 assert_eq!(action_type, "apply_discount");
1373 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1374 }
1375 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1376 }
1377 }
1378
1379 #[test]
1380 fn test_parse_no_loop_attribute() {
1381 let grl = r#"
1382 rule "NoLoopRule" no-loop salience 15 {
1383 when
1384 User.Score < 100
1385 then
1386 set(User.Score, User.Score + 10);
1387 }
1388 "#;
1389
1390 let rules = GRLParser::parse_rules(grl).unwrap();
1391 assert_eq!(rules.len(), 1);
1392 let rule = &rules[0];
1393 assert_eq!(rule.name, "NoLoopRule");
1394 assert_eq!(rule.salience, 15);
1395 assert!(rule.no_loop, "Rule should have no-loop=true");
1396 }
1397
1398 #[test]
1399 fn test_parse_no_loop_different_positions() {
1400 let grl1 = r#"
1402 rule "Rule1" no-loop salience 10 {
1403 when User.Age >= 18
1404 then log("adult");
1405 }
1406 "#;
1407
1408 let grl2 = r#"
1410 rule "Rule2" salience 10 no-loop {
1411 when User.Age >= 18
1412 then log("adult");
1413 }
1414 "#;
1415
1416 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1417 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1418
1419 assert_eq!(rules1.len(), 1);
1420 assert_eq!(rules2.len(), 1);
1421
1422 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1423 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1424
1425 assert_eq!(rules1[0].salience, 10);
1426 assert_eq!(rules2[0].salience, 10);
1427 }
1428
1429 #[test]
1430 fn test_parse_without_no_loop() {
1431 let grl = r#"
1432 rule "RegularRule" salience 5 {
1433 when
1434 User.Active == true
1435 then
1436 log("active user");
1437 }
1438 "#;
1439
1440 let rules = GRLParser::parse_rules(grl).unwrap();
1441 assert_eq!(rules.len(), 1);
1442 let rule = &rules[0];
1443 assert_eq!(rule.name, "RegularRule");
1444 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1445 }
1446
1447 #[test]
1448 fn test_parse_exists_pattern() {
1449 let grl = r#"
1450 rule "ExistsRule" salience 20 {
1451 when
1452 exists(Customer.tier == "VIP")
1453 then
1454 System.premiumActive = true;
1455 }
1456 "#;
1457
1458 let rules = GRLParser::parse_rules(grl).unwrap();
1459 assert_eq!(rules.len(), 1);
1460 let rule = &rules[0];
1461 assert_eq!(rule.name, "ExistsRule");
1462 assert_eq!(rule.salience, 20);
1463
1464 match &rule.conditions {
1466 crate::engine::rule::ConditionGroup::Exists(_) => {
1467 }
1469 _ => panic!(
1470 "Expected EXISTS condition group, got: {:?}",
1471 rule.conditions
1472 ),
1473 }
1474 }
1475
1476 #[test]
1477 fn test_parse_forall_pattern() {
1478 let grl = r#"
1479 rule "ForallRule" salience 15 {
1480 when
1481 forall(Order.status == "processed")
1482 then
1483 Shipping.enabled = true;
1484 }
1485 "#;
1486
1487 let rules = GRLParser::parse_rules(grl).unwrap();
1488 assert_eq!(rules.len(), 1);
1489 let rule = &rules[0];
1490 assert_eq!(rule.name, "ForallRule");
1491
1492 match &rule.conditions {
1494 crate::engine::rule::ConditionGroup::Forall(_) => {
1495 }
1497 _ => panic!(
1498 "Expected FORALL condition group, got: {:?}",
1499 rule.conditions
1500 ),
1501 }
1502 }
1503
1504 #[test]
1505 fn test_parse_combined_patterns() {
1506 let grl = r#"
1507 rule "CombinedRule" salience 25 {
1508 when
1509 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1510 then
1511 System.vipMode = true;
1512 }
1513 "#;
1514
1515 let rules = GRLParser::parse_rules(grl).unwrap();
1516 assert_eq!(rules.len(), 1);
1517 let rule = &rules[0];
1518 assert_eq!(rule.name, "CombinedRule");
1519
1520 match &rule.conditions {
1522 crate::engine::rule::ConditionGroup::Compound {
1523 left,
1524 operator,
1525 right,
1526 } => {
1527 assert_eq!(*operator, crate::types::LogicalOperator::And);
1528
1529 match left.as_ref() {
1531 crate::engine::rule::ConditionGroup::Exists(_) => {
1532 }
1534 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1535 }
1536
1537 match right.as_ref() {
1539 crate::engine::rule::ConditionGroup::Not(inner) => {
1540 match inner.as_ref() {
1541 crate::engine::rule::ConditionGroup::Exists(_) => {
1542 }
1544 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1545 }
1546 }
1547 _ => panic!("Expected NOT in right side, got: {:?}", right),
1548 }
1549 }
1550 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1551 }
1552 }
1553}