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 "update" => {
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::Update {
1099 object: object_name,
1100 })
1101 }
1102 "retract" => {
1103 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1105 stripped.to_string()
1106 } else {
1107 args_str.to_string()
1108 };
1109 Ok(ActionType::Retract {
1110 object: object_name,
1111 })
1112 }
1113 "log" => {
1114 let message = if args_str.is_empty() {
1115 "Log message".to_string()
1116 } else {
1117 let value = self.parse_value(args_str.trim())?;
1118 value.to_string()
1119 };
1120 Ok(ActionType::Log { message })
1121 }
1122 "activateagendagroup" | "activate_agenda_group" => {
1123 let agenda_group = if args_str.is_empty() {
1124 return Err(RuleEngineError::ParseError {
1125 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1126 });
1127 } else {
1128 let value = self.parse_value(args_str.trim())?;
1129 match value {
1130 Value::String(s) => s,
1131 _ => value.to_string(),
1132 }
1133 };
1134 Ok(ActionType::ActivateAgendaGroup {
1135 group: agenda_group,
1136 })
1137 }
1138 "schedulerule" | "schedule_rule" => {
1139 let parts: Vec<&str> = args_str.split(',').collect();
1141 if parts.len() != 2 {
1142 return Err(RuleEngineError::ParseError {
1143 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1144 });
1145 }
1146
1147 let delay_ms = self.parse_value(parts[0].trim())?;
1148 let rule_name = self.parse_value(parts[1].trim())?;
1149
1150 let delay_ms = match delay_ms {
1151 Value::Integer(i) => i as u64,
1152 Value::Number(f) => f as u64,
1153 _ => {
1154 return Err(RuleEngineError::ParseError {
1155 message: "ScheduleRule delay_ms must be a number".to_string(),
1156 })
1157 }
1158 };
1159
1160 let rule_name = match rule_name {
1161 Value::String(s) => s,
1162 _ => rule_name.to_string(),
1163 };
1164
1165 Ok(ActionType::ScheduleRule {
1166 delay_ms,
1167 rule_name,
1168 })
1169 }
1170 "completeworkflow" | "complete_workflow" => {
1171 let workflow_id = if args_str.is_empty() {
1172 return Err(RuleEngineError::ParseError {
1173 message: "CompleteWorkflow requires workflow_id".to_string(),
1174 });
1175 } else {
1176 let value = self.parse_value(args_str.trim())?;
1177 match value {
1178 Value::String(s) => s,
1179 _ => value.to_string(),
1180 }
1181 };
1182 Ok(ActionType::CompleteWorkflow {
1183 workflow_name: workflow_id,
1184 })
1185 }
1186 "setworkflowdata" | "set_workflow_data" => {
1187 let data_str = args_str.trim();
1189
1190 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1192 let key = data_str[..eq_pos].trim().trim_matches('"');
1193 let value_str = data_str[eq_pos + 1..].trim();
1194 let value = self.parse_value(value_str)?;
1195 (key.to_string(), value)
1196 } else {
1197 return Err(RuleEngineError::ParseError {
1198 message: "SetWorkflowData data must be in key=value format".to_string(),
1199 });
1200 };
1201
1202 Ok(ActionType::SetWorkflowData { key, value })
1203 }
1204 _ => {
1205 let params = if args_str.is_empty() {
1207 HashMap::new()
1208 } else {
1209 self.parse_function_args_as_params(args_str)?
1210 };
1211
1212 Ok(ActionType::Custom {
1213 action_type: function_name.to_string(),
1214 params,
1215 })
1216 }
1217 }
1218 } else {
1219 Ok(ActionType::Custom {
1221 action_type: "statement".to_string(),
1222 params: {
1223 let mut params = HashMap::new();
1224 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1225 params
1226 },
1227 })
1228 }
1229 }
1230
1231 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1232 if args_str.trim().is_empty() {
1233 return Ok(Vec::new());
1234 }
1235
1236 let mut args = Vec::new();
1238 let parts: Vec<&str> = args_str.split(',').collect();
1239
1240 for part in parts {
1241 let trimmed = part.trim();
1242
1243 if trimmed.contains('+')
1245 || trimmed.contains('-')
1246 || trimmed.contains('*')
1247 || trimmed.contains('/')
1248 {
1249 args.push(Value::String(trimmed.to_string()));
1251 } else {
1252 args.push(self.parse_value(trimmed)?);
1253 }
1254 }
1255
1256 Ok(args)
1257 }
1258
1259 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1261 let mut params = HashMap::new();
1262
1263 if args_str.trim().is_empty() {
1264 return Ok(params);
1265 }
1266
1267 let parts: Vec<&str> = args_str.split(',').collect();
1269 for (i, part) in parts.iter().enumerate() {
1270 let trimmed = part.trim();
1271 let value = self.parse_value(trimmed)?;
1272
1273 params.insert(i.to_string(), value);
1275 }
1276
1277 Ok(params)
1278 }
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283 use super::GRLParser;
1284
1285 #[test]
1286 fn test_parse_simple_rule() {
1287 let grl = r#"
1288 rule "CheckAge" salience 10 {
1289 when
1290 User.Age >= 18
1291 then
1292 log("User is adult");
1293 }
1294 "#;
1295
1296 let rules = GRLParser::parse_rules(grl).unwrap();
1297 assert_eq!(rules.len(), 1);
1298 let rule = &rules[0];
1299 assert_eq!(rule.name, "CheckAge");
1300 assert_eq!(rule.salience, 10);
1301 assert_eq!(rule.actions.len(), 1);
1302 }
1303
1304 #[test]
1305 fn test_parse_complex_condition() {
1306 let grl = r#"
1307 rule "ComplexRule" {
1308 when
1309 User.Age >= 18 && User.Country == "US"
1310 then
1311 User.Qualified = true;
1312 }
1313 "#;
1314
1315 let rules = GRLParser::parse_rules(grl).unwrap();
1316 assert_eq!(rules.len(), 1);
1317 let rule = &rules[0];
1318 assert_eq!(rule.name, "ComplexRule");
1319 }
1320
1321 #[test]
1322 fn test_parse_new_syntax_with_parentheses() {
1323 let grl = r#"
1324 rule "Default Rule" salience 10 {
1325 when
1326 (user.age >= 18)
1327 then
1328 set(user.status, "approved");
1329 }
1330 "#;
1331
1332 let rules = GRLParser::parse_rules(grl).unwrap();
1333 assert_eq!(rules.len(), 1);
1334 let rule = &rules[0];
1335 assert_eq!(rule.name, "Default Rule");
1336 assert_eq!(rule.salience, 10);
1337 assert_eq!(rule.actions.len(), 1);
1338
1339 match &rule.actions[0] {
1341 crate::types::ActionType::Custom {
1342 action_type,
1343 params,
1344 } => {
1345 assert_eq!(action_type, "set");
1346 assert_eq!(
1347 params.get("0"),
1348 Some(&crate::types::Value::String("user.status".to_string()))
1349 );
1350 assert_eq!(
1351 params.get("1"),
1352 Some(&crate::types::Value::String("approved".to_string()))
1353 );
1354 }
1355 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1356 }
1357 }
1358
1359 #[test]
1360 fn test_parse_complex_nested_conditions() {
1361 let grl = r#"
1362 rule "Complex Business Rule" salience 10 {
1363 when
1364 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1365 then
1366 apply_discount(20000);
1367 }
1368 "#;
1369
1370 let rules = GRLParser::parse_rules(grl).unwrap();
1371 assert_eq!(rules.len(), 1);
1372 let rule = &rules[0];
1373 assert_eq!(rule.name, "Complex Business Rule");
1374 assert_eq!(rule.salience, 10);
1375 assert_eq!(rule.actions.len(), 1);
1376
1377 match &rule.actions[0] {
1379 crate::types::ActionType::Custom {
1380 action_type,
1381 params,
1382 } => {
1383 assert_eq!(action_type, "apply_discount");
1384 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1385 }
1386 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1387 }
1388 }
1389
1390 #[test]
1391 fn test_parse_no_loop_attribute() {
1392 let grl = r#"
1393 rule "NoLoopRule" no-loop salience 15 {
1394 when
1395 User.Score < 100
1396 then
1397 set(User.Score, User.Score + 10);
1398 }
1399 "#;
1400
1401 let rules = GRLParser::parse_rules(grl).unwrap();
1402 assert_eq!(rules.len(), 1);
1403 let rule = &rules[0];
1404 assert_eq!(rule.name, "NoLoopRule");
1405 assert_eq!(rule.salience, 15);
1406 assert!(rule.no_loop, "Rule should have no-loop=true");
1407 }
1408
1409 #[test]
1410 fn test_parse_no_loop_different_positions() {
1411 let grl1 = r#"
1413 rule "Rule1" no-loop salience 10 {
1414 when User.Age >= 18
1415 then log("adult");
1416 }
1417 "#;
1418
1419 let grl2 = r#"
1421 rule "Rule2" salience 10 no-loop {
1422 when User.Age >= 18
1423 then log("adult");
1424 }
1425 "#;
1426
1427 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1428 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1429
1430 assert_eq!(rules1.len(), 1);
1431 assert_eq!(rules2.len(), 1);
1432
1433 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1434 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1435
1436 assert_eq!(rules1[0].salience, 10);
1437 assert_eq!(rules2[0].salience, 10);
1438 }
1439
1440 #[test]
1441 fn test_parse_without_no_loop() {
1442 let grl = r#"
1443 rule "RegularRule" salience 5 {
1444 when
1445 User.Active == true
1446 then
1447 log("active user");
1448 }
1449 "#;
1450
1451 let rules = GRLParser::parse_rules(grl).unwrap();
1452 assert_eq!(rules.len(), 1);
1453 let rule = &rules[0];
1454 assert_eq!(rule.name, "RegularRule");
1455 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1456 }
1457
1458 #[test]
1459 fn test_parse_exists_pattern() {
1460 let grl = r#"
1461 rule "ExistsRule" salience 20 {
1462 when
1463 exists(Customer.tier == "VIP")
1464 then
1465 System.premiumActive = true;
1466 }
1467 "#;
1468
1469 let rules = GRLParser::parse_rules(grl).unwrap();
1470 assert_eq!(rules.len(), 1);
1471 let rule = &rules[0];
1472 assert_eq!(rule.name, "ExistsRule");
1473 assert_eq!(rule.salience, 20);
1474
1475 match &rule.conditions {
1477 crate::engine::rule::ConditionGroup::Exists(_) => {
1478 }
1480 _ => panic!(
1481 "Expected EXISTS condition group, got: {:?}",
1482 rule.conditions
1483 ),
1484 }
1485 }
1486
1487 #[test]
1488 fn test_parse_forall_pattern() {
1489 let grl = r#"
1490 rule "ForallRule" salience 15 {
1491 when
1492 forall(Order.status == "processed")
1493 then
1494 Shipping.enabled = true;
1495 }
1496 "#;
1497
1498 let rules = GRLParser::parse_rules(grl).unwrap();
1499 assert_eq!(rules.len(), 1);
1500 let rule = &rules[0];
1501 assert_eq!(rule.name, "ForallRule");
1502
1503 match &rule.conditions {
1505 crate::engine::rule::ConditionGroup::Forall(_) => {
1506 }
1508 _ => panic!(
1509 "Expected FORALL condition group, got: {:?}",
1510 rule.conditions
1511 ),
1512 }
1513 }
1514
1515 #[test]
1516 fn test_parse_combined_patterns() {
1517 let grl = r#"
1518 rule "CombinedRule" salience 25 {
1519 when
1520 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1521 then
1522 System.vipMode = true;
1523 }
1524 "#;
1525
1526 let rules = GRLParser::parse_rules(grl).unwrap();
1527 assert_eq!(rules.len(), 1);
1528 let rule = &rules[0];
1529 assert_eq!(rule.name, "CombinedRule");
1530
1531 match &rule.conditions {
1533 crate::engine::rule::ConditionGroup::Compound {
1534 left,
1535 operator,
1536 right,
1537 } => {
1538 assert_eq!(*operator, crate::types::LogicalOperator::And);
1539
1540 match left.as_ref() {
1542 crate::engine::rule::ConditionGroup::Exists(_) => {
1543 }
1545 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1546 }
1547
1548 match right.as_ref() {
1550 crate::engine::rule::ConditionGroup::Not(inner) => {
1551 match inner.as_ref() {
1552 crate::engine::rule::ConditionGroup::Exists(_) => {
1553 }
1555 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1556 }
1557 }
1558 _ => panic!("Expected NOT in right side, got: {:?}", right),
1559 }
1560 }
1561 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1562 }
1563 }
1564}