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+(?:"([^"]+)"|(\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
101        let rule_regex =
102            Regex::new(r"rule\s+\w+[^}]*\}").map_err(|e| RuleEngineError::ParseError {
103                message: format!("Rule splitting regex error: {}", e),
104            })?;
105
106        let mut rules = Vec::new();
107
108        for rule_match in rule_regex.find_iter(grl_text) {
109            let rule_text = rule_match.as_str();
110            let rule = self.parse_single_rule(rule_text)?;
111            rules.push(rule);
112        }
113
114        Ok(rules)
115    }
116
117    fn clean_text(&self, text: &str) -> String {
118        text.lines()
119            .map(|line| line.trim())
120            .filter(|line| !line.is_empty() && !line.starts_with("//"))
121            .collect::<Vec<_>>()
122            .join(" ")
123    }
124
125    fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
126        // Handle logical operators
127        if when_clause.contains("&&") {
128            return self.parse_and_condition(when_clause);
129        }
130
131        if when_clause.contains("||") {
132            return self.parse_or_condition(when_clause);
133        }
134
135        if when_clause.starts_with("!") {
136            return self.parse_not_condition(when_clause);
137        }
138
139        // Single condition
140        self.parse_single_condition(when_clause)
141    }
142
143    fn parse_and_condition(&self, clause: &str) -> Result<ConditionGroup> {
144        let parts: Vec<&str> = clause.split("&&").collect();
145        if parts.len() < 2 {
146            return Err(RuleEngineError::ParseError {
147                message: "Invalid AND condition".to_string(),
148            });
149        }
150
151        let mut conditions = Vec::new();
152        for part in parts {
153            let condition = self.parse_when_clause(part.trim())?;
154            conditions.push(condition);
155        }
156
157        // Combine with AND
158        if conditions.is_empty() {
159            return Err(RuleEngineError::ParseError {
160                message: "No conditions found".to_string(),
161            });
162        }
163
164        let mut iter = conditions.into_iter();
165        let mut result = iter.next().unwrap();
166        for condition in iter {
167            result = ConditionGroup::and(result, condition);
168        }
169
170        Ok(result)
171    }
172
173    fn parse_or_condition(&self, clause: &str) -> Result<ConditionGroup> {
174        let parts: Vec<&str> = clause.split("||").collect();
175        if parts.len() < 2 {
176            return Err(RuleEngineError::ParseError {
177                message: "Invalid OR condition".to_string(),
178            });
179        }
180
181        let mut conditions = Vec::new();
182        for part in parts {
183            let condition = self.parse_when_clause(part.trim())?;
184            conditions.push(condition);
185        }
186
187        // Combine with OR
188        if conditions.is_empty() {
189            return Err(RuleEngineError::ParseError {
190                message: "No conditions found".to_string(),
191            });
192        }
193
194        let mut iter = conditions.into_iter();
195        let mut result = iter.next().unwrap();
196        for condition in iter {
197            result = ConditionGroup::or(result, condition);
198        }
199
200        Ok(result)
201    }
202
203    fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
204        let inner_clause = clause.strip_prefix("!").unwrap().trim();
205        let inner_condition = self.parse_when_clause(inner_clause)?;
206        Ok(ConditionGroup::not(inner_condition))
207    }
208
209    fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
210        // Handle typed object conditions like: $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
211        let typed_object_regex =
212            Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#).map_err(|e| {
213                RuleEngineError::ParseError {
214                    message: format!("Typed object regex error: {}", e),
215                }
216            })?;
217
218        if let Some(captures) = typed_object_regex.captures(clause) {
219            let _object_name = captures.get(1).unwrap().as_str();
220            let _object_type = captures.get(2).unwrap().as_str();
221            let conditions_str = captures.get(3).unwrap().as_str();
222
223            // Parse conditions inside parentheses
224            return self.parse_conditions_within_object(conditions_str);
225        }
226
227        // Parse expressions like: User.Age >= 18, Product.Price < 100.0, etc.
228        let condition_regex = Regex::new(
229            r#"(\w+(?:\.\w+)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
230        )
231        .map_err(|e| RuleEngineError::ParseError {
232            message: format!("Condition regex error: {}", e),
233        })?;
234
235        let captures =
236            condition_regex
237                .captures(clause)
238                .ok_or_else(|| RuleEngineError::ParseError {
239                    message: format!("Invalid condition format: {}", clause),
240                })?;
241
242        let field = captures.get(1).unwrap().as_str().to_string();
243        let operator_str = captures.get(2).unwrap().as_str();
244        let value_str = captures.get(3).unwrap().as_str().trim();
245
246        let operator =
247            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
248                operator: operator_str.to_string(),
249            })?;
250
251        let value = self.parse_value(value_str)?;
252
253        let condition = Condition::new(field, operator, value);
254        Ok(ConditionGroup::single(condition))
255    }
256
257    fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
258        // Parse conditions like: speedUp == true && speed < maxSpeed
259        let parts: Vec<&str> = conditions_str.split("&&").collect();
260
261        let mut conditions = Vec::new();
262        for part in parts {
263            let trimmed = part.trim();
264            let condition = self.parse_simple_condition(trimmed)?;
265            conditions.push(condition);
266        }
267
268        // Combine with AND
269        if conditions.is_empty() {
270            return Err(RuleEngineError::ParseError {
271                message: "No conditions found".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_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
285        // Parse simple condition like: speedUp == true or speed < maxSpeed
286        let condition_regex = Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).map_err(|e| {
287            RuleEngineError::ParseError {
288                message: format!("Simple condition regex error: {}", e),
289            }
290        })?;
291
292        let captures =
293            condition_regex
294                .captures(clause)
295                .ok_or_else(|| RuleEngineError::ParseError {
296                    message: format!("Invalid simple condition format: {}", clause),
297                })?;
298
299        let field = captures.get(1).unwrap().as_str().to_string();
300        let operator_str = captures.get(2).unwrap().as_str();
301        let value_str = captures.get(3).unwrap().as_str().trim();
302
303        let operator =
304            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
305                operator: operator_str.to_string(),
306            })?;
307
308        let value = self.parse_value(value_str)?;
309
310        let condition = Condition::new(field, operator, value);
311        Ok(ConditionGroup::single(condition))
312    }
313
314    fn parse_value(&self, value_str: &str) -> Result<Value> {
315        let trimmed = value_str.trim();
316
317        // String literal
318        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
319            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
320        {
321            let unquoted = &trimmed[1..trimmed.len() - 1];
322            return Ok(Value::String(unquoted.to_string()));
323        }
324
325        // Boolean
326        if trimmed.eq_ignore_ascii_case("true") {
327            return Ok(Value::Boolean(true));
328        }
329        if trimmed.eq_ignore_ascii_case("false") {
330            return Ok(Value::Boolean(false));
331        }
332
333        // Null
334        if trimmed.eq_ignore_ascii_case("null") {
335            return Ok(Value::Null);
336        }
337
338        // Number (try integer first, then float)
339        if let Ok(int_val) = trimmed.parse::<i64>() {
340            return Ok(Value::Integer(int_val));
341        }
342
343        if let Ok(float_val) = trimmed.parse::<f64>() {
344            return Ok(Value::Number(float_val));
345        }
346
347        // Field reference (like User.Name)
348        if trimmed.contains('.') {
349            return Ok(Value::String(trimmed.to_string()));
350        }
351
352        // Default to string
353        Ok(Value::String(trimmed.to_string()))
354    }
355
356    fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
357        let statements: Vec<&str> = then_clause
358            .split(';')
359            .map(|s| s.trim())
360            .filter(|s| !s.is_empty())
361            .collect();
362
363        let mut actions = Vec::new();
364
365        for statement in statements {
366            let action = self.parse_action_statement(statement)?;
367            actions.push(action);
368        }
369
370        Ok(actions)
371    }
372
373    fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
374        let trimmed = statement.trim();
375
376        // Method call: $Object.method(args)
377        let method_regex = Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).map_err(|e| {
378            RuleEngineError::ParseError {
379                message: format!("Method regex error: {}", e),
380            }
381        })?;
382
383        if let Some(captures) = method_regex.captures(trimmed) {
384            let object = captures.get(1).unwrap().as_str().to_string();
385            let method = captures.get(2).unwrap().as_str().to_string();
386            let args_str = captures.get(3).unwrap().as_str();
387
388            let args = if args_str.trim().is_empty() {
389                Vec::new()
390            } else {
391                self.parse_method_args(args_str)?
392            };
393
394            return Ok(ActionType::MethodCall {
395                object,
396                method,
397                args,
398            });
399        }
400
401        // Assignment: Field = Value
402        if let Some(eq_pos) = trimmed.find('=') {
403            let field = trimmed[..eq_pos].trim().to_string();
404            let value_str = trimmed[eq_pos + 1..].trim();
405            let value = self.parse_value(value_str)?;
406
407            return Ok(ActionType::Set { field, value });
408        }
409
410        // Function calls: update($Object), retract($Object), etc.
411        let func_regex =
412            Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).map_err(|e| RuleEngineError::ParseError {
413                message: format!("Function regex error: {}", e),
414            })?;
415
416        if let Some(captures) = func_regex.captures(trimmed) {
417            let function_name = captures.get(1).unwrap().as_str();
418            let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
419
420            match function_name.to_lowercase().as_str() {
421                "update" => {
422                    // Extract object name from $Object
423                    let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
424                        stripped.to_string()
425                    } else {
426                        args_str.to_string()
427                    };
428                    Ok(ActionType::Update {
429                        object: object_name,
430                    })
431                }
432                "log" => {
433                    let message = if args_str.is_empty() {
434                        "Log message".to_string()
435                    } else {
436                        let value = self.parse_value(args_str.trim())?;
437                        value.to_string()
438                    };
439                    Ok(ActionType::Log { message })
440                }
441                _ => {
442                    let args = if args_str.is_empty() {
443                        Vec::new()
444                    } else {
445                        args_str
446                            .split(',')
447                            .map(|arg| self.parse_value(arg.trim()))
448                            .collect::<Result<Vec<_>>>()?
449                    };
450                    Ok(ActionType::Call {
451                        function: function_name.to_string(),
452                        args,
453                    })
454                }
455            }
456        } else {
457            // Custom statement
458            Ok(ActionType::Custom {
459                action_type: "statement".to_string(),
460                params: {
461                    let mut params = HashMap::new();
462                    params.insert("statement".to_string(), Value::String(trimmed.to_string()));
463                    params
464                },
465            })
466        }
467    }
468
469    fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
470        if args_str.trim().is_empty() {
471            return Ok(Vec::new());
472        }
473
474        // Handle expressions like: $TestCar.Speed + $TestCar.SpeedIncrement
475        let mut args = Vec::new();
476        let parts: Vec<&str> = args_str.split(',').collect();
477
478        for part in parts {
479            let trimmed = part.trim();
480
481            // Handle arithmetic expressions
482            if trimmed.contains('+')
483                || trimmed.contains('-')
484                || trimmed.contains('*')
485                || trimmed.contains('/')
486            {
487                // For now, store as string - the engine will evaluate
488                args.push(Value::String(trimmed.to_string()));
489            } else {
490                args.push(self.parse_value(trimmed)?);
491            }
492        }
493
494        Ok(args)
495    }
496}
497
498#[cfg(test)]
499mod tests {
500    use crate::parser::grl_parser::GRLParser as NewGRLParser;
501
502    #[test]
503    fn test_parse_simple_rule() {
504        let grl = r#"
505        rule "CheckAge" salience 10 {
506            when
507                User.Age >= 18
508            then
509                log("User is adult");
510        }
511        "#;
512
513        let rules = NewGRLParser::parse_rules(grl).unwrap();
514        assert_eq!(rules.len(), 1);
515        let rule = &rules[0];
516        assert_eq!(rule.name, "CheckAge");
517        assert_eq!(rule.salience, 10);
518        assert_eq!(rule.actions.len(), 1);
519    }
520
521    #[test]
522    fn test_parse_complex_condition() {
523        let grl = r#"
524        rule "ComplexRule" {
525            when
526                User.Age >= 18 && User.Country == "US"
527            then
528                User.Qualified = true;
529        }
530        "#;
531
532        let rules = NewGRLParser::parse_rules(grl).unwrap();
533        assert_eq!(rules.len(), 1);
534        let rule = &rules[0];
535        assert_eq!(rule.name, "ComplexRule");
536    }
537}