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 once_cell::sync::Lazy;
7use std::collections::HashMap;
8
9// Cached main regexes - compiled once at startup
10static RULE_REGEX: Lazy<Regex> = Lazy::new(|| {
11    Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
12        .expect("Invalid rule regex pattern")
13});
14
15static RULE_SPLIT_REGEX: Lazy<Regex> = Lazy::new(|| {
16    Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
17        .expect("Invalid rule split regex pattern")
18});
19
20static WHEN_THEN_REGEX: Lazy<Regex> = Lazy::new(|| {
21    Regex::new(r"when\s+(.+?)\s+then\s+(.+)")
22        .expect("Invalid when-then regex pattern")
23});
24
25static SALIENCE_REGEX: Lazy<Regex> = Lazy::new(|| {
26    Regex::new(r"salience\s+(\d+)")
27        .expect("Invalid salience regex pattern")
28});
29
30static TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
31    Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
32        .expect("Invalid test condition regex")
33});
34
35static TYPED_TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
36    Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
37        .expect("Invalid typed test condition regex")
38});
39
40static FUNCTION_CALL_REGEX: Lazy<Regex> = Lazy::new(|| {
41    Regex::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
42        .expect("Invalid function call regex")
43});
44
45static CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
46    Regex::new(r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*(?:\s*[+\-*/%]\s*[a-zA-Z0-9_\.]+)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
47        .expect("Invalid condition regex")
48});
49
50static METHOD_CALL_REGEX: Lazy<Regex> = Lazy::new(|| {
51    Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#)
52        .expect("Invalid method call regex")
53});
54
55static FUNCTION_BINDING_REGEX: Lazy<Regex> = Lazy::new(|| {
56    Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#)
57        .expect("Invalid function binding regex")
58});
59
60// Cached multifield patterns - called frequently during condition parsing
61static MULTIFIELD_COLLECT_REGEX: Lazy<Regex> = Lazy::new(|| {
62    Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
63        .expect("Invalid multifield collect regex")
64});
65
66static MULTIFIELD_COUNT_REGEX: Lazy<Regex> = Lazy::new(|| {
67    Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
68        .expect("Invalid multifield count regex")
69});
70
71static MULTIFIELD_FIRST_REGEX: Lazy<Regex> = Lazy::new(|| {
72    Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
73        .expect("Invalid multifield first regex")
74});
75
76static MULTIFIELD_LAST_REGEX: Lazy<Regex> = Lazy::new(|| {
77    Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
78        .expect("Invalid multifield last regex")
79});
80
81static MULTIFIELD_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
82    Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#)
83        .expect("Invalid multifield empty regex")
84});
85
86static MULTIFIELD_NOT_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
87    Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
88        .expect("Invalid multifield not_empty regex")
89});
90
91static SIMPLE_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
92    Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#)
93        .expect("Invalid simple condition regex")
94});
95
96/// GRL (Grule Rule Language) Parser
97/// Parses Grule-like syntax into Rule objects
98pub struct GRLParser;
99
100/// Parsed rule attributes from GRL header
101#[derive(Debug, Default)]
102struct RuleAttributes {
103    pub no_loop: bool,
104    pub lock_on_active: bool,
105    pub agenda_group: Option<String>,
106    pub activation_group: Option<String>,
107    pub date_effective: Option<DateTime<Utc>>,
108    pub date_expires: Option<DateTime<Utc>>,
109}
110
111impl GRLParser {
112    /// Parse a single rule from GRL syntax
113    ///
114    /// Example GRL syntax:
115    /// ```grl
116    /// rule CheckAge "Age verification rule" salience 10 {
117    ///     when
118    ///         User.Age >= 18 && User.Country == "US"
119    ///     then
120    ///         User.IsAdult = true;
121    ///         Retract("User");
122    /// }
123    /// ```
124    pub fn parse_rule(grl_text: &str) -> Result<Rule> {
125        let mut parser = GRLParser;
126        parser.parse_single_rule(grl_text)
127    }
128
129    /// Parse multiple rules from GRL text
130    pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
131        let mut parser = GRLParser;
132        parser.parse_multiple_rules(grl_text)
133    }
134
135    fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
136        let cleaned = self.clean_text(grl_text);
137
138        // Extract rule components using cached regex
139        let captures =
140            RULE_REGEX
141                .captures(&cleaned)
142                .ok_or_else(|| RuleEngineError::ParseError {
143                    message: format!("Invalid GRL rule format. Input: {}", cleaned),
144                })?;
145
146        // Rule name can be either quoted (group 1) or unquoted (group 2)
147        let rule_name = if let Some(quoted_name) = captures.get(1) {
148            quoted_name.as_str().to_string()
149        } else if let Some(unquoted_name) = captures.get(2) {
150            unquoted_name.as_str().to_string()
151        } else {
152            return Err(RuleEngineError::ParseError {
153                message: "Could not extract rule name".to_string(),
154            });
155        };
156
157        // Attributes section (group 3)
158        let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
159
160        // Rule body (group 4)
161        let rule_body = captures.get(4).unwrap().as_str();
162
163        // Parse salience from attributes section
164        let salience = self.extract_salience(attributes_section)?;
165
166        // Parse when and then sections using cached regex
167        let when_then_captures =
168            WHEN_THEN_REGEX
169                .captures(rule_body)
170                .ok_or_else(|| RuleEngineError::ParseError {
171                    message: "Missing when or then clause".to_string(),
172                })?;
173
174        let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
175        let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
176
177        // Parse conditions and actions
178        let conditions = self.parse_when_clause(when_clause)?;
179        let actions = self.parse_then_clause(then_clause)?;
180
181        // Parse all attributes from rule header
182        let attributes = self.parse_rule_attributes(attributes_section)?;
183
184        // Build rule
185        let mut rule = Rule::new(rule_name, conditions, actions);
186        rule = rule.with_priority(salience);
187
188        // Apply parsed attributes
189        if attributes.no_loop {
190            rule = rule.with_no_loop(true);
191        }
192        if attributes.lock_on_active {
193            rule = rule.with_lock_on_active(true);
194        }
195        if let Some(agenda_group) = attributes.agenda_group {
196            rule = rule.with_agenda_group(agenda_group);
197        }
198        if let Some(activation_group) = attributes.activation_group {
199            rule = rule.with_activation_group(activation_group);
200        }
201        if let Some(date_effective) = attributes.date_effective {
202            rule = rule.with_date_effective(date_effective);
203        }
204        if let Some(date_expires) = attributes.date_expires {
205            rule = rule.with_date_expires(date_expires);
206        }
207
208        Ok(rule)
209    }
210
211    fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
212        // Split by rule boundaries - support both quoted and unquoted rule names
213        // Use DOTALL flag to match newlines in rule body
214        let mut rules = Vec::new();
215
216        for rule_match in RULE_SPLIT_REGEX.find_iter(grl_text) {
217            let rule_text = rule_match.as_str();
218            let rule = self.parse_single_rule(rule_text)?;
219            rules.push(rule);
220        }
221
222        Ok(rules)
223    }
224
225    /// Parse rule attributes from the rule header
226    fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
227        let mut attributes = RuleAttributes::default();
228
229        // Extract the attributes section (after rule name/description, before opening brace)
230        // This ensures we don't match keywords inside description strings
231        // Strategy: Find all quoted strings and remove them, then check for attributes
232        let mut attrs_section = rule_header.to_string();
233        
234        // Remove all quoted strings (descriptions) to avoid false matches
235        let quoted_regex = Regex::new(r#""[^"]*""#).map_err(|e| RuleEngineError::ParseError {
236            message: format!("Invalid quoted string regex: {}", e),
237        })?;
238        attrs_section = quoted_regex.replace_all(&attrs_section, "").to_string();
239        
240        // Also remove the "rule" keyword and rule name (if unquoted)
241        if let Some(rule_pos) = attrs_section.find("rule") {
242            // Find the next space or attribute keyword after "rule"
243            let after_rule = &attrs_section[rule_pos + 4..];
244            if let Some(first_keyword) = after_rule.find("salience")
245                .or_else(|| after_rule.find("no-loop"))
246                .or_else(|| after_rule.find("lock-on-active"))
247                .or_else(|| after_rule.find("agenda-group"))
248                .or_else(|| after_rule.find("activation-group"))
249                .or_else(|| after_rule.find("date-effective"))
250                .or_else(|| after_rule.find("date-expires"))
251            {
252                attrs_section = after_rule[first_keyword..].to_string();
253            }
254        }
255        
256        // Now check for boolean attributes using word boundaries
257        let no_loop_regex = Regex::new(r"\bno-loop\b").map_err(|e| RuleEngineError::ParseError {
258            message: format!("Invalid no-loop regex: {}", e),
259        })?;
260        let lock_on_active_regex = Regex::new(r"\block-on-active\b").map_err(|e| RuleEngineError::ParseError {
261            message: format!("Invalid lock-on-active regex: {}", e),
262        })?;
263        
264        if no_loop_regex.is_match(&attrs_section) {
265            attributes.no_loop = true;
266        }
267        if lock_on_active_regex.is_match(&attrs_section) {
268            attributes.lock_on_active = true;
269        }
270
271        // Parse agenda-group attribute
272        if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
273            attributes.agenda_group = Some(agenda_group);
274        }
275
276        // Parse activation-group attribute
277        if let Some(activation_group) =
278            self.extract_quoted_attribute(rule_header, "activation-group")?
279        {
280            attributes.activation_group = Some(activation_group);
281        }
282
283        // Parse date-effective attribute
284        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
285            attributes.date_effective = Some(self.parse_date_string(&date_str)?);
286        }
287
288        // Parse date-expires attribute
289        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
290            attributes.date_expires = Some(self.parse_date_string(&date_str)?);
291        }
292
293        Ok(attributes)
294    }
295
296    /// Extract quoted attribute value from rule header
297    fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
298        let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
299        let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
300            message: format!("Invalid attribute regex for {}: {}", attribute, e),
301        })?;
302
303        if let Some(captures) = regex.captures(header) {
304            if let Some(value) = captures.get(1) {
305                return Ok(Some(value.as_str().to_string()));
306            }
307        }
308
309        Ok(None)
310    }
311
312    /// Parse date string in various formats
313    fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
314        // Try ISO 8601 format first
315        if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
316            return Ok(date.with_timezone(&Utc));
317        }
318
319        // Try simple date formats
320        let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
321
322        for format in &formats {
323            if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
324                return Ok(naive_date.and_utc());
325            }
326            if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
327                return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
328            }
329        }
330
331        Err(RuleEngineError::ParseError {
332            message: format!("Unable to parse date: {}", date_str),
333        })
334    }
335
336    /// Extract salience value from attributes section
337    fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
338        if let Some(captures) = SALIENCE_REGEX.captures(attributes_section) {
339            if let Some(salience_match) = captures.get(1) {
340                return salience_match.as_str().parse::<i32>().map_err(|e| {
341                    RuleEngineError::ParseError {
342                        message: format!("Invalid salience value: {}", e),
343                    }
344                });
345            }
346        }
347
348        Ok(0) // Default salience
349    }
350
351    fn clean_text(&self, text: &str) -> String {
352        text.lines()
353            .map(|line| line.trim())
354            .filter(|line| !line.is_empty() && !line.starts_with("//"))
355            .collect::<Vec<_>>()
356            .join(" ")
357    }
358
359    fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
360        // Handle logical operators with proper parentheses support
361        let trimmed = when_clause.trim();
362
363        // Strip outer parentheses if they exist
364        let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
365            // Check if these are the outermost parentheses
366            let inner = &trimmed[1..trimmed.len() - 1];
367            if self.is_balanced_parentheses(inner) {
368                inner
369            } else {
370                trimmed
371            }
372        } else {
373            trimmed
374        };
375
376        // Parse OR at the top level (lowest precedence)
377        if let Some(parts) = self.split_logical_operator(clause, "||") {
378            return self.parse_or_parts(parts);
379        }
380
381        // Parse AND (higher precedence)
382        if let Some(parts) = self.split_logical_operator(clause, "&&") {
383            return self.parse_and_parts(parts);
384        }
385
386        // Handle NOT condition
387        if clause.trim_start().starts_with("!") {
388            return self.parse_not_condition(clause);
389        }
390
391        // Handle EXISTS condition
392        if clause.trim_start().starts_with("exists(") {
393            return self.parse_exists_condition(clause);
394        }
395
396        // Handle FORALL condition
397        if clause.trim_start().starts_with("forall(") {
398            return self.parse_forall_condition(clause);
399        }
400
401        // Handle ACCUMULATE condition
402        if clause.trim_start().starts_with("accumulate(") {
403            return self.parse_accumulate_condition(clause);
404        }
405
406        // Single condition
407        self.parse_single_condition(clause)
408    }
409
410    fn is_balanced_parentheses(&self, text: &str) -> bool {
411        let mut count = 0;
412        for ch in text.chars() {
413            match ch {
414                '(' => count += 1,
415                ')' => {
416                    count -= 1;
417                    if count < 0 {
418                        return false;
419                    }
420                }
421                _ => {}
422            }
423        }
424        count == 0
425    }
426
427    fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
428        let mut parts = Vec::new();
429        let mut current_part = String::new();
430        let mut paren_count = 0;
431        let mut chars = clause.chars().peekable();
432
433        while let Some(ch) = chars.next() {
434            match ch {
435                '(' => {
436                    paren_count += 1;
437                    current_part.push(ch);
438                }
439                ')' => {
440                    paren_count -= 1;
441                    current_part.push(ch);
442                }
443                '&' if operator == "&&" && paren_count == 0 => {
444                    if chars.peek() == Some(&'&') {
445                        chars.next(); // consume second &
446                        parts.push(current_part.trim().to_string());
447                        current_part.clear();
448                    } else {
449                        current_part.push(ch);
450                    }
451                }
452                '|' if operator == "||" && paren_count == 0 => {
453                    if chars.peek() == Some(&'|') {
454                        chars.next(); // consume second |
455                        parts.push(current_part.trim().to_string());
456                        current_part.clear();
457                    } else {
458                        current_part.push(ch);
459                    }
460                }
461                _ => {
462                    current_part.push(ch);
463                }
464            }
465        }
466
467        if !current_part.trim().is_empty() {
468            parts.push(current_part.trim().to_string());
469        }
470
471        if parts.len() > 1 {
472            Some(parts)
473        } else {
474            None
475        }
476    }
477
478    fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
479        let mut conditions = Vec::new();
480        for part in parts {
481            let condition = self.parse_when_clause(&part)?;
482            conditions.push(condition);
483        }
484
485        if conditions.is_empty() {
486            return Err(RuleEngineError::ParseError {
487                message: "No conditions found in OR".to_string(),
488            });
489        }
490
491        let mut iter = conditions.into_iter();
492        let mut result = iter.next().unwrap();
493        for condition in iter {
494            result = ConditionGroup::or(result, condition);
495        }
496
497        Ok(result)
498    }
499
500    fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
501        let mut conditions = Vec::new();
502        for part in parts {
503            let condition = self.parse_when_clause(&part)?;
504            conditions.push(condition);
505        }
506
507        if conditions.is_empty() {
508            return Err(RuleEngineError::ParseError {
509                message: "No conditions found in AND".to_string(),
510            });
511        }
512
513        let mut iter = conditions.into_iter();
514        let mut result = iter.next().unwrap();
515        for condition in iter {
516            result = ConditionGroup::and(result, condition);
517        }
518
519        Ok(result)
520    }
521
522    fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
523        let inner_clause = clause.strip_prefix("!").unwrap().trim();
524        let inner_condition = self.parse_when_clause(inner_clause)?;
525        Ok(ConditionGroup::not(inner_condition))
526    }
527
528    fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
529        let clause = clause.trim_start();
530        if !clause.starts_with("exists(") || !clause.ends_with(")") {
531            return Err(RuleEngineError::ParseError {
532                message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
533            });
534        }
535
536        // Extract content between parentheses
537        let inner_clause = &clause[7..clause.len() - 1]; // Remove "exists(" and ")"
538        let inner_condition = self.parse_when_clause(inner_clause)?;
539        Ok(ConditionGroup::exists(inner_condition))
540    }
541
542    fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
543        let clause = clause.trim_start();
544        if !clause.starts_with("forall(") || !clause.ends_with(")") {
545            return Err(RuleEngineError::ParseError {
546                message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
547            });
548        }
549
550        // Extract content between parentheses
551        let inner_clause = &clause[7..clause.len() - 1]; // Remove "forall(" and ")"
552        let inner_condition = self.parse_when_clause(inner_clause)?;
553        Ok(ConditionGroup::forall(inner_condition))
554    }
555
556    fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
557        let clause = clause.trim_start();
558        if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
559            return Err(RuleEngineError::ParseError {
560                message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)".to_string(),
561            });
562        }
563
564        // Extract content between parentheses
565        let inner = &clause[11..clause.len() - 1]; // Remove "accumulate(" and ")"
566
567        // Split by comma at the top level (not inside parentheses)
568        let parts = self.split_accumulate_parts(inner)?;
569
570        if parts.len() != 2 {
571            return Err(RuleEngineError::ParseError {
572                message: format!(
573                    "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
574                    parts.len()
575                ),
576            });
577        }
578
579        let pattern_part = parts[0].trim();
580        let function_part = parts[1].trim();
581
582        // Parse the pattern: Order($amount: amount, status == "completed")
583        let (source_pattern, extract_field, source_conditions) =
584            self.parse_accumulate_pattern(pattern_part)?;
585
586        // Parse the function: sum($amount)
587        let (function, function_arg) = self.parse_accumulate_function(function_part)?;
588
589        // For now, we'll create a placeholder result variable
590        // In a full implementation, this would be extracted from the parent context
591        // e.g., from "$total: accumulate(...)"
592        let result_var = "$result".to_string();
593
594        Ok(ConditionGroup::accumulate(
595            result_var,
596            source_pattern,
597            extract_field,
598            source_conditions,
599            function,
600            function_arg,
601        ))
602    }
603
604    fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
605        let mut parts = Vec::new();
606        let mut current = String::new();
607        let mut paren_depth = 0;
608
609        for ch in content.chars() {
610            match ch {
611                '(' => {
612                    paren_depth += 1;
613                    current.push(ch);
614                }
615                ')' => {
616                    paren_depth -= 1;
617                    current.push(ch);
618                }
619                ',' if paren_depth == 0 => {
620                    parts.push(current.trim().to_string());
621                    current.clear();
622                }
623                _ => {
624                    current.push(ch);
625                }
626            }
627        }
628
629        if !current.trim().is_empty() {
630            parts.push(current.trim().to_string());
631        }
632
633        Ok(parts)
634    }
635
636    fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
637        // Pattern format: Order($amount: amount, status == "completed", category == "electronics")
638        // We need to extract:
639        // - source_pattern: "Order"
640        // - extract_field: "amount" (from $amount: amount)
641        // - source_conditions: ["status == \"completed\"", "category == \"electronics\""]
642
643        let pattern = pattern.trim();
644
645        // Find the opening parenthesis to get the pattern type
646        let paren_pos = pattern.find('(').ok_or_else(|| RuleEngineError::ParseError {
647            message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
648        })?;
649
650        let source_pattern = pattern[..paren_pos].trim().to_string();
651
652        // Extract content between parentheses
653        if !pattern.ends_with(')') {
654            return Err(RuleEngineError::ParseError {
655                message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
656            });
657        }
658
659        let inner = &pattern[paren_pos + 1..pattern.len() - 1];
660
661        // Split by comma (respecting nested parentheses and quotes)
662        let parts = self.split_pattern_parts(inner)?;
663
664        let mut extract_field = String::new();
665        let mut source_conditions = Vec::new();
666
667        for part in parts {
668            let part = part.trim();
669
670            // Check if this is a variable binding: $var: field
671            if part.contains(':') && part.starts_with('$') {
672                let colon_pos = part.find(':').unwrap();
673                let _var_name = part[..colon_pos].trim();
674                let field_name = part[colon_pos + 1..].trim();
675                extract_field = field_name.to_string();
676            } else if part.contains("==") || part.contains("!=") ||
677                      part.contains(">=") || part.contains("<=") ||
678                      part.contains('>') || part.contains('<') {
679                // This is a condition
680                source_conditions.push(part.to_string());
681            }
682        }
683
684        Ok((source_pattern, extract_field, source_conditions))
685    }
686
687    fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
688        let mut parts = Vec::new();
689        let mut current = String::new();
690        let mut paren_depth = 0;
691        let mut in_quotes = false;
692        let mut quote_char = ' ';
693
694        for ch in content.chars() {
695            match ch {
696                '"' | '\'' if !in_quotes => {
697                    in_quotes = true;
698                    quote_char = ch;
699                    current.push(ch);
700                }
701                '"' | '\'' if in_quotes && ch == quote_char => {
702                    in_quotes = false;
703                    current.push(ch);
704                }
705                '(' if !in_quotes => {
706                    paren_depth += 1;
707                    current.push(ch);
708                }
709                ')' if !in_quotes => {
710                    paren_depth -= 1;
711                    current.push(ch);
712                }
713                ',' if !in_quotes && paren_depth == 0 => {
714                    parts.push(current.trim().to_string());
715                    current.clear();
716                }
717                _ => {
718                    current.push(ch);
719                }
720            }
721        }
722
723        if !current.trim().is_empty() {
724            parts.push(current.trim().to_string());
725        }
726
727        Ok(parts)
728    }
729
730    fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
731        // Function format: sum($amount) or count() or average($price)
732
733        let function_str = function_str.trim();
734
735        let paren_pos = function_str.find('(').ok_or_else(|| RuleEngineError::ParseError {
736            message: format!("Invalid accumulate function: missing '(' in '{}'", function_str),
737        })?;
738
739        let function_name = function_str[..paren_pos].trim().to_string();
740
741        if !function_str.ends_with(')') {
742            return Err(RuleEngineError::ParseError {
743                message: format!("Invalid accumulate function: missing ')' in '{}'", function_str),
744            });
745        }
746
747        let args = &function_str[paren_pos + 1..function_str.len() - 1];
748        let function_arg = args.trim().to_string();
749
750        Ok((function_name, function_arg))
751    }
752
753    fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
754        // Remove outer parentheses if they exist (handle new syntax like "(user.age >= 18)")
755        let trimmed_clause = clause.trim();
756        let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
757            trimmed_clause[1..trimmed_clause.len() - 1].trim()
758        } else {
759            trimmed_clause
760        };
761
762        // === MULTI-FIELD PATTERNS ===
763        // Handle multi-field patterns before other patterns
764        // These must be checked first to avoid conflict with standard patterns
765
766        // Pattern 1: Field.array $?var (Collect operation with variable binding)
767        // Example: Order.items $?all_items
768        if let Some(captures) = MULTIFIELD_COLLECT_REGEX.captures(clause_to_parse) {
769            let field = captures.get(1).unwrap().as_str().to_string();
770            let variable = captures.get(2).unwrap().as_str().to_string();
771
772            // Create a multifield Collect condition
773            // Note: This will need to be handled by the engine
774            let condition = Condition::with_multifield_collect(field, variable);
775            return Ok(ConditionGroup::single(condition));
776        }
777
778        // Pattern 2: Field.array contains "value"
779        // Example: Product.tags contains "electronics"
780        // This is already handled by the standard regex, but we need to distinguish array contains
781
782        // Pattern 3: Field.array count operator value
783        // Example: Order.items count > 0, Order.items count >= 5
784        if let Some(captures) = MULTIFIELD_COUNT_REGEX.captures(clause_to_parse) {
785            let field = captures.get(1).unwrap().as_str().to_string();
786            let operator_str = captures.get(2).unwrap().as_str();
787            let value_str = captures.get(3).unwrap().as_str().trim();
788
789            let operator = Operator::from_str(operator_str)
790                .ok_or_else(|| RuleEngineError::InvalidOperator {
791                    operator: operator_str.to_string(),
792                })?;
793
794            let value = self.parse_value(value_str)?;
795
796            let condition = Condition::with_multifield_count(field, operator, value);
797            return Ok(ConditionGroup::single(condition));
798        }
799
800        // Pattern 4: Field.array first [optional: $var or operator value]
801        // Example: Queue.tasks first, Queue.tasks first $first_task
802        if let Some(captures) = MULTIFIELD_FIRST_REGEX.captures(clause_to_parse) {
803            let field = captures.get(1).unwrap().as_str().to_string();
804            let variable = captures.get(2).map(|m| m.as_str().to_string());
805
806            let condition = Condition::with_multifield_first(field, variable);
807            return Ok(ConditionGroup::single(condition));
808        }
809
810        // Pattern 5: Field.array last [optional: $var]
811        // Example: Queue.tasks last, Queue.tasks last $last_task
812        if let Some(captures) = MULTIFIELD_LAST_REGEX.captures(clause_to_parse) {
813            let field = captures.get(1).unwrap().as_str().to_string();
814            let variable = captures.get(2).map(|m| m.as_str().to_string());
815
816            let condition = Condition::with_multifield_last(field, variable);
817            return Ok(ConditionGroup::single(condition));
818        }
819
820        // Pattern 6: Field.array empty
821        // Example: ShoppingCart.items empty
822        if let Some(captures) = MULTIFIELD_EMPTY_REGEX.captures(clause_to_parse) {
823            let field = captures.get(1).unwrap().as_str().to_string();
824
825            let condition = Condition::with_multifield_empty(field);
826            return Ok(ConditionGroup::single(condition));
827        }
828
829        // Pattern 7: Field.array not_empty
830        // Example: ShoppingCart.items not_empty
831        if let Some(captures) = MULTIFIELD_NOT_EMPTY_REGEX.captures(clause_to_parse) {
832            let field = captures.get(1).unwrap().as_str().to_string();
833
834            let condition = Condition::with_multifield_not_empty(field);
835            return Ok(ConditionGroup::single(condition));
836        }
837
838        // === END MULTI-FIELD PATTERNS ===
839
840        // Handle Test CE: test(functionName(args...))
841        // This is a CLIPS-inspired feature for arbitrary boolean expressions
842        if let Some(captures) = TEST_CONDITION_REGEX.captures(clause_to_parse) {
843            let function_name = captures.get(1).unwrap().as_str().to_string();
844            let args_str = captures.get(2).unwrap().as_str();
845
846            // Parse arguments
847            let args: Vec<String> = if args_str.trim().is_empty() {
848                Vec::new()
849            } else {
850                args_str
851                    .split(',')
852                    .map(|arg| arg.trim().to_string())
853                    .collect()
854            };
855
856            let condition = Condition::with_test(function_name, args);
857            return Ok(ConditionGroup::single(condition));
858        }
859
860        // Handle typed object conditions like: $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
861        if let Some(captures) = TYPED_TEST_CONDITION_REGEX.captures(clause_to_parse) {
862            let _object_name = captures.get(1).unwrap().as_str();
863            let _object_type = captures.get(2).unwrap().as_str();
864            let conditions_str = captures.get(3).unwrap().as_str();
865
866            // Parse conditions inside parentheses
867            return self.parse_conditions_within_object(conditions_str);
868        }
869
870        // Try to parse function call pattern: functionName(arg1, arg2, ...) operator value
871        if let Some(captures) = FUNCTION_CALL_REGEX.captures(clause_to_parse) {
872            let function_name = captures.get(1).unwrap().as_str().to_string();
873            let args_str = captures.get(2).unwrap().as_str();
874            let operator_str = captures.get(3).unwrap().as_str();
875            let value_str = captures.get(4).unwrap().as_str().trim();
876
877            // Parse arguments
878            let args: Vec<String> = if args_str.trim().is_empty() {
879                Vec::new()
880            } else {
881                args_str
882                    .split(',')
883                    .map(|arg| arg.trim().to_string())
884                    .collect()
885            };
886
887            let operator =
888                Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
889                    operator: operator_str.to_string(),
890                })?;
891
892            let value = self.parse_value(value_str)?;
893
894            let condition = Condition::with_function(function_name, args, operator, value);
895            return Ok(ConditionGroup::single(condition));
896        }
897
898        // Parse expressions like: User.Age >= 18, Product.Price < 100.0, user.age >= 18, etc.
899        // Support both PascalCase (User.Age) and lowercase (user.age) field naming
900        // Also support arithmetic expressions like: User.Age % 3 == 0, User.Price * 2 > 100
901        let captures = CONDITION_REGEX.captures(clause_to_parse).ok_or_else(|| {
902            RuleEngineError::ParseError {
903                message: format!("Invalid condition format: {}", clause_to_parse),
904            }
905        })?;
906
907        let left_side = captures.get(1).unwrap().as_str().trim().to_string();
908        let operator_str = captures.get(2).unwrap().as_str();
909        let value_str = captures.get(3).unwrap().as_str().trim();
910
911        let operator =
912            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
913                operator: operator_str.to_string(),
914            })?;
915
916        let value = self.parse_value(value_str)?;
917
918        // Check if left_side contains arithmetic operators - if yes, it's an expression
919        if left_side.contains('+') || left_side.contains('-') || left_side.contains('*') 
920            || left_side.contains('/') || left_side.contains('%') {
921            // This is an arithmetic expression - use Test CE
922            // Format: test(left_side operator value)
923            let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
924            let condition = Condition::with_test(test_expr, vec![]);
925            Ok(ConditionGroup::single(condition))
926        } else {
927            // Simple field reference
928            let condition = Condition::new(left_side, operator, value);
929            Ok(ConditionGroup::single(condition))
930        }
931    }
932
933    fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
934        // Parse conditions like: speedUp == true && speed < maxSpeed
935        let parts: Vec<&str> = conditions_str.split("&&").collect();
936
937        let mut conditions = Vec::new();
938        for part in parts {
939            let trimmed = part.trim();
940            let condition = self.parse_simple_condition(trimmed)?;
941            conditions.push(condition);
942        }
943
944        // Combine with AND
945        if conditions.is_empty() {
946            return Err(RuleEngineError::ParseError {
947                message: "No conditions found".to_string(),
948            });
949        }
950
951        let mut iter = conditions.into_iter();
952        let mut result = iter.next().unwrap();
953        for condition in iter {
954            result = ConditionGroup::and(result, condition);
955        }
956
957        Ok(result)
958    }
959
960    fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
961        // Parse simple condition like: speedUp == true or speed < maxSpeed
962        let captures =
963            SIMPLE_CONDITION_REGEX
964                .captures(clause)
965                .ok_or_else(|| RuleEngineError::ParseError {
966                    message: format!("Invalid simple condition format: {}", clause),
967                })?;
968
969        let field = captures.get(1).unwrap().as_str().to_string();
970        let operator_str = captures.get(2).unwrap().as_str();
971        let value_str = captures.get(3).unwrap().as_str().trim();
972
973        let operator =
974            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
975                operator: operator_str.to_string(),
976            })?;
977
978        let value = self.parse_value(value_str)?;
979
980        let condition = Condition::new(field, operator, value);
981        Ok(ConditionGroup::single(condition))
982    }
983
984    fn parse_value(&self, value_str: &str) -> Result<Value> {
985        let trimmed = value_str.trim();
986
987        // String literal
988        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
989            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
990        {
991            let unquoted = &trimmed[1..trimmed.len() - 1];
992            return Ok(Value::String(unquoted.to_string()));
993        }
994
995        // Boolean
996        if trimmed.eq_ignore_ascii_case("true") {
997            return Ok(Value::Boolean(true));
998        }
999        if trimmed.eq_ignore_ascii_case("false") {
1000            return Ok(Value::Boolean(false));
1001        }
1002
1003        // Null
1004        if trimmed.eq_ignore_ascii_case("null") {
1005            return Ok(Value::Null);
1006        }
1007
1008        // Number (try integer first, then float)
1009        if let Ok(int_val) = trimmed.parse::<i64>() {
1010            return Ok(Value::Integer(int_val));
1011        }
1012
1013        if let Ok(float_val) = trimmed.parse::<f64>() {
1014            return Ok(Value::Number(float_val));
1015        }
1016
1017        // Expression with arithmetic operators (e.g., "Order.quantity * Order.price")
1018        // Detect: contains operators AND (contains field reference OR multiple tokens)
1019        if self.is_expression(trimmed) {
1020            return Ok(Value::Expression(trimmed.to_string()));
1021        }
1022
1023        // Field reference (like User.Name)
1024        if trimmed.contains('.') {
1025            return Ok(Value::String(trimmed.to_string()));
1026        }
1027
1028        // Variable reference (identifier without quotes or dots)
1029        // This handles cases like: order_qty = moq
1030        // where 'moq' should be evaluated as a variable reference at runtime
1031        if self.is_identifier(trimmed) {
1032            return Ok(Value::Expression(trimmed.to_string()));
1033        }
1034
1035        // Default to string
1036        Ok(Value::String(trimmed.to_string()))
1037    }
1038
1039    /// Check if a string is a valid identifier (variable name)
1040    /// Valid identifiers: alphanumeric + underscore, starts with letter or underscore
1041    fn is_identifier(&self, s: &str) -> bool {
1042        if s.is_empty() {
1043            return false;
1044        }
1045
1046        // First character must be letter or underscore
1047        let first_char = s.chars().next().unwrap();
1048        if !first_char.is_alphabetic() && first_char != '_' {
1049            return false;
1050        }
1051
1052        // Rest must be alphanumeric or underscore
1053        s.chars().all(|c| c.is_alphanumeric() || c == '_')
1054    }
1055
1056    /// Check if a string is an arithmetic expression
1057    fn is_expression(&self, s: &str) -> bool {
1058        // Check for arithmetic operators
1059        let has_operator = s.contains('+') || s.contains('-') || s.contains('*') || s.contains('/') || s.contains('%');
1060
1061        // Check for field references (contains .)
1062        let has_field_ref = s.contains('.');
1063
1064        // Check for multiple tokens (spaces between operands/operators)
1065        let has_spaces = s.contains(' ');
1066
1067        // Expression if: has operator AND (has field reference OR has spaces)
1068        has_operator && (has_field_ref || has_spaces)
1069    }
1070
1071    fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1072        let statements: Vec<&str> = then_clause
1073            .split(';')
1074            .map(|s| s.trim())
1075            .filter(|s| !s.is_empty())
1076            .collect();
1077
1078        let mut actions = Vec::new();
1079
1080        for statement in statements {
1081            let action = self.parse_action_statement(statement)?;
1082            actions.push(action);
1083        }
1084
1085        Ok(actions)
1086    }
1087
1088    fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1089        let trimmed = statement.trim();
1090
1091        // Method call: $Object.method(args)
1092        if let Some(captures) = METHOD_CALL_REGEX.captures(trimmed) {
1093            let object = captures.get(1).unwrap().as_str().to_string();
1094            let method = captures.get(2).unwrap().as_str().to_string();
1095            let args_str = captures.get(3).unwrap().as_str();
1096
1097            let args = if args_str.trim().is_empty() {
1098                Vec::new()
1099            } else {
1100                self.parse_method_args(args_str)?
1101            };
1102
1103            return Ok(ActionType::MethodCall {
1104                object,
1105                method,
1106                args,
1107            });
1108        }
1109
1110        // Assignment: Field = Value
1111        if let Some(eq_pos) = trimmed.find('=') {
1112            let field = trimmed[..eq_pos].trim().to_string();
1113            let value_str = trimmed[eq_pos + 1..].trim();
1114            let value = self.parse_value(value_str)?;
1115
1116            return Ok(ActionType::Set { field, value });
1117        }
1118
1119        // Function calls: update($Object), retract($Object), etc.
1120        if let Some(captures) = FUNCTION_BINDING_REGEX.captures(trimmed) {
1121            let function_name = captures.get(1).unwrap().as_str();
1122            let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
1123
1124            match function_name.to_lowercase().as_str() {
1125                "retract" => {
1126                    // Extract object name from $Object
1127                    let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1128                        stripped.to_string()
1129                    } else {
1130                        args_str.to_string()
1131                    };
1132                    Ok(ActionType::Retract {
1133                        object: object_name,
1134                    })
1135                }
1136                "log" => {
1137                    let message = if args_str.is_empty() {
1138                        "Log message".to_string()
1139                    } else {
1140                        let value = self.parse_value(args_str.trim())?;
1141                        value.to_string()
1142                    };
1143                    Ok(ActionType::Log { message })
1144                }
1145                "activateagendagroup" | "activate_agenda_group" => {
1146                    let agenda_group = if args_str.is_empty() {
1147                        return Err(RuleEngineError::ParseError {
1148                            message: "ActivateAgendaGroup requires agenda group name".to_string(),
1149                        });
1150                    } else {
1151                        let value = self.parse_value(args_str.trim())?;
1152                        match value {
1153                            Value::String(s) => s,
1154                            _ => value.to_string(),
1155                        }
1156                    };
1157                    Ok(ActionType::ActivateAgendaGroup {
1158                        group: agenda_group,
1159                    })
1160                }
1161                "schedulerule" | "schedule_rule" => {
1162                    // Parse delay and target rule: ScheduleRule(5000, "next-rule")
1163                    let parts: Vec<&str> = args_str.split(',').collect();
1164                    if parts.len() != 2 {
1165                        return Err(RuleEngineError::ParseError {
1166                            message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1167                        });
1168                    }
1169
1170                    let delay_ms = self.parse_value(parts[0].trim())?;
1171                    let rule_name = self.parse_value(parts[1].trim())?;
1172
1173                    let delay_ms = match delay_ms {
1174                        Value::Integer(i) => i as u64,
1175                        Value::Number(f) => f as u64,
1176                        _ => {
1177                            return Err(RuleEngineError::ParseError {
1178                                message: "ScheduleRule delay_ms must be a number".to_string(),
1179                            })
1180                        }
1181                    };
1182
1183                    let rule_name = match rule_name {
1184                        Value::String(s) => s,
1185                        _ => rule_name.to_string(),
1186                    };
1187
1188                    Ok(ActionType::ScheduleRule {
1189                        delay_ms,
1190                        rule_name,
1191                    })
1192                }
1193                "completeworkflow" | "complete_workflow" => {
1194                    let workflow_id = if args_str.is_empty() {
1195                        return Err(RuleEngineError::ParseError {
1196                            message: "CompleteWorkflow requires workflow_id".to_string(),
1197                        });
1198                    } else {
1199                        let value = self.parse_value(args_str.trim())?;
1200                        match value {
1201                            Value::String(s) => s,
1202                            _ => value.to_string(),
1203                        }
1204                    };
1205                    Ok(ActionType::CompleteWorkflow {
1206                        workflow_name: workflow_id,
1207                    })
1208                }
1209                "setworkflowdata" | "set_workflow_data" => {
1210                    // Parse key=value: SetWorkflowData("key=value")
1211                    let data_str = args_str.trim();
1212
1213                    // Simple key=value parsing
1214                    let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1215                        let key = data_str[..eq_pos].trim().trim_matches('"');
1216                        let value_str = data_str[eq_pos + 1..].trim();
1217                        let value = self.parse_value(value_str)?;
1218                        (key.to_string(), value)
1219                    } else {
1220                        return Err(RuleEngineError::ParseError {
1221                            message: "SetWorkflowData data must be in key=value format".to_string(),
1222                        });
1223                    };
1224
1225                    Ok(ActionType::SetWorkflowData { key, value })
1226                }
1227                _ => {
1228                    // All other functions become custom actions
1229                    let params = if args_str.is_empty() {
1230                        HashMap::new()
1231                    } else {
1232                        self.parse_function_args_as_params(args_str)?
1233                    };
1234
1235                    Ok(ActionType::Custom {
1236                        action_type: function_name.to_string(),
1237                        params,
1238                    })
1239                }
1240            }
1241        } else {
1242            // Custom statement
1243            Ok(ActionType::Custom {
1244                action_type: "statement".to_string(),
1245                params: {
1246                    let mut params = HashMap::new();
1247                    params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1248                    params
1249                },
1250            })
1251        }
1252    }
1253
1254    fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1255        if args_str.trim().is_empty() {
1256            return Ok(Vec::new());
1257        }
1258
1259        // Handle expressions like: $TestCar.Speed + $TestCar.SpeedIncrement
1260        let mut args = Vec::new();
1261        let parts: Vec<&str> = args_str.split(',').collect();
1262
1263        for part in parts {
1264            let trimmed = part.trim();
1265
1266            // Handle arithmetic expressions
1267            if trimmed.contains('+')
1268                || trimmed.contains('-')
1269                || trimmed.contains('*')
1270                || trimmed.contains('/')
1271            {
1272                // For now, store as string - the engine will evaluate
1273                args.push(Value::String(trimmed.to_string()));
1274            } else {
1275                args.push(self.parse_value(trimmed)?);
1276            }
1277        }
1278
1279        Ok(args)
1280    }
1281
1282    /// Parse function arguments as parameters for custom actions
1283    fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1284        let mut params = HashMap::new();
1285
1286        if args_str.trim().is_empty() {
1287            return Ok(params);
1288        }
1289
1290        // Parse positional parameters as numbered args
1291        let parts: Vec<&str> = args_str.split(',').collect();
1292        for (i, part) in parts.iter().enumerate() {
1293            let trimmed = part.trim();
1294            let value = self.parse_value(trimmed)?;
1295
1296            // Use simple numeric indexing - engine will resolve references dynamically
1297            params.insert(i.to_string(), value);
1298        }
1299
1300        Ok(params)
1301    }
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306    use super::GRLParser;
1307
1308    #[test]
1309    fn test_parse_simple_rule() {
1310        let grl = r#"
1311        rule "CheckAge" salience 10 {
1312            when
1313                User.Age >= 18
1314            then
1315                log("User is adult");
1316        }
1317        "#;
1318
1319        let rules = GRLParser::parse_rules(grl).unwrap();
1320        assert_eq!(rules.len(), 1);
1321        let rule = &rules[0];
1322        assert_eq!(rule.name, "CheckAge");
1323        assert_eq!(rule.salience, 10);
1324        assert_eq!(rule.actions.len(), 1);
1325    }
1326
1327    #[test]
1328    fn test_parse_complex_condition() {
1329        let grl = r#"
1330        rule "ComplexRule" {
1331            when
1332                User.Age >= 18 && User.Country == "US"
1333            then
1334                User.Qualified = true;
1335        }
1336        "#;
1337
1338        let rules = GRLParser::parse_rules(grl).unwrap();
1339        assert_eq!(rules.len(), 1);
1340        let rule = &rules[0];
1341        assert_eq!(rule.name, "ComplexRule");
1342    }
1343
1344    #[test]
1345    fn test_parse_new_syntax_with_parentheses() {
1346        let grl = r#"
1347        rule "Default Rule" salience 10 {
1348            when
1349                (user.age >= 18)
1350            then
1351                set(user.status, "approved");
1352        }
1353        "#;
1354
1355        let rules = GRLParser::parse_rules(grl).unwrap();
1356        assert_eq!(rules.len(), 1);
1357        let rule = &rules[0];
1358        assert_eq!(rule.name, "Default Rule");
1359        assert_eq!(rule.salience, 10);
1360        assert_eq!(rule.actions.len(), 1);
1361
1362        // Check that the action is parsed as a Custom action (set is now custom)
1363        match &rule.actions[0] {
1364            crate::types::ActionType::Custom {
1365                action_type,
1366                params,
1367            } => {
1368                assert_eq!(action_type, "set");
1369                assert_eq!(
1370                    params.get("0"),
1371                    Some(&crate::types::Value::String("user.status".to_string()))
1372                );
1373                assert_eq!(
1374                    params.get("1"),
1375                    Some(&crate::types::Value::String("approved".to_string()))
1376                );
1377            }
1378            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1379        }
1380    }
1381
1382    #[test]
1383    fn test_parse_complex_nested_conditions() {
1384        let grl = r#"
1385        rule "Complex Business Rule" salience 10 {
1386            when
1387                (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1388            then
1389                apply_discount(20000);
1390        }
1391        "#;
1392
1393        let rules = GRLParser::parse_rules(grl).unwrap();
1394        assert_eq!(rules.len(), 1);
1395        let rule = &rules[0];
1396        assert_eq!(rule.name, "Complex Business Rule");
1397        assert_eq!(rule.salience, 10);
1398        assert_eq!(rule.actions.len(), 1);
1399
1400        // Check that the action is parsed as a Custom action (apply_discount is now custom)
1401        match &rule.actions[0] {
1402            crate::types::ActionType::Custom {
1403                action_type,
1404                params,
1405            } => {
1406                assert_eq!(action_type, "apply_discount");
1407                assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1408            }
1409            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1410        }
1411    }
1412
1413    #[test]
1414    fn test_parse_no_loop_attribute() {
1415        let grl = r#"
1416        rule "NoLoopRule" no-loop salience 15 {
1417            when
1418                User.Score < 100
1419            then
1420                set(User.Score, User.Score + 10);
1421        }
1422        "#;
1423
1424        let rules = GRLParser::parse_rules(grl).unwrap();
1425        assert_eq!(rules.len(), 1);
1426        let rule = &rules[0];
1427        assert_eq!(rule.name, "NoLoopRule");
1428        assert_eq!(rule.salience, 15);
1429        assert!(rule.no_loop, "Rule should have no-loop=true");
1430    }
1431
1432    #[test]
1433    fn test_parse_no_loop_different_positions() {
1434        // Test no-loop before salience
1435        let grl1 = r#"
1436        rule "Rule1" no-loop salience 10 {
1437            when User.Age >= 18
1438            then log("adult");
1439        }
1440        "#;
1441
1442        // Test no-loop after salience
1443        let grl2 = r#"
1444        rule "Rule2" salience 10 no-loop {
1445            when User.Age >= 18
1446            then log("adult");
1447        }
1448        "#;
1449
1450        let rules1 = GRLParser::parse_rules(grl1).unwrap();
1451        let rules2 = GRLParser::parse_rules(grl2).unwrap();
1452
1453        assert_eq!(rules1.len(), 1);
1454        assert_eq!(rules2.len(), 1);
1455
1456        assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1457        assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1458
1459        assert_eq!(rules1[0].salience, 10);
1460        assert_eq!(rules2[0].salience, 10);
1461    }
1462
1463    #[test]
1464    fn test_parse_without_no_loop() {
1465        let grl = r#"
1466        rule "RegularRule" salience 5 {
1467            when
1468                User.Active == true
1469            then
1470                log("active user");
1471        }
1472        "#;
1473
1474        let rules = GRLParser::parse_rules(grl).unwrap();
1475        assert_eq!(rules.len(), 1);
1476        let rule = &rules[0];
1477        assert_eq!(rule.name, "RegularRule");
1478        assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1479    }
1480
1481    #[test]
1482    fn test_parse_exists_pattern() {
1483        let grl = r#"
1484        rule "ExistsRule" salience 20 {
1485            when
1486                exists(Customer.tier == "VIP")
1487            then
1488                System.premiumActive = true;
1489        }
1490        "#;
1491
1492        let rules = GRLParser::parse_rules(grl).unwrap();
1493        assert_eq!(rules.len(), 1);
1494        let rule = &rules[0];
1495        assert_eq!(rule.name, "ExistsRule");
1496        assert_eq!(rule.salience, 20);
1497
1498        // Check that condition is EXISTS pattern
1499        match &rule.conditions {
1500            crate::engine::rule::ConditionGroup::Exists(_) => {
1501                // Test passes
1502            }
1503            _ => panic!(
1504                "Expected EXISTS condition group, got: {:?}",
1505                rule.conditions
1506            ),
1507        }
1508    }
1509
1510    #[test]
1511    fn test_parse_forall_pattern() {
1512        let grl = r#"
1513        rule "ForallRule" salience 15 {
1514            when
1515                forall(Order.status == "processed")
1516            then
1517                Shipping.enabled = true;
1518        }
1519        "#;
1520
1521        let rules = GRLParser::parse_rules(grl).unwrap();
1522        assert_eq!(rules.len(), 1);
1523        let rule = &rules[0];
1524        assert_eq!(rule.name, "ForallRule");
1525
1526        // Check that condition is FORALL pattern
1527        match &rule.conditions {
1528            crate::engine::rule::ConditionGroup::Forall(_) => {
1529                // Test passes
1530            }
1531            _ => panic!(
1532                "Expected FORALL condition group, got: {:?}",
1533                rule.conditions
1534            ),
1535        }
1536    }
1537
1538    #[test]
1539    fn test_parse_combined_patterns() {
1540        let grl = r#"
1541        rule "CombinedRule" salience 25 {
1542            when
1543                exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1544            then
1545                System.vipMode = true;
1546        }
1547        "#;
1548
1549        let rules = GRLParser::parse_rules(grl).unwrap();
1550        assert_eq!(rules.len(), 1);
1551        let rule = &rules[0];
1552        assert_eq!(rule.name, "CombinedRule");
1553
1554        // Check that condition is AND with EXISTS and NOT(EXISTS) patterns
1555        match &rule.conditions {
1556            crate::engine::rule::ConditionGroup::Compound {
1557                left,
1558                operator,
1559                right,
1560            } => {
1561                assert_eq!(*operator, crate::types::LogicalOperator::And);
1562
1563                // Left should be EXISTS
1564                match left.as_ref() {
1565                    crate::engine::rule::ConditionGroup::Exists(_) => {
1566                        // Expected
1567                    }
1568                    _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1569                }
1570
1571                // Right should be NOT(EXISTS)
1572                match right.as_ref() {
1573                    crate::engine::rule::ConditionGroup::Not(inner) => {
1574                        match inner.as_ref() {
1575                            crate::engine::rule::ConditionGroup::Exists(_) => {
1576                                // Expected
1577                            }
1578                            _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1579                        }
1580                    }
1581                    _ => panic!("Expected NOT in right side, got: {:?}", right),
1582                }
1583            }
1584            _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1585        }
1586    }
1587}