rust_rule_engine/parser/
grl.rs

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
7/// GRL (Grule Rule Language) Parser
8/// Parses Grule-like syntax into Rule objects
9pub struct GRLParser;
10
11impl GRLParser {
12    /// Parse a single rule from GRL syntax
13    ///
14    /// Example GRL syntax:
15    /// ```grl
16    /// rule CheckAge "Age verification rule" salience 10 {
17    ///     when
18    ///         User.Age >= 18 && User.Country == "US"
19    ///     then
20    ///         User.IsAdult = true;
21    ///         Retract("User");
22    /// }
23    /// ```
24    pub fn parse_rule(grl_text: &str) -> Result<Rule> {
25        let mut parser = GRLParser;
26        parser.parse_single_rule(grl_text)
27    }
28
29    /// Parse multiple rules from GRL text
30    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        // Extract rule components using regex - support quoted rule names
39        let rule_regex =
40            Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*(?:salience\s+(\d+))?\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        // Rule name can be either quoted (group 1) or unquoted (group 2)
53        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        // Extract salience and rule body
63        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        // Parse when and then sections
71        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        // Parse conditions
87        let conditions = self.parse_when_clause(when_clause)?;
88
89        // Parse actions
90        let actions = self.parse_then_clause(then_clause)?;
91
92        // Build rule
93        let mut rule = Rule::new(rule_name, conditions, actions);
94        rule = rule.with_priority(salience);
95
96        Ok(rule)
97    }
98
99    fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
100        // Split by rule boundaries - support both quoted and unquoted rule names
101        // Use DOTALL flag to match newlines in rule body
102        let rule_regex =
103            Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#).map_err(|e| {
104                RuleEngineError::ParseError {
105                    message: format!("Rule splitting regex error: {}", e),
106                }
107            })?;
108
109        let mut rules = Vec::new();
110
111        for rule_match in rule_regex.find_iter(grl_text) {
112            let rule_text = rule_match.as_str();
113            let rule = self.parse_single_rule(rule_text)?;
114            rules.push(rule);
115        }
116
117        Ok(rules)
118    }
119
120    fn clean_text(&self, text: &str) -> String {
121        text.lines()
122            .map(|line| line.trim())
123            .filter(|line| !line.is_empty() && !line.starts_with("//"))
124            .collect::<Vec<_>>()
125            .join(" ")
126    }
127
128    fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
129        // Handle logical operators with proper parentheses support
130        let trimmed = when_clause.trim();
131
132        // Strip outer parentheses if they exist
133        let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
134            // Check if these are the outermost parentheses
135            let inner = &trimmed[1..trimmed.len() - 1];
136            if self.is_balanced_parentheses(inner) {
137                inner
138            } else {
139                trimmed
140            }
141        } else {
142            trimmed
143        };
144
145        // Parse OR at the top level (lowest precedence)
146        if let Some(parts) = self.split_logical_operator(clause, "||") {
147            return self.parse_or_parts(parts);
148        }
149
150        // Parse AND (higher precedence)
151        if let Some(parts) = self.split_logical_operator(clause, "&&") {
152            return self.parse_and_parts(parts);
153        }
154
155        // Handle NOT condition
156        if clause.trim_start().starts_with("!") {
157            return self.parse_not_condition(clause);
158        }
159
160        // Single condition
161        self.parse_single_condition(clause)
162    }
163
164    fn is_balanced_parentheses(&self, text: &str) -> bool {
165        let mut count = 0;
166        for ch in text.chars() {
167            match ch {
168                '(' => count += 1,
169                ')' => {
170                    count -= 1;
171                    if count < 0 {
172                        return false;
173                    }
174                }
175                _ => {}
176            }
177        }
178        count == 0
179    }
180
181    fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
182        let mut parts = Vec::new();
183        let mut current_part = String::new();
184        let mut paren_count = 0;
185        let mut chars = clause.chars().peekable();
186
187        while let Some(ch) = chars.next() {
188            match ch {
189                '(' => {
190                    paren_count += 1;
191                    current_part.push(ch);
192                }
193                ')' => {
194                    paren_count -= 1;
195                    current_part.push(ch);
196                }
197                '&' if operator == "&&" && paren_count == 0 => {
198                    if chars.peek() == Some(&'&') {
199                        chars.next(); // consume second &
200                        parts.push(current_part.trim().to_string());
201                        current_part.clear();
202                    } else {
203                        current_part.push(ch);
204                    }
205                }
206                '|' if operator == "||" && paren_count == 0 => {
207                    if chars.peek() == Some(&'|') {
208                        chars.next(); // consume second |
209                        parts.push(current_part.trim().to_string());
210                        current_part.clear();
211                    } else {
212                        current_part.push(ch);
213                    }
214                }
215                _ => {
216                    current_part.push(ch);
217                }
218            }
219        }
220
221        if !current_part.trim().is_empty() {
222            parts.push(current_part.trim().to_string());
223        }
224
225        if parts.len() > 1 {
226            Some(parts)
227        } else {
228            None
229        }
230    }
231
232    fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
233        let mut conditions = Vec::new();
234        for part in parts {
235            let condition = self.parse_when_clause(&part)?;
236            conditions.push(condition);
237        }
238
239        if conditions.is_empty() {
240            return Err(RuleEngineError::ParseError {
241                message: "No conditions found in OR".to_string(),
242            });
243        }
244
245        let mut iter = conditions.into_iter();
246        let mut result = iter.next().unwrap();
247        for condition in iter {
248            result = ConditionGroup::or(result, condition);
249        }
250
251        Ok(result)
252    }
253
254    fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
255        let mut conditions = Vec::new();
256        for part in parts {
257            let condition = self.parse_when_clause(&part)?;
258            conditions.push(condition);
259        }
260
261        if conditions.is_empty() {
262            return Err(RuleEngineError::ParseError {
263                message: "No conditions found in AND".to_string(),
264            });
265        }
266
267        let mut iter = conditions.into_iter();
268        let mut result = iter.next().unwrap();
269        for condition in iter {
270            result = ConditionGroup::and(result, condition);
271        }
272
273        Ok(result)
274    }
275
276    fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
277        let inner_clause = clause.strip_prefix("!").unwrap().trim();
278        let inner_condition = self.parse_when_clause(inner_clause)?;
279        Ok(ConditionGroup::not(inner_condition))
280    }
281
282    fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
283        // Remove outer parentheses if they exist (handle new syntax like "(user.age >= 18)")
284        let trimmed_clause = clause.trim();
285        let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
286            trimmed_clause[1..trimmed_clause.len() - 1].trim()
287        } else {
288            trimmed_clause
289        };
290
291        // Handle typed object conditions like: $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
292        let typed_object_regex =
293            Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#).map_err(|e| {
294                RuleEngineError::ParseError {
295                    message: format!("Typed object regex error: {}", e),
296                }
297            })?;
298
299        if let Some(captures) = typed_object_regex.captures(clause_to_parse) {
300            let _object_name = captures.get(1).unwrap().as_str();
301            let _object_type = captures.get(2).unwrap().as_str();
302            let conditions_str = captures.get(3).unwrap().as_str();
303
304            // Parse conditions inside parentheses
305            return self.parse_conditions_within_object(conditions_str);
306        }
307
308        // Parse expressions like: User.Age >= 18, Product.Price < 100.0, user.age >= 18, etc.
309        // Support both PascalCase (User.Age) and lowercase (user.age) field naming
310        let condition_regex = Regex::new(
311            r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
312        )
313        .map_err(|e| RuleEngineError::ParseError {
314            message: format!("Condition regex error: {}", e),
315        })?;
316
317        let captures = condition_regex.captures(clause_to_parse).ok_or_else(|| {
318            RuleEngineError::ParseError {
319                message: format!("Invalid condition format: {}", clause_to_parse),
320            }
321        })?;
322
323        let field = captures.get(1).unwrap().as_str().to_string();
324        let operator_str = captures.get(2).unwrap().as_str();
325        let value_str = captures.get(3).unwrap().as_str().trim();
326
327        let operator =
328            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
329                operator: operator_str.to_string(),
330            })?;
331
332        let value = self.parse_value(value_str)?;
333
334        let condition = Condition::new(field, operator, value);
335        Ok(ConditionGroup::single(condition))
336    }
337
338    fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
339        // Parse conditions like: speedUp == true && speed < maxSpeed
340        let parts: Vec<&str> = conditions_str.split("&&").collect();
341
342        let mut conditions = Vec::new();
343        for part in parts {
344            let trimmed = part.trim();
345            let condition = self.parse_simple_condition(trimmed)?;
346            conditions.push(condition);
347        }
348
349        // Combine with AND
350        if conditions.is_empty() {
351            return Err(RuleEngineError::ParseError {
352                message: "No conditions found".to_string(),
353            });
354        }
355
356        let mut iter = conditions.into_iter();
357        let mut result = iter.next().unwrap();
358        for condition in iter {
359            result = ConditionGroup::and(result, condition);
360        }
361
362        Ok(result)
363    }
364
365    fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
366        // Parse simple condition like: speedUp == true or speed < maxSpeed
367        let condition_regex = Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).map_err(|e| {
368            RuleEngineError::ParseError {
369                message: format!("Simple condition regex error: {}", e),
370            }
371        })?;
372
373        let captures =
374            condition_regex
375                .captures(clause)
376                .ok_or_else(|| RuleEngineError::ParseError {
377                    message: format!("Invalid simple condition format: {}", clause),
378                })?;
379
380        let field = captures.get(1).unwrap().as_str().to_string();
381        let operator_str = captures.get(2).unwrap().as_str();
382        let value_str = captures.get(3).unwrap().as_str().trim();
383
384        let operator =
385            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
386                operator: operator_str.to_string(),
387            })?;
388
389        let value = self.parse_value(value_str)?;
390
391        let condition = Condition::new(field, operator, value);
392        Ok(ConditionGroup::single(condition))
393    }
394
395    fn parse_value(&self, value_str: &str) -> Result<Value> {
396        let trimmed = value_str.trim();
397
398        // String literal
399        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
400            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
401        {
402            let unquoted = &trimmed[1..trimmed.len() - 1];
403            return Ok(Value::String(unquoted.to_string()));
404        }
405
406        // Boolean
407        if trimmed.eq_ignore_ascii_case("true") {
408            return Ok(Value::Boolean(true));
409        }
410        if trimmed.eq_ignore_ascii_case("false") {
411            return Ok(Value::Boolean(false));
412        }
413
414        // Null
415        if trimmed.eq_ignore_ascii_case("null") {
416            return Ok(Value::Null);
417        }
418
419        // Number (try integer first, then float)
420        if let Ok(int_val) = trimmed.parse::<i64>() {
421            return Ok(Value::Integer(int_val));
422        }
423
424        if let Ok(float_val) = trimmed.parse::<f64>() {
425            return Ok(Value::Number(float_val));
426        }
427
428        // Field reference (like User.Name)
429        if trimmed.contains('.') {
430            return Ok(Value::String(trimmed.to_string()));
431        }
432
433        // Default to string
434        Ok(Value::String(trimmed.to_string()))
435    }
436
437    fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
438        let statements: Vec<&str> = then_clause
439            .split(';')
440            .map(|s| s.trim())
441            .filter(|s| !s.is_empty())
442            .collect();
443
444        let mut actions = Vec::new();
445
446        for statement in statements {
447            let action = self.parse_action_statement(statement)?;
448            actions.push(action);
449        }
450
451        Ok(actions)
452    }
453
454    fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
455        let trimmed = statement.trim();
456
457        // Method call: $Object.method(args)
458        let method_regex = Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).map_err(|e| {
459            RuleEngineError::ParseError {
460                message: format!("Method regex error: {}", e),
461            }
462        })?;
463
464        if let Some(captures) = method_regex.captures(trimmed) {
465            let object = captures.get(1).unwrap().as_str().to_string();
466            let method = captures.get(2).unwrap().as_str().to_string();
467            let args_str = captures.get(3).unwrap().as_str();
468
469            let args = if args_str.trim().is_empty() {
470                Vec::new()
471            } else {
472                self.parse_method_args(args_str)?
473            };
474
475            return Ok(ActionType::MethodCall {
476                object,
477                method,
478                args,
479            });
480        }
481
482        // Assignment: Field = Value
483        if let Some(eq_pos) = trimmed.find('=') {
484            let field = trimmed[..eq_pos].trim().to_string();
485            let value_str = trimmed[eq_pos + 1..].trim();
486            let value = self.parse_value(value_str)?;
487
488            return Ok(ActionType::Set { field, value });
489        }
490
491        // Function calls: update($Object), retract($Object), etc.
492        let func_regex =
493            Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).map_err(|e| RuleEngineError::ParseError {
494                message: format!("Function regex error: {}", e),
495            })?;
496
497        if let Some(captures) = func_regex.captures(trimmed) {
498            let function_name = captures.get(1).unwrap().as_str();
499            let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
500
501            match function_name.to_lowercase().as_str() {
502                "update" => {
503                    // Extract object name from $Object
504                    let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
505                        stripped.to_string()
506                    } else {
507                        args_str.to_string()
508                    };
509                    Ok(ActionType::Update {
510                        object: object_name,
511                    })
512                }
513                "set" => {
514                    // Handle set(field, value) format
515                    let args = if args_str.is_empty() {
516                        Vec::new()
517                    } else {
518                        args_str
519                            .split(',')
520                            .map(|arg| self.parse_value(arg.trim()))
521                            .collect::<Result<Vec<_>>>()?
522                    };
523
524                    if args.len() >= 2 {
525                        let field = args[0].to_string();
526                        let value = args[1].clone();
527                        Ok(ActionType::Set { field, value })
528                    } else if args.len() == 1 {
529                        // set(field) - set to true by default
530                        Ok(ActionType::Set {
531                            field: args[0].to_string(),
532                            value: Value::Boolean(true),
533                        })
534                    } else {
535                        Ok(ActionType::Custom {
536                            action_type: "set".to_string(),
537                            params: {
538                                let mut params = HashMap::new();
539                                params.insert(
540                                    "args".to_string(),
541                                    Value::String(args_str.to_string()),
542                                );
543                                params
544                            },
545                        })
546                    }
547                }
548                "add" => {
549                    // Handle add(value) format
550                    let value = if args_str.is_empty() {
551                        Value::Integer(1) // Default increment
552                    } else {
553                        self.parse_value(args_str.trim())?
554                    };
555                    Ok(ActionType::Custom {
556                        action_type: "add".to_string(),
557                        params: {
558                            let mut params = HashMap::new();
559                            params.insert("value".to_string(), value);
560                            params
561                        },
562                    })
563                }
564                "log" => {
565                    let message = if args_str.is_empty() {
566                        "Log message".to_string()
567                    } else {
568                        let value = self.parse_value(args_str.trim())?;
569                        value.to_string()
570                    };
571                    Ok(ActionType::Log { message })
572                }
573                _ => {
574                    let args = if args_str.is_empty() {
575                        Vec::new()
576                    } else {
577                        args_str
578                            .split(',')
579                            .map(|arg| self.parse_value(arg.trim()))
580                            .collect::<Result<Vec<_>>>()?
581                    };
582                    Ok(ActionType::Call {
583                        function: function_name.to_string(),
584                        args,
585                    })
586                }
587            }
588        } else {
589            // Custom statement
590            Ok(ActionType::Custom {
591                action_type: "statement".to_string(),
592                params: {
593                    let mut params = HashMap::new();
594                    params.insert("statement".to_string(), Value::String(trimmed.to_string()));
595                    params
596                },
597            })
598        }
599    }
600
601    fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
602        if args_str.trim().is_empty() {
603            return Ok(Vec::new());
604        }
605
606        // Handle expressions like: $TestCar.Speed + $TestCar.SpeedIncrement
607        let mut args = Vec::new();
608        let parts: Vec<&str> = args_str.split(',').collect();
609
610        for part in parts {
611            let trimmed = part.trim();
612
613            // Handle arithmetic expressions
614            if trimmed.contains('+')
615                || trimmed.contains('-')
616                || trimmed.contains('*')
617                || trimmed.contains('/')
618            {
619                // For now, store as string - the engine will evaluate
620                args.push(Value::String(trimmed.to_string()));
621            } else {
622                args.push(self.parse_value(trimmed)?);
623            }
624        }
625
626        Ok(args)
627    }
628}
629
630#[cfg(test)]
631mod tests {
632    use super::GRLParser;
633
634    #[test]
635    fn test_parse_simple_rule() {
636        let grl = r#"
637        rule "CheckAge" salience 10 {
638            when
639                User.Age >= 18
640            then
641                log("User is adult");
642        }
643        "#;
644
645        let rules = GRLParser::parse_rules(grl).unwrap();
646        assert_eq!(rules.len(), 1);
647        let rule = &rules[0];
648        assert_eq!(rule.name, "CheckAge");
649        assert_eq!(rule.salience, 10);
650        assert_eq!(rule.actions.len(), 1);
651    }
652
653    #[test]
654    fn test_parse_complex_condition() {
655        let grl = r#"
656        rule "ComplexRule" {
657            when
658                User.Age >= 18 && User.Country == "US"
659            then
660                User.Qualified = true;
661        }
662        "#;
663
664        let rules = GRLParser::parse_rules(grl).unwrap();
665        assert_eq!(rules.len(), 1);
666        let rule = &rules[0];
667        assert_eq!(rule.name, "ComplexRule");
668    }
669
670    #[test]
671    fn test_parse_new_syntax_with_parentheses() {
672        let grl = r#"
673        rule "Default Rule" salience 10 {
674            when
675                (user.age >= 18)
676            then
677                set(user.status, "approved");
678        }
679        "#;
680
681        let rules = GRLParser::parse_rules(grl).unwrap();
682        assert_eq!(rules.len(), 1);
683        let rule = &rules[0];
684        assert_eq!(rule.name, "Default Rule");
685        assert_eq!(rule.salience, 10);
686        assert_eq!(rule.actions.len(), 1);
687
688        // Check that the action is parsed as a Set action
689        match &rule.actions[0] {
690            crate::types::ActionType::Set { field, value } => {
691                assert_eq!(field, "user.status");
692                assert_eq!(value, &crate::types::Value::String("approved".to_string()));
693            }
694            _ => panic!("Expected Set action, got: {:?}", rule.actions[0]),
695        }
696    }
697
698    #[test]
699    fn test_parse_complex_nested_conditions() {
700        let grl = r#"
701        rule "Complex Business Rule" salience 10 {
702            when
703                (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
704            then
705                apply_discount(20000);
706        }
707        "#;
708
709        let rules = GRLParser::parse_rules(grl).unwrap();
710        assert_eq!(rules.len(), 1);
711        let rule = &rules[0];
712        assert_eq!(rule.name, "Complex Business Rule");
713        assert_eq!(rule.salience, 10);
714        assert_eq!(rule.actions.len(), 1);
715
716        // Check that the action is parsed as a function call
717        match &rule.actions[0] {
718            crate::types::ActionType::Call { function, args } => {
719                assert_eq!(function, "apply_discount");
720                assert_eq!(args.len(), 1);
721                assert_eq!(args[0], crate::types::Value::Integer(20000));
722            }
723            _ => panic!("Expected Call action, got: {:?}", rule.actions[0]),
724        }
725    }
726}