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