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