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 chrono::{DateTime, Utc};
5use regex::Regex;
6use std::collections::HashMap;
7
8/// GRL (Grule Rule Language) Parser
9/// Parses Grule-like syntax into Rule objects
10pub struct GRLParser;
11
12/// Parsed rule attributes from GRL header
13#[derive(Debug, Default)]
14struct RuleAttributes {
15    pub no_loop: bool,
16    pub lock_on_active: bool,
17    pub agenda_group: Option<String>,
18    pub activation_group: Option<String>,
19    pub date_effective: Option<DateTime<Utc>>,
20    pub date_expires: Option<DateTime<Utc>>,
21}
22
23impl GRLParser {
24    /// Parse a single rule from GRL syntax
25    ///
26    /// Example GRL syntax:
27    /// ```grl
28    /// rule CheckAge "Age verification rule" salience 10 {
29    ///     when
30    ///         User.Age >= 18 && User.Country == "US"
31    ///     then
32    ///         User.IsAdult = true;
33    ///         Retract("User");
34    /// }
35    /// ```
36    pub fn parse_rule(grl_text: &str) -> Result<Rule> {
37        let mut parser = GRLParser;
38        parser.parse_single_rule(grl_text)
39    }
40
41    /// Parse multiple rules from GRL text
42    pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
43        let mut parser = GRLParser;
44        parser.parse_multiple_rules(grl_text)
45    }
46
47    fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
48        let cleaned = self.clean_text(grl_text);
49
50        // Extract rule components using regex - support various attributes
51        let rule_regex = Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
52            .map_err(|e| RuleEngineError::ParseError {
53                message: format!("Invalid rule regex: {}", e),
54            })?;
55
56        let captures =
57            rule_regex
58                .captures(&cleaned)
59                .ok_or_else(|| RuleEngineError::ParseError {
60                    message: format!("Invalid GRL rule format. Input: {}", cleaned),
61                })?;
62
63        // Rule name can be either quoted (group 1) or unquoted (group 2)
64        let rule_name = if let Some(quoted_name) = captures.get(1) {
65            quoted_name.as_str().to_string()
66        } else if let Some(unquoted_name) = captures.get(2) {
67            unquoted_name.as_str().to_string()
68        } else {
69            return Err(RuleEngineError::ParseError {
70                message: "Could not extract rule name".to_string(),
71            });
72        };
73
74        // Attributes section (group 3)
75        let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
76
77        // Rule body (group 4)
78        let rule_body = captures.get(4).unwrap().as_str();
79
80        // Parse salience from attributes section
81        let salience = self.extract_salience(attributes_section)?;
82
83        // Parse when and then sections
84        let when_then_regex =
85            Regex::new(r"when\s+(.+?)\s+then\s+(.+)").map_err(|e| RuleEngineError::ParseError {
86                message: format!("Invalid when-then regex: {}", e),
87            })?;
88
89        let when_then_captures =
90            when_then_regex
91                .captures(rule_body)
92                .ok_or_else(|| RuleEngineError::ParseError {
93                    message: "Missing when or then clause".to_string(),
94                })?;
95
96        let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
97        let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
98
99        // Parse conditions and actions
100        let conditions = self.parse_when_clause(when_clause)?;
101        let actions = self.parse_then_clause(then_clause)?;
102
103        // Parse all attributes from rule header
104        let attributes = self.parse_rule_attributes(attributes_section)?;
105
106        // Build rule
107        let mut rule = Rule::new(rule_name, conditions, actions);
108        rule = rule.with_priority(salience);
109
110        // Apply parsed attributes
111        if attributes.no_loop {
112            rule = rule.with_no_loop(true);
113        }
114        if attributes.lock_on_active {
115            rule = rule.with_lock_on_active(true);
116        }
117        if let Some(agenda_group) = attributes.agenda_group {
118            rule = rule.with_agenda_group(agenda_group);
119        }
120        if let Some(activation_group) = attributes.activation_group {
121            rule = rule.with_activation_group(activation_group);
122        }
123        if let Some(date_effective) = attributes.date_effective {
124            rule = rule.with_date_effective(date_effective);
125        }
126        if let Some(date_expires) = attributes.date_expires {
127            rule = rule.with_date_expires(date_expires);
128        }
129
130        Ok(rule)
131    }
132
133    fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
134        // Split by rule boundaries - support both quoted and unquoted rule names
135        // Use DOTALL flag to match newlines in rule body
136        let rule_regex =
137            Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#).map_err(|e| {
138                RuleEngineError::ParseError {
139                    message: format!("Rule splitting regex error: {}", e),
140                }
141            })?;
142
143        let mut rules = Vec::new();
144
145        for rule_match in rule_regex.find_iter(grl_text) {
146            let rule_text = rule_match.as_str();
147            let rule = self.parse_single_rule(rule_text)?;
148            rules.push(rule);
149        }
150
151        Ok(rules)
152    }
153
154    /// Parse rule attributes from the rule header
155    fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
156        let mut attributes = RuleAttributes::default();
157
158        // Check for simple boolean attributes
159        if rule_header.contains("no-loop") {
160            attributes.no_loop = true;
161        }
162        if rule_header.contains("lock-on-active") {
163            attributes.lock_on_active = true;
164        }
165
166        // Parse agenda-group attribute
167        if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
168            attributes.agenda_group = Some(agenda_group);
169        }
170
171        // Parse activation-group attribute
172        if let Some(activation_group) =
173            self.extract_quoted_attribute(rule_header, "activation-group")?
174        {
175            attributes.activation_group = Some(activation_group);
176        }
177
178        // Parse date-effective attribute
179        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
180            attributes.date_effective = Some(self.parse_date_string(&date_str)?);
181        }
182
183        // Parse date-expires attribute
184        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
185            attributes.date_expires = Some(self.parse_date_string(&date_str)?);
186        }
187
188        Ok(attributes)
189    }
190
191    /// Extract quoted attribute value from rule header
192    fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
193        let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
194        let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
195            message: format!("Invalid attribute regex for {}: {}", attribute, e),
196        })?;
197
198        if let Some(captures) = regex.captures(header) {
199            if let Some(value) = captures.get(1) {
200                return Ok(Some(value.as_str().to_string()));
201            }
202        }
203
204        Ok(None)
205    }
206
207    /// Parse date string in various formats
208    fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
209        // Try ISO 8601 format first
210        if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
211            return Ok(date.with_timezone(&Utc));
212        }
213
214        // Try simple date formats
215        let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
216
217        for format in &formats {
218            if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
219                return Ok(naive_date.and_utc());
220            }
221            if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
222                return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
223            }
224        }
225
226        Err(RuleEngineError::ParseError {
227            message: format!("Unable to parse date: {}", date_str),
228        })
229    }
230
231    /// Extract salience value from attributes section
232    fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
233        let salience_regex =
234            Regex::new(r"salience\s+(\d+)").map_err(|e| RuleEngineError::ParseError {
235                message: format!("Invalid salience regex: {}", e),
236            })?;
237
238        if let Some(captures) = salience_regex.captures(attributes_section) {
239            if let Some(salience_match) = captures.get(1) {
240                return salience_match.as_str().parse::<i32>().map_err(|e| {
241                    RuleEngineError::ParseError {
242                        message: format!("Invalid salience value: {}", e),
243                    }
244                });
245            }
246        }
247
248        Ok(0) // Default salience
249    }
250
251    fn clean_text(&self, text: &str) -> String {
252        text.lines()
253            .map(|line| line.trim())
254            .filter(|line| !line.is_empty() && !line.starts_with("//"))
255            .collect::<Vec<_>>()
256            .join(" ")
257    }
258
259    fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
260        // Handle logical operators with proper parentheses support
261        let trimmed = when_clause.trim();
262
263        // Strip outer parentheses if they exist
264        let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
265            // Check if these are the outermost parentheses
266            let inner = &trimmed[1..trimmed.len() - 1];
267            if self.is_balanced_parentheses(inner) {
268                inner
269            } else {
270                trimmed
271            }
272        } else {
273            trimmed
274        };
275
276        // Parse OR at the top level (lowest precedence)
277        if let Some(parts) = self.split_logical_operator(clause, "||") {
278            return self.parse_or_parts(parts);
279        }
280
281        // Parse AND (higher precedence)
282        if let Some(parts) = self.split_logical_operator(clause, "&&") {
283            return self.parse_and_parts(parts);
284        }
285
286        // Handle NOT condition
287        if clause.trim_start().starts_with("!") {
288            return self.parse_not_condition(clause);
289        }
290
291        // Handle EXISTS condition
292        if clause.trim_start().starts_with("exists(") {
293            return self.parse_exists_condition(clause);
294        }
295
296        // Handle FORALL condition
297        if clause.trim_start().starts_with("forall(") {
298            return self.parse_forall_condition(clause);
299        }
300
301        // Single condition
302        self.parse_single_condition(clause)
303    }
304
305    fn is_balanced_parentheses(&self, text: &str) -> bool {
306        let mut count = 0;
307        for ch in text.chars() {
308            match ch {
309                '(' => count += 1,
310                ')' => {
311                    count -= 1;
312                    if count < 0 {
313                        return false;
314                    }
315                }
316                _ => {}
317            }
318        }
319        count == 0
320    }
321
322    fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
323        let mut parts = Vec::new();
324        let mut current_part = String::new();
325        let mut paren_count = 0;
326        let mut chars = clause.chars().peekable();
327
328        while let Some(ch) = chars.next() {
329            match ch {
330                '(' => {
331                    paren_count += 1;
332                    current_part.push(ch);
333                }
334                ')' => {
335                    paren_count -= 1;
336                    current_part.push(ch);
337                }
338                '&' if operator == "&&" && paren_count == 0 => {
339                    if chars.peek() == Some(&'&') {
340                        chars.next(); // consume second &
341                        parts.push(current_part.trim().to_string());
342                        current_part.clear();
343                    } else {
344                        current_part.push(ch);
345                    }
346                }
347                '|' if operator == "||" && paren_count == 0 => {
348                    if chars.peek() == Some(&'|') {
349                        chars.next(); // consume second |
350                        parts.push(current_part.trim().to_string());
351                        current_part.clear();
352                    } else {
353                        current_part.push(ch);
354                    }
355                }
356                _ => {
357                    current_part.push(ch);
358                }
359            }
360        }
361
362        if !current_part.trim().is_empty() {
363            parts.push(current_part.trim().to_string());
364        }
365
366        if parts.len() > 1 {
367            Some(parts)
368        } else {
369            None
370        }
371    }
372
373    fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
374        let mut conditions = Vec::new();
375        for part in parts {
376            let condition = self.parse_when_clause(&part)?;
377            conditions.push(condition);
378        }
379
380        if conditions.is_empty() {
381            return Err(RuleEngineError::ParseError {
382                message: "No conditions found in OR".to_string(),
383            });
384        }
385
386        let mut iter = conditions.into_iter();
387        let mut result = iter.next().unwrap();
388        for condition in iter {
389            result = ConditionGroup::or(result, condition);
390        }
391
392        Ok(result)
393    }
394
395    fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
396        let mut conditions = Vec::new();
397        for part in parts {
398            let condition = self.parse_when_clause(&part)?;
399            conditions.push(condition);
400        }
401
402        if conditions.is_empty() {
403            return Err(RuleEngineError::ParseError {
404                message: "No conditions found in AND".to_string(),
405            });
406        }
407
408        let mut iter = conditions.into_iter();
409        let mut result = iter.next().unwrap();
410        for condition in iter {
411            result = ConditionGroup::and(result, condition);
412        }
413
414        Ok(result)
415    }
416
417    fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
418        let inner_clause = clause.strip_prefix("!").unwrap().trim();
419        let inner_condition = self.parse_when_clause(inner_clause)?;
420        Ok(ConditionGroup::not(inner_condition))
421    }
422
423    fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
424        let clause = clause.trim_start();
425        if !clause.starts_with("exists(") || !clause.ends_with(")") {
426            return Err(RuleEngineError::ParseError {
427                message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
428            });
429        }
430
431        // Extract content between parentheses
432        let inner_clause = &clause[7..clause.len() - 1]; // Remove "exists(" and ")"
433        let inner_condition = self.parse_when_clause(inner_clause)?;
434        Ok(ConditionGroup::exists(inner_condition))
435    }
436
437    fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
438        let clause = clause.trim_start();
439        if !clause.starts_with("forall(") || !clause.ends_with(")") {
440            return Err(RuleEngineError::ParseError {
441                message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
442            });
443        }
444
445        // Extract content between parentheses
446        let inner_clause = &clause[7..clause.len() - 1]; // Remove "forall(" and ")"
447        let inner_condition = self.parse_when_clause(inner_clause)?;
448        Ok(ConditionGroup::forall(inner_condition))
449    }
450
451    fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
452        // Remove outer parentheses if they exist (handle new syntax like "(user.age >= 18)")
453        let trimmed_clause = clause.trim();
454        let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
455            trimmed_clause[1..trimmed_clause.len() - 1].trim()
456        } else {
457            trimmed_clause
458        };
459
460        // Handle Test CE: test(functionName(args...))
461        // This is a CLIPS-inspired feature for arbitrary boolean expressions
462        let test_regex = Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
463            .map_err(|e| RuleEngineError::ParseError {
464                message: format!("Test CE regex error: {}", e),
465            })?;
466
467        if let Some(captures) = test_regex.captures(clause_to_parse) {
468            let function_name = captures.get(1).unwrap().as_str().to_string();
469            let args_str = captures.get(2).unwrap().as_str();
470
471            // Parse arguments
472            let args: Vec<String> = if args_str.trim().is_empty() {
473                Vec::new()
474            } else {
475                args_str
476                    .split(',')
477                    .map(|arg| arg.trim().to_string())
478                    .collect()
479            };
480
481            let condition = Condition::with_test(function_name, args);
482            return Ok(ConditionGroup::single(condition));
483        }
484
485        // Handle typed object conditions like: $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
486        let typed_object_regex =
487            Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#).map_err(|e| {
488                RuleEngineError::ParseError {
489                    message: format!("Typed object regex error: {}", e),
490                }
491            })?;
492
493        if let Some(captures) = typed_object_regex.captures(clause_to_parse) {
494            let _object_name = captures.get(1).unwrap().as_str();
495            let _object_type = captures.get(2).unwrap().as_str();
496            let conditions_str = captures.get(3).unwrap().as_str();
497
498            // Parse conditions inside parentheses
499            return self.parse_conditions_within_object(conditions_str);
500        }
501
502        // Try to parse function call pattern: functionName(arg1, arg2, ...) operator value
503        let function_regex = Regex::new(
504            r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
505        )
506        .map_err(|e| RuleEngineError::ParseError {
507            message: format!("Function regex error: {}", e),
508        })?;
509
510        if let Some(captures) = function_regex.captures(clause_to_parse) {
511            let function_name = captures.get(1).unwrap().as_str().to_string();
512            let args_str = captures.get(2).unwrap().as_str();
513            let operator_str = captures.get(3).unwrap().as_str();
514            let value_str = captures.get(4).unwrap().as_str().trim();
515
516            // Parse arguments
517            let args: Vec<String> = if args_str.trim().is_empty() {
518                Vec::new()
519            } else {
520                args_str
521                    .split(',')
522                    .map(|arg| arg.trim().to_string())
523                    .collect()
524            };
525
526            let operator =
527                Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
528                    operator: operator_str.to_string(),
529                })?;
530
531            let value = self.parse_value(value_str)?;
532
533            let condition = Condition::with_function(function_name, args, operator, value);
534            return Ok(ConditionGroup::single(condition));
535        }
536
537        // Parse expressions like: User.Age >= 18, Product.Price < 100.0, user.age >= 18, etc.
538        // Support both PascalCase (User.Age) and lowercase (user.age) field naming
539        let condition_regex = Regex::new(
540            r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
541        )
542        .map_err(|e| RuleEngineError::ParseError {
543            message: format!("Condition regex error: {}", e),
544        })?;
545
546        let captures = condition_regex.captures(clause_to_parse).ok_or_else(|| {
547            RuleEngineError::ParseError {
548                message: format!("Invalid condition format: {}", clause_to_parse),
549            }
550        })?;
551
552        let field = captures.get(1).unwrap().as_str().to_string();
553        let operator_str = captures.get(2).unwrap().as_str();
554        let value_str = captures.get(3).unwrap().as_str().trim();
555
556        let operator =
557            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
558                operator: operator_str.to_string(),
559            })?;
560
561        let value = self.parse_value(value_str)?;
562
563        let condition = Condition::new(field, operator, value);
564        Ok(ConditionGroup::single(condition))
565    }
566
567    fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
568        // Parse conditions like: speedUp == true && speed < maxSpeed
569        let parts: Vec<&str> = conditions_str.split("&&").collect();
570
571        let mut conditions = Vec::new();
572        for part in parts {
573            let trimmed = part.trim();
574            let condition = self.parse_simple_condition(trimmed)?;
575            conditions.push(condition);
576        }
577
578        // Combine with AND
579        if conditions.is_empty() {
580            return Err(RuleEngineError::ParseError {
581                message: "No conditions found".to_string(),
582            });
583        }
584
585        let mut iter = conditions.into_iter();
586        let mut result = iter.next().unwrap();
587        for condition in iter {
588            result = ConditionGroup::and(result, condition);
589        }
590
591        Ok(result)
592    }
593
594    fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
595        // Parse simple condition like: speedUp == true or speed < maxSpeed
596        let condition_regex = Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).map_err(|e| {
597            RuleEngineError::ParseError {
598                message: format!("Simple condition regex error: {}", e),
599            }
600        })?;
601
602        let captures =
603            condition_regex
604                .captures(clause)
605                .ok_or_else(|| RuleEngineError::ParseError {
606                    message: format!("Invalid simple condition format: {}", clause),
607                })?;
608
609        let field = captures.get(1).unwrap().as_str().to_string();
610        let operator_str = captures.get(2).unwrap().as_str();
611        let value_str = captures.get(3).unwrap().as_str().trim();
612
613        let operator =
614            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
615                operator: operator_str.to_string(),
616            })?;
617
618        let value = self.parse_value(value_str)?;
619
620        let condition = Condition::new(field, operator, value);
621        Ok(ConditionGroup::single(condition))
622    }
623
624    fn parse_value(&self, value_str: &str) -> Result<Value> {
625        let trimmed = value_str.trim();
626
627        // String literal
628        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
629            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
630        {
631            let unquoted = &trimmed[1..trimmed.len() - 1];
632            return Ok(Value::String(unquoted.to_string()));
633        }
634
635        // Boolean
636        if trimmed.eq_ignore_ascii_case("true") {
637            return Ok(Value::Boolean(true));
638        }
639        if trimmed.eq_ignore_ascii_case("false") {
640            return Ok(Value::Boolean(false));
641        }
642
643        // Null
644        if trimmed.eq_ignore_ascii_case("null") {
645            return Ok(Value::Null);
646        }
647
648        // Number (try integer first, then float)
649        if let Ok(int_val) = trimmed.parse::<i64>() {
650            return Ok(Value::Integer(int_val));
651        }
652
653        if let Ok(float_val) = trimmed.parse::<f64>() {
654            return Ok(Value::Number(float_val));
655        }
656
657        // Field reference (like User.Name)
658        if trimmed.contains('.') {
659            return Ok(Value::String(trimmed.to_string()));
660        }
661
662        // Default to string
663        Ok(Value::String(trimmed.to_string()))
664    }
665
666    fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
667        let statements: Vec<&str> = then_clause
668            .split(';')
669            .map(|s| s.trim())
670            .filter(|s| !s.is_empty())
671            .collect();
672
673        let mut actions = Vec::new();
674
675        for statement in statements {
676            let action = self.parse_action_statement(statement)?;
677            actions.push(action);
678        }
679
680        Ok(actions)
681    }
682
683    fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
684        let trimmed = statement.trim();
685
686        // Method call: $Object.method(args)
687        let method_regex = Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).map_err(|e| {
688            RuleEngineError::ParseError {
689                message: format!("Method regex error: {}", e),
690            }
691        })?;
692
693        if let Some(captures) = method_regex.captures(trimmed) {
694            let object = captures.get(1).unwrap().as_str().to_string();
695            let method = captures.get(2).unwrap().as_str().to_string();
696            let args_str = captures.get(3).unwrap().as_str();
697
698            let args = if args_str.trim().is_empty() {
699                Vec::new()
700            } else {
701                self.parse_method_args(args_str)?
702            };
703
704            return Ok(ActionType::MethodCall {
705                object,
706                method,
707                args,
708            });
709        }
710
711        // Assignment: Field = Value
712        if let Some(eq_pos) = trimmed.find('=') {
713            let field = trimmed[..eq_pos].trim().to_string();
714            let value_str = trimmed[eq_pos + 1..].trim();
715            let value = self.parse_value(value_str)?;
716
717            return Ok(ActionType::Set { field, value });
718        }
719
720        // Function calls: update($Object), retract($Object), etc.
721        let func_regex =
722            Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).map_err(|e| RuleEngineError::ParseError {
723                message: format!("Function regex error: {}", e),
724            })?;
725
726        if let Some(captures) = func_regex.captures(trimmed) {
727            let function_name = captures.get(1).unwrap().as_str();
728            let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
729
730            match function_name.to_lowercase().as_str() {
731                "update" => {
732                    // Extract object name from $Object
733                    let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
734                        stripped.to_string()
735                    } else {
736                        args_str.to_string()
737                    };
738                    Ok(ActionType::Update {
739                        object: object_name,
740                    })
741                }
742                "log" => {
743                    let message = if args_str.is_empty() {
744                        "Log message".to_string()
745                    } else {
746                        let value = self.parse_value(args_str.trim())?;
747                        value.to_string()
748                    };
749                    Ok(ActionType::Log { message })
750                }
751                "activateagendagroup" | "activate_agenda_group" => {
752                    let agenda_group = if args_str.is_empty() {
753                        return Err(RuleEngineError::ParseError {
754                            message: "ActivateAgendaGroup requires agenda group name".to_string(),
755                        });
756                    } else {
757                        let value = self.parse_value(args_str.trim())?;
758                        match value {
759                            Value::String(s) => s,
760                            _ => value.to_string(),
761                        }
762                    };
763                    Ok(ActionType::ActivateAgendaGroup {
764                        group: agenda_group,
765                    })
766                }
767                "schedulerule" | "schedule_rule" => {
768                    // Parse delay and target rule: ScheduleRule(5000, "next-rule")
769                    let parts: Vec<&str> = args_str.split(',').collect();
770                    if parts.len() != 2 {
771                        return Err(RuleEngineError::ParseError {
772                            message: "ScheduleRule requires delay_ms and rule_name".to_string(),
773                        });
774                    }
775
776                    let delay_ms = self.parse_value(parts[0].trim())?;
777                    let rule_name = self.parse_value(parts[1].trim())?;
778
779                    let delay_ms = match delay_ms {
780                        Value::Integer(i) => i as u64,
781                        Value::Number(f) => f as u64,
782                        _ => {
783                            return Err(RuleEngineError::ParseError {
784                                message: "ScheduleRule delay_ms must be a number".to_string(),
785                            })
786                        }
787                    };
788
789                    let rule_name = match rule_name {
790                        Value::String(s) => s,
791                        _ => rule_name.to_string(),
792                    };
793
794                    Ok(ActionType::ScheduleRule {
795                        delay_ms,
796                        rule_name,
797                    })
798                }
799                "completeworkflow" | "complete_workflow" => {
800                    let workflow_id = if args_str.is_empty() {
801                        return Err(RuleEngineError::ParseError {
802                            message: "CompleteWorkflow requires workflow_id".to_string(),
803                        });
804                    } else {
805                        let value = self.parse_value(args_str.trim())?;
806                        match value {
807                            Value::String(s) => s,
808                            _ => value.to_string(),
809                        }
810                    };
811                    Ok(ActionType::CompleteWorkflow {
812                        workflow_name: workflow_id,
813                    })
814                }
815                "setworkflowdata" | "set_workflow_data" => {
816                    // Parse key=value: SetWorkflowData("key=value")
817                    let data_str = args_str.trim();
818
819                    // Simple key=value parsing
820                    let (key, value) = if let Some(eq_pos) = data_str.find('=') {
821                        let key = data_str[..eq_pos].trim().trim_matches('"');
822                        let value_str = data_str[eq_pos + 1..].trim();
823                        let value = self.parse_value(value_str)?;
824                        (key.to_string(), value)
825                    } else {
826                        return Err(RuleEngineError::ParseError {
827                            message: "SetWorkflowData data must be in key=value format".to_string(),
828                        });
829                    };
830
831                    Ok(ActionType::SetWorkflowData { key, value })
832                }
833                _ => {
834                    // All other functions become custom actions
835                    let params = if args_str.is_empty() {
836                        HashMap::new()
837                    } else {
838                        self.parse_function_args_as_params(args_str)?
839                    };
840
841                    Ok(ActionType::Custom {
842                        action_type: function_name.to_string(),
843                        params,
844                    })
845                }
846            }
847        } else {
848            // Custom statement
849            Ok(ActionType::Custom {
850                action_type: "statement".to_string(),
851                params: {
852                    let mut params = HashMap::new();
853                    params.insert("statement".to_string(), Value::String(trimmed.to_string()));
854                    params
855                },
856            })
857        }
858    }
859
860    fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
861        if args_str.trim().is_empty() {
862            return Ok(Vec::new());
863        }
864
865        // Handle expressions like: $TestCar.Speed + $TestCar.SpeedIncrement
866        let mut args = Vec::new();
867        let parts: Vec<&str> = args_str.split(',').collect();
868
869        for part in parts {
870            let trimmed = part.trim();
871
872            // Handle arithmetic expressions
873            if trimmed.contains('+')
874                || trimmed.contains('-')
875                || trimmed.contains('*')
876                || trimmed.contains('/')
877            {
878                // For now, store as string - the engine will evaluate
879                args.push(Value::String(trimmed.to_string()));
880            } else {
881                args.push(self.parse_value(trimmed)?);
882            }
883        }
884
885        Ok(args)
886    }
887
888    /// Parse function arguments as parameters for custom actions
889    fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
890        let mut params = HashMap::new();
891
892        if args_str.trim().is_empty() {
893            return Ok(params);
894        }
895
896        // Parse positional parameters as numbered args
897        let parts: Vec<&str> = args_str.split(',').collect();
898        for (i, part) in parts.iter().enumerate() {
899            let trimmed = part.trim();
900            let value = self.parse_value(trimmed)?;
901
902            // Use simple numeric indexing - engine will resolve references dynamically
903            params.insert(i.to_string(), value);
904        }
905
906        Ok(params)
907    }
908}
909
910#[cfg(test)]
911mod tests {
912    use super::GRLParser;
913
914    #[test]
915    fn test_parse_simple_rule() {
916        let grl = r#"
917        rule "CheckAge" salience 10 {
918            when
919                User.Age >= 18
920            then
921                log("User is adult");
922        }
923        "#;
924
925        let rules = GRLParser::parse_rules(grl).unwrap();
926        assert_eq!(rules.len(), 1);
927        let rule = &rules[0];
928        assert_eq!(rule.name, "CheckAge");
929        assert_eq!(rule.salience, 10);
930        assert_eq!(rule.actions.len(), 1);
931    }
932
933    #[test]
934    fn test_parse_complex_condition() {
935        let grl = r#"
936        rule "ComplexRule" {
937            when
938                User.Age >= 18 && User.Country == "US"
939            then
940                User.Qualified = true;
941        }
942        "#;
943
944        let rules = GRLParser::parse_rules(grl).unwrap();
945        assert_eq!(rules.len(), 1);
946        let rule = &rules[0];
947        assert_eq!(rule.name, "ComplexRule");
948    }
949
950    #[test]
951    fn test_parse_new_syntax_with_parentheses() {
952        let grl = r#"
953        rule "Default Rule" salience 10 {
954            when
955                (user.age >= 18)
956            then
957                set(user.status, "approved");
958        }
959        "#;
960
961        let rules = GRLParser::parse_rules(grl).unwrap();
962        assert_eq!(rules.len(), 1);
963        let rule = &rules[0];
964        assert_eq!(rule.name, "Default Rule");
965        assert_eq!(rule.salience, 10);
966        assert_eq!(rule.actions.len(), 1);
967
968        // Check that the action is parsed as a Custom action (set is now custom)
969        match &rule.actions[0] {
970            crate::types::ActionType::Custom {
971                action_type,
972                params,
973            } => {
974                assert_eq!(action_type, "set");
975                assert_eq!(
976                    params.get("0"),
977                    Some(&crate::types::Value::String("user.status".to_string()))
978                );
979                assert_eq!(
980                    params.get("1"),
981                    Some(&crate::types::Value::String("approved".to_string()))
982                );
983            }
984            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
985        }
986    }
987
988    #[test]
989    fn test_parse_complex_nested_conditions() {
990        let grl = r#"
991        rule "Complex Business Rule" salience 10 {
992            when
993                (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
994            then
995                apply_discount(20000);
996        }
997        "#;
998
999        let rules = GRLParser::parse_rules(grl).unwrap();
1000        assert_eq!(rules.len(), 1);
1001        let rule = &rules[0];
1002        assert_eq!(rule.name, "Complex Business Rule");
1003        assert_eq!(rule.salience, 10);
1004        assert_eq!(rule.actions.len(), 1);
1005
1006        // Check that the action is parsed as a Custom action (apply_discount is now custom)
1007        match &rule.actions[0] {
1008            crate::types::ActionType::Custom {
1009                action_type,
1010                params,
1011            } => {
1012                assert_eq!(action_type, "apply_discount");
1013                assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1014            }
1015            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1016        }
1017    }
1018
1019    #[test]
1020    fn test_parse_no_loop_attribute() {
1021        let grl = r#"
1022        rule "NoLoopRule" no-loop salience 15 {
1023            when
1024                User.Score < 100
1025            then
1026                set(User.Score, User.Score + 10);
1027        }
1028        "#;
1029
1030        let rules = GRLParser::parse_rules(grl).unwrap();
1031        assert_eq!(rules.len(), 1);
1032        let rule = &rules[0];
1033        assert_eq!(rule.name, "NoLoopRule");
1034        assert_eq!(rule.salience, 15);
1035        assert!(rule.no_loop, "Rule should have no-loop=true");
1036    }
1037
1038    #[test]
1039    fn test_parse_no_loop_different_positions() {
1040        // Test no-loop before salience
1041        let grl1 = r#"
1042        rule "Rule1" no-loop salience 10 {
1043            when User.Age >= 18
1044            then log("adult");
1045        }
1046        "#;
1047
1048        // Test no-loop after salience
1049        let grl2 = r#"
1050        rule "Rule2" salience 10 no-loop {
1051            when User.Age >= 18
1052            then log("adult");
1053        }
1054        "#;
1055
1056        let rules1 = GRLParser::parse_rules(grl1).unwrap();
1057        let rules2 = GRLParser::parse_rules(grl2).unwrap();
1058
1059        assert_eq!(rules1.len(), 1);
1060        assert_eq!(rules2.len(), 1);
1061
1062        assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1063        assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1064
1065        assert_eq!(rules1[0].salience, 10);
1066        assert_eq!(rules2[0].salience, 10);
1067    }
1068
1069    #[test]
1070    fn test_parse_without_no_loop() {
1071        let grl = r#"
1072        rule "RegularRule" salience 5 {
1073            when
1074                User.Active == true
1075            then
1076                log("active user");
1077        }
1078        "#;
1079
1080        let rules = GRLParser::parse_rules(grl).unwrap();
1081        assert_eq!(rules.len(), 1);
1082        let rule = &rules[0];
1083        assert_eq!(rule.name, "RegularRule");
1084        assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1085    }
1086
1087    #[test]
1088    fn test_parse_exists_pattern() {
1089        let grl = r#"
1090        rule "ExistsRule" salience 20 {
1091            when
1092                exists(Customer.tier == "VIP")
1093            then
1094                System.premiumActive = true;
1095        }
1096        "#;
1097
1098        let rules = GRLParser::parse_rules(grl).unwrap();
1099        assert_eq!(rules.len(), 1);
1100        let rule = &rules[0];
1101        assert_eq!(rule.name, "ExistsRule");
1102        assert_eq!(rule.salience, 20);
1103
1104        // Check that condition is EXISTS pattern
1105        match &rule.conditions {
1106            crate::engine::rule::ConditionGroup::Exists(_) => {
1107                // Test passes
1108            }
1109            _ => panic!(
1110                "Expected EXISTS condition group, got: {:?}",
1111                rule.conditions
1112            ),
1113        }
1114    }
1115
1116    #[test]
1117    fn test_parse_forall_pattern() {
1118        let grl = r#"
1119        rule "ForallRule" salience 15 {
1120            when
1121                forall(Order.status == "processed")
1122            then
1123                Shipping.enabled = true;
1124        }
1125        "#;
1126
1127        let rules = GRLParser::parse_rules(grl).unwrap();
1128        assert_eq!(rules.len(), 1);
1129        let rule = &rules[0];
1130        assert_eq!(rule.name, "ForallRule");
1131
1132        // Check that condition is FORALL pattern
1133        match &rule.conditions {
1134            crate::engine::rule::ConditionGroup::Forall(_) => {
1135                // Test passes
1136            }
1137            _ => panic!(
1138                "Expected FORALL condition group, got: {:?}",
1139                rule.conditions
1140            ),
1141        }
1142    }
1143
1144    #[test]
1145    fn test_parse_combined_patterns() {
1146        let grl = r#"
1147        rule "CombinedRule" salience 25 {
1148            when
1149                exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1150            then
1151                System.vipMode = true;
1152        }
1153        "#;
1154
1155        let rules = GRLParser::parse_rules(grl).unwrap();
1156        assert_eq!(rules.len(), 1);
1157        let rule = &rules[0];
1158        assert_eq!(rule.name, "CombinedRule");
1159
1160        // Check that condition is AND with EXISTS and NOT(EXISTS) patterns
1161        match &rule.conditions {
1162            crate::engine::rule::ConditionGroup::Compound {
1163                left,
1164                operator,
1165                right,
1166            } => {
1167                assert_eq!(*operator, crate::types::LogicalOperator::And);
1168
1169                // Left should be EXISTS
1170                match left.as_ref() {
1171                    crate::engine::rule::ConditionGroup::Exists(_) => {
1172                        // Expected
1173                    }
1174                    _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1175                }
1176
1177                // Right should be NOT(EXISTS)
1178                match right.as_ref() {
1179                    crate::engine::rule::ConditionGroup::Not(inner) => {
1180                        match inner.as_ref() {
1181                            crate::engine::rule::ConditionGroup::Exists(_) => {
1182                                // Expected
1183                            }
1184                            _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1185                        }
1186                    }
1187                    _ => panic!("Expected NOT in right side, got: {:?}", right),
1188                }
1189            }
1190            _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1191        }
1192    }
1193}