1use crate::engine::rule::{Condition, ConditionGroup, Rule};
2use crate::errors::{Result, RuleEngineError};
3use crate::types::{ActionType, Operator, Value};
4use regex::Regex;
5use std::collections::HashMap;
6
7pub struct GRLParser;
10
11impl GRLParser {
12 pub fn parse_rule(grl_text: &str) -> Result<Rule> {
25 let mut parser = GRLParser;
26 parser.parse_single_rule(grl_text)
27 }
28
29 pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
31 let mut parser = GRLParser;
32 parser.parse_multiple_rules(grl_text)
33 }
34
35 fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
36 let cleaned = self.clean_text(grl_text);
37
38 let rule_regex = Regex::new(
40 r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*(?:no-loop)?\s*(?:salience\s+(\d+))?\s*(?:no-loop)?\s*\{(.+)\}"#
41 ).map_err(|e| RuleEngineError::ParseError {
42 message: format!("Invalid rule regex: {}", e),
43 })?;
44
45 let captures =
46 rule_regex
47 .captures(&cleaned)
48 .ok_or_else(|| RuleEngineError::ParseError {
49 message: format!("Invalid GRL rule format. Input: {}", cleaned),
50 })?;
51
52 let rule_name = if let Some(quoted_name) = captures.get(1) {
54 quoted_name.as_str().to_string()
55 } else if let Some(unquoted_name) = captures.get(2) {
56 unquoted_name.as_str().to_string()
57 } else {
58 return Err(RuleEngineError::ParseError {
59 message: "Could not extract rule name".to_string(),
60 });
61 };
62 let salience = captures
64 .get(3)
65 .and_then(|m| m.as_str().parse::<i32>().ok())
66 .unwrap_or(0);
67
68 let rule_body = captures.get(4).unwrap().as_str();
69
70 let when_then_regex =
72 Regex::new(r"when\s+(.+?)\s+then\s+(.+)").map_err(|e| RuleEngineError::ParseError {
73 message: format!("Invalid when-then regex: {}", e),
74 })?;
75
76 let when_then_captures =
77 when_then_regex
78 .captures(rule_body)
79 .ok_or_else(|| RuleEngineError::ParseError {
80 message: "Missing when or then clause".to_string(),
81 })?;
82
83 let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
84 let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
85
86 let rule_header = grl_text.split('{').next().unwrap_or("");
88 let has_no_loop = rule_header.contains("no-loop");
89
90 let conditions = self.parse_when_clause(when_clause)?;
92
93 let actions = self.parse_then_clause(then_clause)?;
95
96 let mut rule = Rule::new(rule_name, conditions, actions);
98 rule = rule.with_priority(salience);
99
100 if has_no_loop {
101 rule = rule.with_no_loop(true);
102 }
103
104 Ok(rule)
105 }
106
107 fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
108 let rule_regex =
111 Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#).map_err(|e| {
112 RuleEngineError::ParseError {
113 message: format!("Rule splitting regex error: {}", e),
114 }
115 })?;
116
117 let mut rules = Vec::new();
118
119 for rule_match in rule_regex.find_iter(grl_text) {
120 let rule_text = rule_match.as_str();
121 let rule = self.parse_single_rule(rule_text)?;
122 rules.push(rule);
123 }
124
125 Ok(rules)
126 }
127
128 fn clean_text(&self, text: &str) -> String {
129 text.lines()
130 .map(|line| line.trim())
131 .filter(|line| !line.is_empty() && !line.starts_with("//"))
132 .collect::<Vec<_>>()
133 .join(" ")
134 }
135
136 fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
137 let trimmed = when_clause.trim();
139
140 let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
142 let inner = &trimmed[1..trimmed.len() - 1];
144 if self.is_balanced_parentheses(inner) {
145 inner
146 } else {
147 trimmed
148 }
149 } else {
150 trimmed
151 };
152
153 if let Some(parts) = self.split_logical_operator(clause, "||") {
155 return self.parse_or_parts(parts);
156 }
157
158 if let Some(parts) = self.split_logical_operator(clause, "&&") {
160 return self.parse_and_parts(parts);
161 }
162
163 if clause.trim_start().starts_with("!") {
165 return self.parse_not_condition(clause);
166 }
167
168 self.parse_single_condition(clause)
170 }
171
172 fn is_balanced_parentheses(&self, text: &str) -> bool {
173 let mut count = 0;
174 for ch in text.chars() {
175 match ch {
176 '(' => count += 1,
177 ')' => {
178 count -= 1;
179 if count < 0 {
180 return false;
181 }
182 }
183 _ => {}
184 }
185 }
186 count == 0
187 }
188
189 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
190 let mut parts = Vec::new();
191 let mut current_part = String::new();
192 let mut paren_count = 0;
193 let mut chars = clause.chars().peekable();
194
195 while let Some(ch) = chars.next() {
196 match ch {
197 '(' => {
198 paren_count += 1;
199 current_part.push(ch);
200 }
201 ')' => {
202 paren_count -= 1;
203 current_part.push(ch);
204 }
205 '&' if operator == "&&" && paren_count == 0 => {
206 if chars.peek() == Some(&'&') {
207 chars.next(); parts.push(current_part.trim().to_string());
209 current_part.clear();
210 } else {
211 current_part.push(ch);
212 }
213 }
214 '|' if operator == "||" && paren_count == 0 => {
215 if chars.peek() == Some(&'|') {
216 chars.next(); parts.push(current_part.trim().to_string());
218 current_part.clear();
219 } else {
220 current_part.push(ch);
221 }
222 }
223 _ => {
224 current_part.push(ch);
225 }
226 }
227 }
228
229 if !current_part.trim().is_empty() {
230 parts.push(current_part.trim().to_string());
231 }
232
233 if parts.len() > 1 {
234 Some(parts)
235 } else {
236 None
237 }
238 }
239
240 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
241 let mut conditions = Vec::new();
242 for part in parts {
243 let condition = self.parse_when_clause(&part)?;
244 conditions.push(condition);
245 }
246
247 if conditions.is_empty() {
248 return Err(RuleEngineError::ParseError {
249 message: "No conditions found in OR".to_string(),
250 });
251 }
252
253 let mut iter = conditions.into_iter();
254 let mut result = iter.next().unwrap();
255 for condition in iter {
256 result = ConditionGroup::or(result, condition);
257 }
258
259 Ok(result)
260 }
261
262 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
263 let mut conditions = Vec::new();
264 for part in parts {
265 let condition = self.parse_when_clause(&part)?;
266 conditions.push(condition);
267 }
268
269 if conditions.is_empty() {
270 return Err(RuleEngineError::ParseError {
271 message: "No conditions found in AND".to_string(),
272 });
273 }
274
275 let mut iter = conditions.into_iter();
276 let mut result = iter.next().unwrap();
277 for condition in iter {
278 result = ConditionGroup::and(result, condition);
279 }
280
281 Ok(result)
282 }
283
284 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
285 let inner_clause = clause.strip_prefix("!").unwrap().trim();
286 let inner_condition = self.parse_when_clause(inner_clause)?;
287 Ok(ConditionGroup::not(inner_condition))
288 }
289
290 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
291 let trimmed_clause = clause.trim();
293 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
294 trimmed_clause[1..trimmed_clause.len() - 1].trim()
295 } else {
296 trimmed_clause
297 };
298
299 let typed_object_regex =
301 Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#).map_err(|e| {
302 RuleEngineError::ParseError {
303 message: format!("Typed object regex error: {}", e),
304 }
305 })?;
306
307 if let Some(captures) = typed_object_regex.captures(clause_to_parse) {
308 let _object_name = captures.get(1).unwrap().as_str();
309 let _object_type = captures.get(2).unwrap().as_str();
310 let conditions_str = captures.get(3).unwrap().as_str();
311
312 return self.parse_conditions_within_object(conditions_str);
314 }
315
316 let condition_regex = Regex::new(
319 r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
320 )
321 .map_err(|e| RuleEngineError::ParseError {
322 message: format!("Condition regex error: {}", e),
323 })?;
324
325 let captures = condition_regex.captures(clause_to_parse).ok_or_else(|| {
326 RuleEngineError::ParseError {
327 message: format!("Invalid condition format: {}", clause_to_parse),
328 }
329 })?;
330
331 let field = captures.get(1).unwrap().as_str().to_string();
332 let operator_str = captures.get(2).unwrap().as_str();
333 let value_str = captures.get(3).unwrap().as_str().trim();
334
335 let operator =
336 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
337 operator: operator_str.to_string(),
338 })?;
339
340 let value = self.parse_value(value_str)?;
341
342 let condition = Condition::new(field, operator, value);
343 Ok(ConditionGroup::single(condition))
344 }
345
346 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
347 let parts: Vec<&str> = conditions_str.split("&&").collect();
349
350 let mut conditions = Vec::new();
351 for part in parts {
352 let trimmed = part.trim();
353 let condition = self.parse_simple_condition(trimmed)?;
354 conditions.push(condition);
355 }
356
357 if conditions.is_empty() {
359 return Err(RuleEngineError::ParseError {
360 message: "No conditions found".to_string(),
361 });
362 }
363
364 let mut iter = conditions.into_iter();
365 let mut result = iter.next().unwrap();
366 for condition in iter {
367 result = ConditionGroup::and(result, condition);
368 }
369
370 Ok(result)
371 }
372
373 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
374 let condition_regex = Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).map_err(|e| {
376 RuleEngineError::ParseError {
377 message: format!("Simple condition regex error: {}", e),
378 }
379 })?;
380
381 let captures =
382 condition_regex
383 .captures(clause)
384 .ok_or_else(|| RuleEngineError::ParseError {
385 message: format!("Invalid simple condition format: {}", clause),
386 })?;
387
388 let field = captures.get(1).unwrap().as_str().to_string();
389 let operator_str = captures.get(2).unwrap().as_str();
390 let value_str = captures.get(3).unwrap().as_str().trim();
391
392 let operator =
393 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
394 operator: operator_str.to_string(),
395 })?;
396
397 let value = self.parse_value(value_str)?;
398
399 let condition = Condition::new(field, operator, value);
400 Ok(ConditionGroup::single(condition))
401 }
402
403 fn parse_value(&self, value_str: &str) -> Result<Value> {
404 let trimmed = value_str.trim();
405
406 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
408 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
409 {
410 let unquoted = &trimmed[1..trimmed.len() - 1];
411 return Ok(Value::String(unquoted.to_string()));
412 }
413
414 if trimmed.eq_ignore_ascii_case("true") {
416 return Ok(Value::Boolean(true));
417 }
418 if trimmed.eq_ignore_ascii_case("false") {
419 return Ok(Value::Boolean(false));
420 }
421
422 if trimmed.eq_ignore_ascii_case("null") {
424 return Ok(Value::Null);
425 }
426
427 if let Ok(int_val) = trimmed.parse::<i64>() {
429 return Ok(Value::Integer(int_val));
430 }
431
432 if let Ok(float_val) = trimmed.parse::<f64>() {
433 return Ok(Value::Number(float_val));
434 }
435
436 if trimmed.contains('.') {
438 return Ok(Value::String(trimmed.to_string()));
439 }
440
441 Ok(Value::String(trimmed.to_string()))
443 }
444
445 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
446 let statements: Vec<&str> = then_clause
447 .split(';')
448 .map(|s| s.trim())
449 .filter(|s| !s.is_empty())
450 .collect();
451
452 let mut actions = Vec::new();
453
454 for statement in statements {
455 let action = self.parse_action_statement(statement)?;
456 actions.push(action);
457 }
458
459 Ok(actions)
460 }
461
462 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
463 let trimmed = statement.trim();
464
465 let method_regex = Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).map_err(|e| {
467 RuleEngineError::ParseError {
468 message: format!("Method regex error: {}", e),
469 }
470 })?;
471
472 if let Some(captures) = method_regex.captures(trimmed) {
473 let object = captures.get(1).unwrap().as_str().to_string();
474 let method = captures.get(2).unwrap().as_str().to_string();
475 let args_str = captures.get(3).unwrap().as_str();
476
477 let args = if args_str.trim().is_empty() {
478 Vec::new()
479 } else {
480 self.parse_method_args(args_str)?
481 };
482
483 return Ok(ActionType::MethodCall {
484 object,
485 method,
486 args,
487 });
488 }
489
490 if let Some(eq_pos) = trimmed.find('=') {
492 let field = trimmed[..eq_pos].trim().to_string();
493 let value_str = trimmed[eq_pos + 1..].trim();
494 let value = self.parse_value(value_str)?;
495
496 return Ok(ActionType::Set { field, value });
497 }
498
499 let func_regex =
501 Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).map_err(|e| RuleEngineError::ParseError {
502 message: format!("Function regex error: {}", e),
503 })?;
504
505 if let Some(captures) = func_regex.captures(trimmed) {
506 let function_name = captures.get(1).unwrap().as_str();
507 let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
508
509 match function_name.to_lowercase().as_str() {
510 "update" => {
511 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
513 stripped.to_string()
514 } else {
515 args_str.to_string()
516 };
517 Ok(ActionType::Update {
518 object: object_name,
519 })
520 }
521 "set" => {
522 let args = if args_str.is_empty() {
524 Vec::new()
525 } else {
526 args_str
527 .split(',')
528 .map(|arg| self.parse_value(arg.trim()))
529 .collect::<Result<Vec<_>>>()?
530 };
531
532 if args.len() >= 2 {
533 let field = args[0].to_string();
534 let value = args[1].clone();
535 Ok(ActionType::Set { field, value })
536 } else if args.len() == 1 {
537 Ok(ActionType::Set {
539 field: args[0].to_string(),
540 value: Value::Boolean(true),
541 })
542 } else {
543 Ok(ActionType::Custom {
544 action_type: "set".to_string(),
545 params: {
546 let mut params = HashMap::new();
547 params.insert(
548 "args".to_string(),
549 Value::String(args_str.to_string()),
550 );
551 params
552 },
553 })
554 }
555 }
556 "add" => {
557 let value = if args_str.is_empty() {
559 Value::Integer(1) } else {
561 self.parse_value(args_str.trim())?
562 };
563 Ok(ActionType::Custom {
564 action_type: "add".to_string(),
565 params: {
566 let mut params = HashMap::new();
567 params.insert("value".to_string(), value);
568 params
569 },
570 })
571 }
572 "log" => {
573 let message = if args_str.is_empty() {
574 "Log message".to_string()
575 } else {
576 let value = self.parse_value(args_str.trim())?;
577 value.to_string()
578 };
579 Ok(ActionType::Log { message })
580 }
581 _ => {
582 let args = if args_str.is_empty() {
583 Vec::new()
584 } else {
585 args_str
586 .split(',')
587 .map(|arg| self.parse_value(arg.trim()))
588 .collect::<Result<Vec<_>>>()?
589 };
590 Ok(ActionType::Call {
591 function: function_name.to_string(),
592 args,
593 })
594 }
595 }
596 } else {
597 Ok(ActionType::Custom {
599 action_type: "statement".to_string(),
600 params: {
601 let mut params = HashMap::new();
602 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
603 params
604 },
605 })
606 }
607 }
608
609 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
610 if args_str.trim().is_empty() {
611 return Ok(Vec::new());
612 }
613
614 let mut args = Vec::new();
616 let parts: Vec<&str> = args_str.split(',').collect();
617
618 for part in parts {
619 let trimmed = part.trim();
620
621 if trimmed.contains('+')
623 || trimmed.contains('-')
624 || trimmed.contains('*')
625 || trimmed.contains('/')
626 {
627 args.push(Value::String(trimmed.to_string()));
629 } else {
630 args.push(self.parse_value(trimmed)?);
631 }
632 }
633
634 Ok(args)
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::GRLParser;
641
642 #[test]
643 fn test_parse_simple_rule() {
644 let grl = r#"
645 rule "CheckAge" salience 10 {
646 when
647 User.Age >= 18
648 then
649 log("User is adult");
650 }
651 "#;
652
653 let rules = GRLParser::parse_rules(grl).unwrap();
654 assert_eq!(rules.len(), 1);
655 let rule = &rules[0];
656 assert_eq!(rule.name, "CheckAge");
657 assert_eq!(rule.salience, 10);
658 assert_eq!(rule.actions.len(), 1);
659 }
660
661 #[test]
662 fn test_parse_complex_condition() {
663 let grl = r#"
664 rule "ComplexRule" {
665 when
666 User.Age >= 18 && User.Country == "US"
667 then
668 User.Qualified = true;
669 }
670 "#;
671
672 let rules = GRLParser::parse_rules(grl).unwrap();
673 assert_eq!(rules.len(), 1);
674 let rule = &rules[0];
675 assert_eq!(rule.name, "ComplexRule");
676 }
677
678 #[test]
679 fn test_parse_new_syntax_with_parentheses() {
680 let grl = r#"
681 rule "Default Rule" salience 10 {
682 when
683 (user.age >= 18)
684 then
685 set(user.status, "approved");
686 }
687 "#;
688
689 let rules = GRLParser::parse_rules(grl).unwrap();
690 assert_eq!(rules.len(), 1);
691 let rule = &rules[0];
692 assert_eq!(rule.name, "Default Rule");
693 assert_eq!(rule.salience, 10);
694 assert_eq!(rule.actions.len(), 1);
695
696 match &rule.actions[0] {
698 crate::types::ActionType::Set { field, value } => {
699 assert_eq!(field, "user.status");
700 assert_eq!(value, &crate::types::Value::String("approved".to_string()));
701 }
702 _ => panic!("Expected Set action, got: {:?}", rule.actions[0]),
703 }
704 }
705
706 #[test]
707 fn test_parse_complex_nested_conditions() {
708 let grl = r#"
709 rule "Complex Business Rule" salience 10 {
710 when
711 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
712 then
713 apply_discount(20000);
714 }
715 "#;
716
717 let rules = GRLParser::parse_rules(grl).unwrap();
718 assert_eq!(rules.len(), 1);
719 let rule = &rules[0];
720 assert_eq!(rule.name, "Complex Business Rule");
721 assert_eq!(rule.salience, 10);
722 assert_eq!(rule.actions.len(), 1);
723
724 match &rule.actions[0] {
726 crate::types::ActionType::Call { function, args } => {
727 assert_eq!(function, "apply_discount");
728 assert_eq!(args.len(), 1);
729 assert_eq!(args[0], crate::types::Value::Integer(20000));
730 }
731 _ => panic!("Expected Call action, got: {:?}", rule.actions[0]),
732 }
733 }
734
735 #[test]
736 fn test_parse_no_loop_attribute() {
737 let grl = r#"
738 rule "NoLoopRule" no-loop salience 15 {
739 when
740 User.Score < 100
741 then
742 set(User.Score, User.Score + 10);
743 }
744 "#;
745
746 let rules = GRLParser::parse_rules(grl).unwrap();
747 assert_eq!(rules.len(), 1);
748 let rule = &rules[0];
749 assert_eq!(rule.name, "NoLoopRule");
750 assert_eq!(rule.salience, 15);
751 assert!(rule.no_loop, "Rule should have no-loop=true");
752 }
753
754 #[test]
755 fn test_parse_no_loop_different_positions() {
756 let grl1 = r#"
758 rule "Rule1" no-loop salience 10 {
759 when User.Age >= 18
760 then log("adult");
761 }
762 "#;
763
764 let grl2 = r#"
766 rule "Rule2" salience 10 no-loop {
767 when User.Age >= 18
768 then log("adult");
769 }
770 "#;
771
772 let rules1 = GRLParser::parse_rules(grl1).unwrap();
773 let rules2 = GRLParser::parse_rules(grl2).unwrap();
774
775 assert_eq!(rules1.len(), 1);
776 assert_eq!(rules2.len(), 1);
777
778 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
779 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
780
781 assert_eq!(rules1[0].salience, 10);
782 assert_eq!(rules2[0].salience, 10);
783 }
784
785 #[test]
786 fn test_parse_without_no_loop() {
787 let grl = r#"
788 rule "RegularRule" salience 5 {
789 when
790 User.Active == true
791 then
792 log("active user");
793 }
794 "#;
795
796 let rules = GRLParser::parse_rules(grl).unwrap();
797 assert_eq!(rules.len(), 1);
798 let rule = &rules[0];
799 assert_eq!(rule.name, "RegularRule");
800 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
801 }
802}