rust_rule_engine/parser/
grl.rs

1#![allow(clippy::manual_pattern_char_comparison)]
2#![allow(clippy::collapsible_if)]
3#![allow(clippy::needless_borrow)]
4#![allow(clippy::manual_strip)]
5#![allow(deprecated)]
6use crate::engine::module::{ExportItem, ExportList, ImportType, ItemType, ModuleManager};
7use crate::engine::rule::{Condition, ConditionGroup, Rule};
8use crate::errors::{Result, RuleEngineError};
9use crate::types::{ActionType, Operator, Value};
10use chrono::{DateTime, Utc};
11use regex::Regex;
12use std::collections::HashMap;
13use std::sync::OnceLock;
14
15// Stream syntax parser module
16#[cfg(feature = "streaming")]
17pub mod stream_syntax;
18
19// Cached main regexes - compiled once at startup
20static RULE_REGEX: OnceLock<Regex> = OnceLock::new();
21static RULE_SPLIT_REGEX: OnceLock<Regex> = OnceLock::new();
22static DEFMODULE_REGEX: OnceLock<Regex> = OnceLock::new();
23static DEFMODULE_SPLIT_REGEX: OnceLock<Regex> = OnceLock::new();
24static WHEN_THEN_REGEX: OnceLock<Regex> = OnceLock::new();
25static SALIENCE_REGEX: OnceLock<Regex> = OnceLock::new();
26static TEST_CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
27static TYPED_TEST_CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
28static FUNCTION_CALL_REGEX: OnceLock<Regex> = OnceLock::new();
29static CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
30static METHOD_CALL_REGEX: OnceLock<Regex> = OnceLock::new();
31static FUNCTION_BINDING_REGEX: OnceLock<Regex> = OnceLock::new();
32static MULTIFIELD_COLLECT_REGEX: OnceLock<Regex> = OnceLock::new();
33static MULTIFIELD_COUNT_REGEX: OnceLock<Regex> = OnceLock::new();
34static MULTIFIELD_FIRST_REGEX: OnceLock<Regex> = OnceLock::new();
35static MULTIFIELD_LAST_REGEX: OnceLock<Regex> = OnceLock::new();
36static MULTIFIELD_EMPTY_REGEX: OnceLock<Regex> = OnceLock::new();
37static MULTIFIELD_NOT_EMPTY_REGEX: OnceLock<Regex> = OnceLock::new();
38static SIMPLE_CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
39
40// Helper functions to get or initialize regexes
41fn rule_regex() -> &'static Regex {
42    RULE_REGEX.get_or_init(|| {
43        Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
44            .expect("Invalid rule regex pattern")
45    })
46}
47
48fn rule_split_regex() -> &'static Regex {
49    RULE_SPLIT_REGEX.get_or_init(|| {
50        Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
51            .expect("Invalid rule split regex pattern")
52    })
53}
54
55fn defmodule_regex() -> &'static Regex {
56    DEFMODULE_REGEX.get_or_init(|| {
57        Regex::new(r#"defmodule\s+([A-Z_]\w*)\s*\{([^}]*)\}"#)
58            .expect("Invalid defmodule regex pattern")
59    })
60}
61
62fn defmodule_split_regex() -> &'static Regex {
63    DEFMODULE_SPLIT_REGEX.get_or_init(|| {
64        Regex::new(r#"(?s)defmodule\s+[A-Z_]\w*\s*\{[^}]*\}"#)
65            .expect("Invalid defmodule split regex pattern")
66    })
67}
68
69fn when_then_regex() -> &'static Regex {
70    WHEN_THEN_REGEX.get_or_init(|| {
71        Regex::new(r"when\s+(.+?)\s+then\s+(.+)").expect("Invalid when-then regex pattern")
72    })
73}
74
75fn salience_regex() -> &'static Regex {
76    SALIENCE_REGEX
77        .get_or_init(|| Regex::new(r"salience\s+(\d+)").expect("Invalid salience regex pattern"))
78}
79
80fn test_condition_regex() -> &'static Regex {
81    TEST_CONDITION_REGEX.get_or_init(|| {
82        Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
83            .expect("Invalid test condition regex")
84    })
85}
86
87fn typed_test_condition_regex() -> &'static Regex {
88    TYPED_TEST_CONDITION_REGEX.get_or_init(|| {
89        Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
90            .expect("Invalid typed test condition regex")
91    })
92}
93
94fn function_call_regex() -> &'static Regex {
95    FUNCTION_CALL_REGEX.get_or_init(|| {
96        Regex::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
97            .expect("Invalid function call regex")
98    })
99}
100
101fn condition_regex() -> &'static Regex {
102    CONDITION_REGEX.get_or_init(|| {
103        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*(.+)"#)
104            .expect("Invalid condition regex")
105    })
106}
107
108fn method_call_regex() -> &'static Regex {
109    METHOD_CALL_REGEX.get_or_init(|| {
110        Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).expect("Invalid method call regex")
111    })
112}
113
114fn function_binding_regex() -> &'static Regex {
115    FUNCTION_BINDING_REGEX.get_or_init(|| {
116        Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).expect("Invalid function binding regex")
117    })
118}
119
120fn multifield_collect_regex() -> &'static Regex {
121    MULTIFIELD_COLLECT_REGEX.get_or_init(|| {
122        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
123            .expect("Invalid multifield collect regex")
124    })
125}
126
127fn multifield_count_regex() -> &'static Regex {
128    MULTIFIELD_COUNT_REGEX.get_or_init(|| {
129        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
130            .expect("Invalid multifield count regex")
131    })
132}
133
134fn multifield_first_regex() -> &'static Regex {
135    MULTIFIELD_FIRST_REGEX.get_or_init(|| {
136        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
137            .expect("Invalid multifield first regex")
138    })
139}
140
141fn multifield_last_regex() -> &'static Regex {
142    MULTIFIELD_LAST_REGEX.get_or_init(|| {
143        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
144            .expect("Invalid multifield last regex")
145    })
146}
147
148fn multifield_empty_regex() -> &'static Regex {
149    MULTIFIELD_EMPTY_REGEX.get_or_init(|| {
150        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#)
151            .expect("Invalid multifield empty regex")
152    })
153}
154
155fn multifield_not_empty_regex() -> &'static Regex {
156    MULTIFIELD_NOT_EMPTY_REGEX.get_or_init(|| {
157        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
158            .expect("Invalid multifield not_empty regex")
159    })
160}
161
162fn simple_condition_regex() -> &'static Regex {
163    SIMPLE_CONDITION_REGEX.get_or_init(|| {
164        Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).expect("Invalid simple condition regex")
165    })
166}
167
168/// GRL (Grule Rule Language) Parser
169/// Parses Grule-like syntax into Rule objects
170///
171/// # ⚠️ Performance Note
172///
173/// This parser uses regex internally which is 4-60x slower than the new literal search parsers.
174///
175/// **For new code, consider using the faster alternatives:**
176///
177/// ```rust,no_run
178/// use rust_rule_engine::parser::{grl_helpers, parallel};
179///
180/// // Example: a small dummy GRL string (doctest is marked no_run to avoid
181/// // executing heavy parsing during docs build)
182/// let grl = r#"rule \"Example\" { when true then }"#;
183///
184/// // Fast rule splitting with SIMD (3.5x faster)
185/// let rules = grl_helpers::split_into_rules(&grl);
186///
187/// // Or parallel parsing for large files (up to 8x faster on multi-core)
188/// let rules = parallel::parse_rules_adaptive(&grl);
189/// ```
190///
191/// This parser (`GRLParser`) is kept for backward compatibility with existing code.
192#[deprecated(
193    since = "1.18.0",
194    note = "Use `grl_helpers` or `parallel` parsers for 4-60x better performance. See module docs."
195)]
196pub struct GRLParser;
197
198/// Parsed rule attributes from GRL header
199#[derive(Debug, Default)]
200struct RuleAttributes {
201    pub no_loop: bool,
202    pub lock_on_active: bool,
203    pub agenda_group: Option<String>,
204    pub activation_group: Option<String>,
205    pub date_effective: Option<DateTime<Utc>>,
206    pub date_expires: Option<DateTime<Utc>>,
207}
208
209/// Result from parsing GRL with modules
210#[derive(Debug, Clone)]
211pub struct ParsedGRL {
212    /// Parsed rules
213    pub rules: Vec<Rule>,
214    /// Module manager with configured modules
215    pub module_manager: ModuleManager,
216    /// Map of rule name to module name
217    pub rule_modules: HashMap<String, String>,
218}
219
220impl Default for ParsedGRL {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226impl ParsedGRL {
227    pub fn new() -> Self {
228        Self {
229            rules: Vec::new(),
230            module_manager: ModuleManager::new(),
231            rule_modules: HashMap::new(),
232        }
233    }
234}
235
236impl GRLParser {
237    /// Parse a single rule from GRL syntax
238    ///
239    /// Example GRL syntax:
240    /// ```grl
241    /// rule CheckAge "Age verification rule" salience 10 {
242    ///     when
243    ///         User.Age >= 18 && User.Country == "US"
244    ///     then
245    ///         User.IsAdult = true;
246    ///         Retract("User");
247    /// }
248    /// ```
249    pub fn parse_rule(grl_text: &str) -> Result<Rule> {
250        let mut parser = GRLParser;
251        parser.parse_single_rule(grl_text)
252    }
253
254    /// Parse multiple rules from GRL text
255    pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
256        let mut parser = GRLParser;
257        parser.parse_multiple_rules(grl_text)
258    }
259
260    /// Parse GRL text with module support
261    ///
262    /// Example:
263    /// ```grl
264    /// defmodule SENSORS {
265    ///   export: all
266    /// }
267    ///
268    /// defmodule CONTROL {
269    ///   import: SENSORS (rules * (templates temperature))
270    /// }
271    ///
272    /// rule "CheckTemp" {
273    ///   when temperature.value > 28
274    ///   then println("Hot");
275    /// }
276    /// ```
277    pub fn parse_with_modules(grl_text: &str) -> Result<ParsedGRL> {
278        let mut parser = GRLParser;
279        parser.parse_grl_with_modules(grl_text)
280    }
281
282    fn parse_grl_with_modules(&mut self, grl_text: &str) -> Result<ParsedGRL> {
283        let mut result = ParsedGRL::new();
284
285        // First, parse and register all modules
286        for module_match in defmodule_split_regex().find_iter(grl_text) {
287            let module_def = module_match.as_str();
288            self.parse_and_register_module(module_def, &mut result.module_manager)?;
289        }
290
291        // Remove all defmodule blocks from text before parsing rules
292        let rules_text = defmodule_split_regex().replace_all(grl_text, "");
293
294        // Then parse all rules from cleaned text
295        let rules = self.parse_multiple_rules(&rules_text)?;
296
297        // Try to assign rules to modules based on comments
298        for rule in rules {
299            let module_name = self.extract_module_from_context(grl_text, &rule.name);
300            result
301                .rule_modules
302                .insert(rule.name.clone(), module_name.clone());
303
304            // Add rule to module in manager
305            if let Ok(module) = result.module_manager.get_module_mut(&module_name) {
306                module.add_rule(&rule.name);
307            }
308
309            result.rules.push(rule);
310        }
311
312        Ok(result)
313    }
314
315    fn parse_and_register_module(
316        &self,
317        module_def: &str,
318        manager: &mut ModuleManager,
319    ) -> Result<()> {
320        // Parse: defmodule MODULE_NAME { export: all/none, import: ... }
321        if let Some(captures) = defmodule_regex().captures(module_def) {
322            let module_name = captures.get(1).unwrap().as_str().to_string();
323            let module_body = captures.get(2).unwrap().as_str();
324
325            // Create module (ignore if already exists)
326            let _ = manager.create_module(&module_name);
327            let module = manager.get_module_mut(&module_name)?;
328
329            // Parse export directive
330            if let Some(export_type) = self.extract_directive(module_body, "export:") {
331                let exports = if export_type.trim() == "all" {
332                    ExportList::All
333                } else if export_type.trim() == "none" {
334                    ExportList::None
335                } else {
336                    // Parse pattern-based exports
337                    ExportList::Specific(vec![ExportItem {
338                        item_type: ItemType::All,
339                        pattern: export_type.trim().to_string(),
340                    }])
341                };
342                module.set_exports(exports);
343            }
344
345            // Parse import directives
346            let import_lines: Vec<&str> = module_body
347                .lines()
348                .filter(|line| line.trim().starts_with("import:"))
349                .collect();
350
351            for import_line in import_lines {
352                if let Some(import_spec) = self.extract_directive(import_line, "import:") {
353                    // Parse: "MODULE_A (rules * (templates foo))"
354                    self.parse_import_spec(&module_name, &import_spec, manager)?;
355                }
356            }
357        }
358
359        Ok(())
360    }
361
362    fn extract_directive(&self, text: &str, directive: &str) -> Option<String> {
363        if let Some(pos) = text.find(directive) {
364            let after_directive = &text[pos + directive.len()..];
365
366            // Find the end of the directive (next directive, or end of block)
367            let end = after_directive
368                .find("import:")
369                .or_else(|| after_directive.find("export:"))
370                .unwrap_or(after_directive.len());
371
372            Some(after_directive[..end].trim().to_string())
373        } else {
374            None
375        }
376    }
377
378    fn parse_import_spec(
379        &self,
380        importing_module: &str,
381        spec: &str,
382        manager: &mut ModuleManager,
383    ) -> Result<()> {
384        // Parse: "SENSORS (rules * (templates temperature))"
385        let parts: Vec<&str> = spec.splitn(2, '(').collect();
386        if parts.is_empty() {
387            return Ok(());
388        }
389
390        let source_module = parts[0].trim().to_string();
391        let rest = if parts.len() > 1 { parts[1] } else { "" };
392
393        // Check if we're importing rules or templates
394        if rest.contains("rules") {
395            manager.import_from(importing_module, &source_module, ImportType::AllRules, "*")?;
396        }
397
398        if rest.contains("templates") {
399            manager.import_from(
400                importing_module,
401                &source_module,
402                ImportType::AllTemplates,
403                "*",
404            )?;
405        }
406
407        Ok(())
408    }
409
410    fn extract_module_from_context(&self, grl_text: &str, rule_name: &str) -> String {
411        // Look backward from rule to find the module comment
412        if let Some(rule_pos) = grl_text
413            .find(&format!("rule \"{}\"", rule_name))
414            .or_else(|| grl_text.find(&format!("rule {}", rule_name)))
415        {
416            // Look backward for ;; MODULE: comment
417            let before = &grl_text[..rule_pos];
418            if let Some(module_pos) = before.rfind(";; MODULE:") {
419                let after_module_marker = &before[module_pos + 10..];
420                if let Some(end_of_line) = after_module_marker.find('\n') {
421                    let module_line = &after_module_marker[..end_of_line].trim();
422                    // Extract module name from "SENSORS - Temperature Monitoring"
423                    if let Some(first_word) = module_line.split_whitespace().next() {
424                        return first_word.to_string();
425                    }
426                }
427            }
428        }
429
430        // Default to MAIN
431        "MAIN".to_string()
432    }
433
434    fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
435        let cleaned = self.clean_text(grl_text);
436
437        // Extract rule components using cached regex
438        let captures =
439            rule_regex()
440                .captures(&cleaned)
441                .ok_or_else(|| RuleEngineError::ParseError {
442                    message: format!("Invalid GRL rule format. Input: {}", cleaned),
443                })?;
444
445        // Rule name can be either quoted (group 1) or unquoted (group 2)
446        let rule_name = if let Some(quoted_name) = captures.get(1) {
447            quoted_name.as_str().to_string()
448        } else if let Some(unquoted_name) = captures.get(2) {
449            unquoted_name.as_str().to_string()
450        } else {
451            return Err(RuleEngineError::ParseError {
452                message: "Could not extract rule name".to_string(),
453            });
454        };
455
456        // Attributes section (group 3)
457        let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
458
459        // Rule body (group 4)
460        let rule_body = captures.get(4).unwrap().as_str();
461
462        // Parse salience from attributes section
463        let salience = self.extract_salience(attributes_section)?;
464
465        // Parse when and then sections using cached regex
466        let when_then_captures =
467            when_then_regex()
468                .captures(rule_body)
469                .ok_or_else(|| RuleEngineError::ParseError {
470                    message: "Missing when or then clause".to_string(),
471                })?;
472
473        let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
474        let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
475
476        // Parse conditions and actions
477        let conditions = self.parse_when_clause(when_clause)?;
478        let actions = self.parse_then_clause(then_clause)?;
479
480        // Parse all attributes from rule header
481        let attributes = self.parse_rule_attributes(attributes_section)?;
482
483        // Build rule
484        let mut rule = Rule::new(rule_name, conditions, actions);
485        rule = rule.with_priority(salience);
486
487        // Apply parsed attributes
488        if attributes.no_loop {
489            rule = rule.with_no_loop(true);
490        }
491        if attributes.lock_on_active {
492            rule = rule.with_lock_on_active(true);
493        }
494        if let Some(agenda_group) = attributes.agenda_group {
495            rule = rule.with_agenda_group(agenda_group);
496        }
497        if let Some(activation_group) = attributes.activation_group {
498            rule = rule.with_activation_group(activation_group);
499        }
500        if let Some(date_effective) = attributes.date_effective {
501            rule = rule.with_date_effective(date_effective);
502        }
503        if let Some(date_expires) = attributes.date_expires {
504            rule = rule.with_date_expires(date_expires);
505        }
506
507        Ok(rule)
508    }
509
510    fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
511        // Split by rule boundaries - support both quoted and unquoted rule names
512        // Use DOTALL flag to match newlines in rule body
513        let mut rules = Vec::new();
514
515        for rule_match in rule_split_regex().find_iter(grl_text) {
516            let rule_text = rule_match.as_str();
517            let rule = self.parse_single_rule(rule_text)?;
518            rules.push(rule);
519        }
520
521        Ok(rules)
522    }
523
524    /// Parse rule attributes from the rule header
525    fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
526        let mut attributes = RuleAttributes::default();
527
528        // Extract the attributes section (after rule name/description, before opening brace)
529        // This ensures we don't match keywords inside description strings
530        // Strategy: Find all quoted strings and remove them, then check for attributes
531        let mut attrs_section = rule_header.to_string();
532
533        // Remove all quoted strings (descriptions) to avoid false matches
534        let quoted_regex = Regex::new(r#""[^"]*""#).map_err(|e| RuleEngineError::ParseError {
535            message: format!("Invalid quoted string regex: {}", e),
536        })?;
537        attrs_section = quoted_regex.replace_all(&attrs_section, "").to_string();
538
539        // Also remove the "rule" keyword and rule name (if unquoted)
540        if let Some(rule_pos) = attrs_section.find("rule") {
541            // Find the next space or attribute keyword after "rule"
542            let after_rule = &attrs_section[rule_pos + 4..];
543            if let Some(first_keyword) = after_rule
544                .find("salience")
545                .or_else(|| after_rule.find("no-loop"))
546                .or_else(|| after_rule.find("lock-on-active"))
547                .or_else(|| after_rule.find("agenda-group"))
548                .or_else(|| after_rule.find("activation-group"))
549                .or_else(|| after_rule.find("date-effective"))
550                .or_else(|| after_rule.find("date-expires"))
551            {
552                attrs_section = after_rule[first_keyword..].to_string();
553            }
554        }
555
556        // Now check for boolean attributes using word boundaries
557        let no_loop_regex =
558            Regex::new(r"\bno-loop\b").map_err(|e| RuleEngineError::ParseError {
559                message: format!("Invalid no-loop regex: {}", e),
560            })?;
561        let lock_on_active_regex =
562            Regex::new(r"\block-on-active\b").map_err(|e| RuleEngineError::ParseError {
563                message: format!("Invalid lock-on-active regex: {}", e),
564            })?;
565
566        if no_loop_regex.is_match(&attrs_section) {
567            attributes.no_loop = true;
568        }
569        if lock_on_active_regex.is_match(&attrs_section) {
570            attributes.lock_on_active = true;
571        }
572
573        // Parse agenda-group attribute
574        if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
575            attributes.agenda_group = Some(agenda_group);
576        }
577
578        // Parse activation-group attribute
579        if let Some(activation_group) =
580            self.extract_quoted_attribute(rule_header, "activation-group")?
581        {
582            attributes.activation_group = Some(activation_group);
583        }
584
585        // Parse date-effective attribute
586        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
587            attributes.date_effective = Some(self.parse_date_string(&date_str)?);
588        }
589
590        // Parse date-expires attribute
591        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
592            attributes.date_expires = Some(self.parse_date_string(&date_str)?);
593        }
594
595        Ok(attributes)
596    }
597
598    /// Extract quoted attribute value from rule header
599    fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
600        let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
601        let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
602            message: format!("Invalid attribute regex for {}: {}", attribute, e),
603        })?;
604
605        if let Some(captures) = regex.captures(header) {
606            if let Some(value) = captures.get(1) {
607                return Ok(Some(value.as_str().to_string()));
608            }
609        }
610
611        Ok(None)
612    }
613
614    /// Parse date string in various formats
615    fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
616        // Try ISO 8601 format first
617        if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
618            return Ok(date.with_timezone(&Utc));
619        }
620
621        // Try simple date formats
622        let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
623
624        for format in &formats {
625            if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
626                return Ok(naive_date.and_utc());
627            }
628            if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
629                return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
630            }
631        }
632
633        Err(RuleEngineError::ParseError {
634            message: format!("Unable to parse date: {}", date_str),
635        })
636    }
637
638    /// Extract salience value from attributes section
639    fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
640        if let Some(captures) = salience_regex().captures(attributes_section) {
641            if let Some(salience_match) = captures.get(1) {
642                return salience_match.as_str().parse::<i32>().map_err(|e| {
643                    RuleEngineError::ParseError {
644                        message: format!("Invalid salience value: {}", e),
645                    }
646                });
647            }
648        }
649
650        Ok(0) // Default salience
651    }
652
653    fn clean_text(&self, text: &str) -> String {
654        text.lines()
655            .map(|line| line.trim())
656            .filter(|line| !line.is_empty() && !line.starts_with("//"))
657            .collect::<Vec<_>>()
658            .join(" ")
659    }
660
661    fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
662        // Handle logical operators with proper parentheses support
663        let trimmed = when_clause.trim();
664
665        // Strip outer parentheses if they exist
666        let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
667            // Check if these are the outermost parentheses
668            let inner = &trimmed[1..trimmed.len() - 1];
669            if self.is_balanced_parentheses(inner) {
670                inner
671            } else {
672                trimmed
673            }
674        } else {
675            trimmed
676        };
677
678        // Parse OR at the top level (lowest precedence)
679        if let Some(parts) = self.split_logical_operator(clause, "||") {
680            return self.parse_or_parts(parts);
681        }
682
683        // Parse AND (higher precedence)
684        if let Some(parts) = self.split_logical_operator(clause, "&&") {
685            return self.parse_and_parts(parts);
686        }
687
688        // Handle NOT condition
689        if clause.trim_start().starts_with("!") {
690            return self.parse_not_condition(clause);
691        }
692
693        // Handle EXISTS condition
694        if clause.trim_start().starts_with("exists(") {
695            return self.parse_exists_condition(clause);
696        }
697
698        // Handle FORALL condition
699        if clause.trim_start().starts_with("forall(") {
700            return self.parse_forall_condition(clause);
701        }
702
703        // Handle ACCUMULATE condition
704        if clause.trim_start().starts_with("accumulate(") {
705            return self.parse_accumulate_condition(clause);
706        }
707
708        // Single condition
709        self.parse_single_condition(clause)
710    }
711
712    fn is_balanced_parentheses(&self, text: &str) -> bool {
713        let mut count = 0;
714        for ch in text.chars() {
715            match ch {
716                '(' => count += 1,
717                ')' => {
718                    count -= 1;
719                    if count < 0 {
720                        return false;
721                    }
722                }
723                _ => {}
724            }
725        }
726        count == 0
727    }
728
729    fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
730        let mut parts = Vec::new();
731        let mut current_part = String::new();
732        let mut paren_count = 0;
733        let mut chars = clause.chars().peekable();
734
735        while let Some(ch) = chars.next() {
736            match ch {
737                '(' => {
738                    paren_count += 1;
739                    current_part.push(ch);
740                }
741                ')' => {
742                    paren_count -= 1;
743                    current_part.push(ch);
744                }
745                '&' if operator == "&&" && paren_count == 0 => {
746                    if chars.peek() == Some(&'&') {
747                        chars.next(); // consume second &
748                        parts.push(current_part.trim().to_string());
749                        current_part.clear();
750                    } else {
751                        current_part.push(ch);
752                    }
753                }
754                '|' if operator == "||" && paren_count == 0 => {
755                    if chars.peek() == Some(&'|') {
756                        chars.next(); // consume second |
757                        parts.push(current_part.trim().to_string());
758                        current_part.clear();
759                    } else {
760                        current_part.push(ch);
761                    }
762                }
763                _ => {
764                    current_part.push(ch);
765                }
766            }
767        }
768
769        if !current_part.trim().is_empty() {
770            parts.push(current_part.trim().to_string());
771        }
772
773        if parts.len() > 1 {
774            Some(parts)
775        } else {
776            None
777        }
778    }
779
780    fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
781        let mut conditions = Vec::new();
782        for part in parts {
783            let condition = self.parse_when_clause(&part)?;
784            conditions.push(condition);
785        }
786
787        if conditions.is_empty() {
788            return Err(RuleEngineError::ParseError {
789                message: "No conditions found in OR".to_string(),
790            });
791        }
792
793        let mut iter = conditions.into_iter();
794        let mut result = iter.next().unwrap();
795        for condition in iter {
796            result = ConditionGroup::or(result, condition);
797        }
798
799        Ok(result)
800    }
801
802    fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
803        let mut conditions = Vec::new();
804        for part in parts {
805            let condition = self.parse_when_clause(&part)?;
806            conditions.push(condition);
807        }
808
809        if conditions.is_empty() {
810            return Err(RuleEngineError::ParseError {
811                message: "No conditions found in AND".to_string(),
812            });
813        }
814
815        let mut iter = conditions.into_iter();
816        let mut result = iter.next().unwrap();
817        for condition in iter {
818            result = ConditionGroup::and(result, condition);
819        }
820
821        Ok(result)
822    }
823
824    fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
825        let inner_clause = clause.strip_prefix("!").unwrap().trim();
826        let inner_condition = self.parse_when_clause(inner_clause)?;
827        Ok(ConditionGroup::not(inner_condition))
828    }
829
830    fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
831        let clause = clause.trim_start();
832        if !clause.starts_with("exists(") || !clause.ends_with(")") {
833            return Err(RuleEngineError::ParseError {
834                message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
835            });
836        }
837
838        // Extract content between parentheses
839        let inner_clause = &clause[7..clause.len() - 1]; // Remove "exists(" and ")"
840        let inner_condition = self.parse_when_clause(inner_clause)?;
841        Ok(ConditionGroup::exists(inner_condition))
842    }
843
844    fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
845        let clause = clause.trim_start();
846        if !clause.starts_with("forall(") || !clause.ends_with(")") {
847            return Err(RuleEngineError::ParseError {
848                message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
849            });
850        }
851
852        // Extract content between parentheses
853        let inner_clause = &clause[7..clause.len() - 1]; // Remove "forall(" and ")"
854        let inner_condition = self.parse_when_clause(inner_clause)?;
855        Ok(ConditionGroup::forall(inner_condition))
856    }
857
858    fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
859        let clause = clause.trim_start();
860        if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
861            return Err(RuleEngineError::ParseError {
862                message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)"
863                    .to_string(),
864            });
865        }
866
867        // Extract content between parentheses
868        let inner = &clause[11..clause.len() - 1]; // Remove "accumulate(" and ")"
869
870        // Split by comma at the top level (not inside parentheses)
871        let parts = self.split_accumulate_parts(inner)?;
872
873        if parts.len() != 2 {
874            return Err(RuleEngineError::ParseError {
875                message: format!(
876                    "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
877                    parts.len()
878                ),
879            });
880        }
881
882        let pattern_part = parts[0].trim();
883        let function_part = parts[1].trim();
884
885        // Parse the pattern: Order($amount: amount, status == "completed")
886        let (source_pattern, extract_field, source_conditions) =
887            self.parse_accumulate_pattern(pattern_part)?;
888
889        // Parse the function: sum($amount)
890        let (function, function_arg) = self.parse_accumulate_function(function_part)?;
891
892        // For now, we'll create a placeholder result variable
893        // In a full implementation, this would be extracted from the parent context
894        // e.g., from "$total: accumulate(...)"
895        let result_var = "$result".to_string();
896
897        Ok(ConditionGroup::accumulate(
898            result_var,
899            source_pattern,
900            extract_field,
901            source_conditions,
902            function,
903            function_arg,
904        ))
905    }
906
907    fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
908        let mut parts = Vec::new();
909        let mut current = String::new();
910        let mut paren_depth = 0;
911
912        for ch in content.chars() {
913            match ch {
914                '(' => {
915                    paren_depth += 1;
916                    current.push(ch);
917                }
918                ')' => {
919                    paren_depth -= 1;
920                    current.push(ch);
921                }
922                ',' if paren_depth == 0 => {
923                    parts.push(current.trim().to_string());
924                    current.clear();
925                }
926                _ => {
927                    current.push(ch);
928                }
929            }
930        }
931
932        if !current.trim().is_empty() {
933            parts.push(current.trim().to_string());
934        }
935
936        Ok(parts)
937    }
938
939    fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
940        // Pattern format: Order($amount: amount, status == "completed", category == "electronics")
941        // We need to extract:
942        // - source_pattern: "Order"
943        // - extract_field: "amount" (from $amount: amount)
944        // - source_conditions: ["status == \"completed\"", "category == \"electronics\""]
945
946        let pattern = pattern.trim();
947
948        // Find the opening parenthesis to get the pattern type
949        let paren_pos = pattern
950            .find('(')
951            .ok_or_else(|| RuleEngineError::ParseError {
952                message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
953            })?;
954
955        let source_pattern = pattern[..paren_pos].trim().to_string();
956
957        // Extract content between parentheses
958        if !pattern.ends_with(')') {
959            return Err(RuleEngineError::ParseError {
960                message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
961            });
962        }
963
964        let inner = &pattern[paren_pos + 1..pattern.len() - 1];
965
966        // Split by comma (respecting nested parentheses and quotes)
967        let parts = self.split_pattern_parts(inner)?;
968
969        let mut extract_field = String::new();
970        let mut source_conditions = Vec::new();
971
972        for part in parts {
973            let part = part.trim();
974
975            // Check if this is a variable binding: $var: field
976            if part.contains(':') && part.starts_with('$') {
977                let colon_pos = part.find(':').unwrap();
978                let _var_name = part[..colon_pos].trim();
979                let field_name = part[colon_pos + 1..].trim();
980                extract_field = field_name.to_string();
981            } else if part.contains("==")
982                || part.contains("!=")
983                || part.contains(">=")
984                || part.contains("<=")
985                || part.contains('>')
986                || part.contains('<')
987            {
988                // This is a condition
989                source_conditions.push(part.to_string());
990            }
991        }
992
993        Ok((source_pattern, extract_field, source_conditions))
994    }
995
996    fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
997        let mut parts = Vec::new();
998        let mut current = String::new();
999        let mut paren_depth = 0;
1000        let mut in_quotes = false;
1001        let mut quote_char = ' ';
1002
1003        for ch in content.chars() {
1004            match ch {
1005                '"' | '\'' if !in_quotes => {
1006                    in_quotes = true;
1007                    quote_char = ch;
1008                    current.push(ch);
1009                }
1010                '"' | '\'' if in_quotes && ch == quote_char => {
1011                    in_quotes = false;
1012                    current.push(ch);
1013                }
1014                '(' if !in_quotes => {
1015                    paren_depth += 1;
1016                    current.push(ch);
1017                }
1018                ')' if !in_quotes => {
1019                    paren_depth -= 1;
1020                    current.push(ch);
1021                }
1022                ',' if !in_quotes && paren_depth == 0 => {
1023                    parts.push(current.trim().to_string());
1024                    current.clear();
1025                }
1026                _ => {
1027                    current.push(ch);
1028                }
1029            }
1030        }
1031
1032        if !current.trim().is_empty() {
1033            parts.push(current.trim().to_string());
1034        }
1035
1036        Ok(parts)
1037    }
1038
1039    fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
1040        // Function format: sum($amount) or count() or average($price)
1041
1042        let function_str = function_str.trim();
1043
1044        let paren_pos = function_str
1045            .find('(')
1046            .ok_or_else(|| RuleEngineError::ParseError {
1047                message: format!(
1048                    "Invalid accumulate function: missing '(' in '{}'",
1049                    function_str
1050                ),
1051            })?;
1052
1053        let function_name = function_str[..paren_pos].trim().to_string();
1054
1055        if !function_str.ends_with(')') {
1056            return Err(RuleEngineError::ParseError {
1057                message: format!(
1058                    "Invalid accumulate function: missing ')' in '{}'",
1059                    function_str
1060                ),
1061            });
1062        }
1063
1064        let args = &function_str[paren_pos + 1..function_str.len() - 1];
1065        let function_arg = args.trim().to_string();
1066
1067        Ok((function_name, function_arg))
1068    }
1069
1070    fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
1071        // Remove outer parentheses if they exist (handle new syntax like "(user.age >= 18)")
1072        let trimmed_clause = clause.trim();
1073        let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
1074            trimmed_clause[1..trimmed_clause.len() - 1].trim()
1075        } else {
1076            trimmed_clause
1077        };
1078
1079        // === STREAM PATTERNS ===
1080        // Check for stream pattern syntax: "var: Type from stream(...)"
1081        #[cfg(feature = "streaming")]
1082        if clause_to_parse.contains("from stream(") {
1083            return self.parse_stream_pattern_condition(clause_to_parse);
1084        }
1085
1086        // === MULTI-FIELD PATTERNS ===
1087        // Handle multi-field patterns before other patterns
1088        // These must be checked first to avoid conflict with standard patterns
1089
1090        // Pattern 1: Field.array $?var (Collect operation with variable binding)
1091        // Example: Order.items $?all_items
1092        if let Some(captures) = multifield_collect_regex().captures(clause_to_parse) {
1093            let field = captures.get(1).unwrap().as_str().to_string();
1094            let variable = captures.get(2).unwrap().as_str().to_string();
1095
1096            // Create a multifield Collect condition
1097            // Note: This will need to be handled by the engine
1098            let condition = Condition::with_multifield_collect(field, variable);
1099            return Ok(ConditionGroup::single(condition));
1100        }
1101
1102        // Pattern 2: Field.array contains "value"
1103        // Example: Product.tags contains "electronics"
1104        // This is already handled by the standard regex, but we need to distinguish array contains
1105
1106        // Pattern 3: Field.array count operator value
1107        // Example: Order.items count > 0, Order.items count >= 5
1108        if let Some(captures) = multifield_count_regex().captures(clause_to_parse) {
1109            let field = captures.get(1).unwrap().as_str().to_string();
1110            let operator_str = captures.get(2).unwrap().as_str();
1111            let value_str = captures.get(3).unwrap().as_str().trim();
1112
1113            let operator = Operator::from_str(operator_str).ok_or_else(|| {
1114                RuleEngineError::InvalidOperator {
1115                    operator: operator_str.to_string(),
1116                }
1117            })?;
1118
1119            let value = self.parse_value(value_str)?;
1120
1121            let condition = Condition::with_multifield_count(field, operator, value);
1122            return Ok(ConditionGroup::single(condition));
1123        }
1124
1125        // Pattern 4: Field.array first [optional: $var or operator value]
1126        // Example: Queue.tasks first, Queue.tasks first $first_task
1127        if let Some(captures) = multifield_first_regex().captures(clause_to_parse) {
1128            let field = captures.get(1).unwrap().as_str().to_string();
1129            let variable = captures.get(2).map(|m| m.as_str().to_string());
1130
1131            let condition = Condition::with_multifield_first(field, variable);
1132            return Ok(ConditionGroup::single(condition));
1133        }
1134
1135        // Pattern 5: Field.array last [optional: $var]
1136        // Example: Queue.tasks last, Queue.tasks last $last_task
1137        if let Some(captures) = multifield_last_regex().captures(clause_to_parse) {
1138            let field = captures.get(1).unwrap().as_str().to_string();
1139            let variable = captures.get(2).map(|m| m.as_str().to_string());
1140
1141            let condition = Condition::with_multifield_last(field, variable);
1142            return Ok(ConditionGroup::single(condition));
1143        }
1144
1145        // Pattern 6: Field.array empty
1146        // Example: ShoppingCart.items empty
1147        if let Some(captures) = multifield_empty_regex().captures(clause_to_parse) {
1148            let field = captures.get(1).unwrap().as_str().to_string();
1149
1150            let condition = Condition::with_multifield_empty(field);
1151            return Ok(ConditionGroup::single(condition));
1152        }
1153
1154        // Pattern 7: Field.array not_empty
1155        // Example: ShoppingCart.items not_empty
1156        if let Some(captures) = multifield_not_empty_regex().captures(clause_to_parse) {
1157            let field = captures.get(1).unwrap().as_str().to_string();
1158
1159            let condition = Condition::with_multifield_not_empty(field);
1160            return Ok(ConditionGroup::single(condition));
1161        }
1162
1163        // === END MULTI-FIELD PATTERNS ===
1164
1165        // Handle Test CE: test(functionName(args...))
1166        // This is a CLIPS-inspired feature for arbitrary boolean expressions
1167        if let Some(captures) = test_condition_regex().captures(clause_to_parse) {
1168            let function_name = captures.get(1).unwrap().as_str().to_string();
1169            let args_str = captures.get(2).unwrap().as_str();
1170
1171            // Parse arguments
1172            let args: Vec<String> = if args_str.trim().is_empty() {
1173                Vec::new()
1174            } else {
1175                args_str
1176                    .split(',')
1177                    .map(|arg| arg.trim().to_string())
1178                    .collect()
1179            };
1180
1181            let condition = Condition::with_test(function_name, args);
1182            return Ok(ConditionGroup::single(condition));
1183        }
1184
1185        // Handle typed object conditions like: $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
1186        if let Some(captures) = typed_test_condition_regex().captures(clause_to_parse) {
1187            let _object_name = captures.get(1).unwrap().as_str();
1188            let _object_type = captures.get(2).unwrap().as_str();
1189            let conditions_str = captures.get(3).unwrap().as_str();
1190
1191            // Parse conditions inside parentheses
1192            return self.parse_conditions_within_object(conditions_str);
1193        }
1194
1195        // Try to parse function call pattern: functionName(arg1, arg2, ...) operator value
1196        if let Some(captures) = function_call_regex().captures(clause_to_parse) {
1197            let function_name = captures.get(1).unwrap().as_str().to_string();
1198            let args_str = captures.get(2).unwrap().as_str();
1199            let operator_str = captures.get(3).unwrap().as_str();
1200            let value_str = captures.get(4).unwrap().as_str().trim();
1201
1202            // Parse arguments
1203            let args: Vec<String> = if args_str.trim().is_empty() {
1204                Vec::new()
1205            } else {
1206                args_str
1207                    .split(',')
1208                    .map(|arg| arg.trim().to_string())
1209                    .collect()
1210            };
1211
1212            let operator = Operator::from_str(operator_str).ok_or_else(|| {
1213                RuleEngineError::InvalidOperator {
1214                    operator: operator_str.to_string(),
1215                }
1216            })?;
1217
1218            let value = self.parse_value(value_str)?;
1219
1220            let condition = Condition::with_function(function_name, args, operator, value);
1221            return Ok(ConditionGroup::single(condition));
1222        }
1223
1224        // Parse expressions like: User.Age >= 18, Product.Price < 100.0, user.age >= 18, etc.
1225        // Support both PascalCase (User.Age) and lowercase (user.age) field naming
1226        // Also support arithmetic expressions like: User.Age % 3 == 0, User.Price * 2 > 100
1227        let captures = condition_regex().captures(clause_to_parse).ok_or_else(|| {
1228            RuleEngineError::ParseError {
1229                message: format!("Invalid condition format: {}", clause_to_parse),
1230            }
1231        })?;
1232
1233        let left_side = captures.get(1).unwrap().as_str().trim().to_string();
1234        let operator_str = captures.get(2).unwrap().as_str();
1235        let value_str = captures.get(3).unwrap().as_str().trim();
1236
1237        let operator =
1238            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1239                operator: operator_str.to_string(),
1240            })?;
1241
1242        let value = self.parse_value(value_str)?;
1243
1244        // Check if left_side contains arithmetic operators - if yes, it's an expression
1245        if left_side.contains('+')
1246            || left_side.contains('-')
1247            || left_side.contains('*')
1248            || left_side.contains('/')
1249            || left_side.contains('%')
1250        {
1251            // This is an arithmetic expression - use Test CE
1252            // Format: test(left_side operator value)
1253            let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
1254            let condition = Condition::with_test(test_expr, vec![]);
1255            Ok(ConditionGroup::single(condition))
1256        } else {
1257            // Simple field reference
1258            let condition = Condition::new(left_side, operator, value);
1259            Ok(ConditionGroup::single(condition))
1260        }
1261    }
1262
1263    fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
1264        // Parse conditions like: speedUp == true && speed < maxSpeed
1265        let parts: Vec<&str> = conditions_str.split("&&").collect();
1266
1267        let mut conditions = Vec::new();
1268        for part in parts {
1269            let trimmed = part.trim();
1270            let condition = self.parse_simple_condition(trimmed)?;
1271            conditions.push(condition);
1272        }
1273
1274        // Combine with AND
1275        if conditions.is_empty() {
1276            return Err(RuleEngineError::ParseError {
1277                message: "No conditions found".to_string(),
1278            });
1279        }
1280
1281        let mut iter = conditions.into_iter();
1282        let mut result = iter.next().unwrap();
1283        for condition in iter {
1284            result = ConditionGroup::and(result, condition);
1285        }
1286
1287        Ok(result)
1288    }
1289
1290    fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
1291        // Parse simple condition like: speedUp == true or speed < maxSpeed
1292        let captures = simple_condition_regex().captures(clause).ok_or_else(|| {
1293            RuleEngineError::ParseError {
1294                message: format!("Invalid simple condition format: {}", clause),
1295            }
1296        })?;
1297
1298        let field = captures.get(1).unwrap().as_str().to_string();
1299        let operator_str = captures.get(2).unwrap().as_str();
1300        let value_str = captures.get(3).unwrap().as_str().trim();
1301
1302        let operator =
1303            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1304                operator: operator_str.to_string(),
1305            })?;
1306
1307        let value = self.parse_value(value_str)?;
1308
1309        let condition = Condition::new(field, operator, value);
1310        Ok(ConditionGroup::single(condition))
1311    }
1312
1313    fn parse_value(&self, value_str: &str) -> Result<Value> {
1314        let trimmed = value_str.trim();
1315
1316        // String literal
1317        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
1318            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
1319        {
1320            let unquoted = &trimmed[1..trimmed.len() - 1];
1321            return Ok(Value::String(unquoted.to_string()));
1322        }
1323
1324        // Boolean
1325        if trimmed.eq_ignore_ascii_case("true") {
1326            return Ok(Value::Boolean(true));
1327        }
1328        if trimmed.eq_ignore_ascii_case("false") {
1329            return Ok(Value::Boolean(false));
1330        }
1331
1332        // Null
1333        if trimmed.eq_ignore_ascii_case("null") {
1334            return Ok(Value::Null);
1335        }
1336
1337        // Number (try integer first, then float)
1338        if let Ok(int_val) = trimmed.parse::<i64>() {
1339            return Ok(Value::Integer(int_val));
1340        }
1341
1342        if let Ok(float_val) = trimmed.parse::<f64>() {
1343            return Ok(Value::Number(float_val));
1344        }
1345
1346        // Expression with arithmetic operators (e.g., "Order.quantity * Order.price")
1347        // Detect: contains operators AND (contains field reference OR multiple tokens)
1348        if self.is_expression(trimmed) {
1349            return Ok(Value::Expression(trimmed.to_string()));
1350        }
1351
1352        // Field reference (like User.Name)
1353        if trimmed.contains('.') {
1354            return Ok(Value::String(trimmed.to_string()));
1355        }
1356
1357        // Variable reference (identifier without quotes or dots)
1358        // This handles cases like: order_qty = moq
1359        // where 'moq' should be evaluated as a variable reference at runtime
1360        if self.is_identifier(trimmed) {
1361            return Ok(Value::Expression(trimmed.to_string()));
1362        }
1363
1364        // Default to string
1365        Ok(Value::String(trimmed.to_string()))
1366    }
1367
1368    /// Check if a string is a valid identifier (variable name)
1369    /// Valid identifiers: alphanumeric + underscore, starts with letter or underscore
1370    fn is_identifier(&self, s: &str) -> bool {
1371        if s.is_empty() {
1372            return false;
1373        }
1374
1375        // First character must be letter or underscore
1376        let first_char = s.chars().next().unwrap();
1377        if !first_char.is_alphabetic() && first_char != '_' {
1378            return false;
1379        }
1380
1381        // Rest must be alphanumeric or underscore
1382        s.chars().all(|c| c.is_alphanumeric() || c == '_')
1383    }
1384
1385    /// Check if a string is an arithmetic expression
1386    fn is_expression(&self, s: &str) -> bool {
1387        // Check for arithmetic operators
1388        let has_operator = s.contains('+')
1389            || s.contains('-')
1390            || s.contains('*')
1391            || s.contains('/')
1392            || s.contains('%');
1393
1394        // Check for field references (contains .)
1395        let has_field_ref = s.contains('.');
1396
1397        // Check for multiple tokens (spaces between operands/operators)
1398        let has_spaces = s.contains(' ');
1399
1400        // Expression if: has operator AND (has field reference OR has spaces)
1401        has_operator && (has_field_ref || has_spaces)
1402    }
1403
1404    fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1405        let statements: Vec<&str> = then_clause
1406            .split(';')
1407            .map(|s| s.trim())
1408            .filter(|s| !s.is_empty())
1409            .collect();
1410
1411        let mut actions = Vec::new();
1412
1413        for statement in statements {
1414            let action = self.parse_action_statement(statement)?;
1415            actions.push(action);
1416        }
1417
1418        Ok(actions)
1419    }
1420
1421    fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1422        let trimmed = statement.trim();
1423
1424        // Method call: $Object.method(args)
1425        if let Some(captures) = method_call_regex().captures(trimmed) {
1426            let object = captures.get(1).unwrap().as_str().to_string();
1427            let method = captures.get(2).unwrap().as_str().to_string();
1428            let args_str = captures.get(3).unwrap().as_str();
1429
1430            let args = if args_str.trim().is_empty() {
1431                Vec::new()
1432            } else {
1433                self.parse_method_args(args_str)?
1434            };
1435
1436            return Ok(ActionType::MethodCall {
1437                object,
1438                method,
1439                args,
1440            });
1441        }
1442
1443        // Check for compound assignment operators first (+=, -=, etc.)
1444        if let Some(plus_eq_pos) = trimmed.find("+=") {
1445            // Append operator: Field += Value
1446            let field = trimmed[..plus_eq_pos].trim().to_string();
1447            let value_str = trimmed[plus_eq_pos + 2..].trim();
1448            let value = self.parse_value(value_str)?;
1449
1450            return Ok(ActionType::Append { field, value });
1451        }
1452
1453        // Assignment: Field = Value
1454        if let Some(eq_pos) = trimmed.find('=') {
1455            let field = trimmed[..eq_pos].trim().to_string();
1456            let value_str = trimmed[eq_pos + 1..].trim();
1457            let value = self.parse_value(value_str)?;
1458
1459            return Ok(ActionType::Set { field, value });
1460        }
1461
1462        // Function calls: update($Object), retract($Object), etc.
1463        if let Some(captures) = function_binding_regex().captures(trimmed) {
1464            let function_name = captures.get(1).unwrap().as_str();
1465            let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
1466
1467            match function_name.to_lowercase().as_str() {
1468                "retract" => {
1469                    // Extract object name from $Object
1470                    let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1471                        stripped.to_string()
1472                    } else {
1473                        args_str.to_string()
1474                    };
1475                    Ok(ActionType::Retract {
1476                        object: object_name,
1477                    })
1478                }
1479                "log" => {
1480                    let message = if args_str.is_empty() {
1481                        "Log message".to_string()
1482                    } else {
1483                        let value = self.parse_value(args_str.trim())?;
1484                        value.to_string()
1485                    };
1486                    Ok(ActionType::Log { message })
1487                }
1488                "activateagendagroup" | "activate_agenda_group" => {
1489                    let agenda_group = if args_str.is_empty() {
1490                        return Err(RuleEngineError::ParseError {
1491                            message: "ActivateAgendaGroup requires agenda group name".to_string(),
1492                        });
1493                    } else {
1494                        let value = self.parse_value(args_str.trim())?;
1495                        match value {
1496                            Value::String(s) => s,
1497                            _ => value.to_string(),
1498                        }
1499                    };
1500                    Ok(ActionType::ActivateAgendaGroup {
1501                        group: agenda_group,
1502                    })
1503                }
1504                "schedulerule" | "schedule_rule" => {
1505                    // Parse delay and target rule: ScheduleRule(5000, "next-rule")
1506                    let parts: Vec<&str> = args_str.split(',').collect();
1507                    if parts.len() != 2 {
1508                        return Err(RuleEngineError::ParseError {
1509                            message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1510                        });
1511                    }
1512
1513                    let delay_ms = self.parse_value(parts[0].trim())?;
1514                    let rule_name = self.parse_value(parts[1].trim())?;
1515
1516                    let delay_ms = match delay_ms {
1517                        Value::Integer(i) => i as u64,
1518                        Value::Number(f) => f as u64,
1519                        _ => {
1520                            return Err(RuleEngineError::ParseError {
1521                                message: "ScheduleRule delay_ms must be a number".to_string(),
1522                            })
1523                        }
1524                    };
1525
1526                    let rule_name = match rule_name {
1527                        Value::String(s) => s,
1528                        _ => rule_name.to_string(),
1529                    };
1530
1531                    Ok(ActionType::ScheduleRule {
1532                        delay_ms,
1533                        rule_name,
1534                    })
1535                }
1536                "completeworkflow" | "complete_workflow" => {
1537                    let workflow_id = if args_str.is_empty() {
1538                        return Err(RuleEngineError::ParseError {
1539                            message: "CompleteWorkflow requires workflow_id".to_string(),
1540                        });
1541                    } else {
1542                        let value = self.parse_value(args_str.trim())?;
1543                        match value {
1544                            Value::String(s) => s,
1545                            _ => value.to_string(),
1546                        }
1547                    };
1548                    Ok(ActionType::CompleteWorkflow {
1549                        workflow_name: workflow_id,
1550                    })
1551                }
1552                "setworkflowdata" | "set_workflow_data" => {
1553                    // Parse key=value: SetWorkflowData("key=value")
1554                    let data_str = args_str.trim();
1555
1556                    // Simple key=value parsing
1557                    let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1558                        let key = data_str[..eq_pos].trim().trim_matches('"');
1559                        let value_str = data_str[eq_pos + 1..].trim();
1560                        let value = self.parse_value(value_str)?;
1561                        (key.to_string(), value)
1562                    } else {
1563                        return Err(RuleEngineError::ParseError {
1564                            message: "SetWorkflowData data must be in key=value format".to_string(),
1565                        });
1566                    };
1567
1568                    Ok(ActionType::SetWorkflowData { key, value })
1569                }
1570                _ => {
1571                    // All other functions become custom actions
1572                    let params = if args_str.is_empty() {
1573                        HashMap::new()
1574                    } else {
1575                        self.parse_function_args_as_params(args_str)?
1576                    };
1577
1578                    Ok(ActionType::Custom {
1579                        action_type: function_name.to_string(),
1580                        params,
1581                    })
1582                }
1583            }
1584        } else {
1585            // Custom statement
1586            Ok(ActionType::Custom {
1587                action_type: "statement".to_string(),
1588                params: {
1589                    let mut params = HashMap::new();
1590                    params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1591                    params
1592                },
1593            })
1594        }
1595    }
1596
1597    fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1598        if args_str.trim().is_empty() {
1599            return Ok(Vec::new());
1600        }
1601
1602        // Handle expressions like: $TestCar.Speed + $TestCar.SpeedIncrement
1603        let mut args = Vec::new();
1604        let parts: Vec<&str> = args_str.split(',').collect();
1605
1606        for part in parts {
1607            let trimmed = part.trim();
1608
1609            // Handle arithmetic expressions
1610            if trimmed.contains('+')
1611                || trimmed.contains('-')
1612                || trimmed.contains('*')
1613                || trimmed.contains('/')
1614            {
1615                // For now, store as string - the engine will evaluate
1616                args.push(Value::String(trimmed.to_string()));
1617            } else {
1618                args.push(self.parse_value(trimmed)?);
1619            }
1620        }
1621
1622        Ok(args)
1623    }
1624
1625    /// Parse function arguments as parameters for custom actions
1626    fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1627        let mut params = HashMap::new();
1628
1629        if args_str.trim().is_empty() {
1630            return Ok(params);
1631        }
1632
1633        // Parse positional parameters as numbered args
1634        let parts: Vec<&str> = args_str.split(',').collect();
1635        for (i, part) in parts.iter().enumerate() {
1636            let trimmed = part.trim();
1637            let value = self.parse_value(trimmed)?;
1638
1639            // Use simple numeric indexing - engine will resolve references dynamically
1640            params.insert(i.to_string(), value);
1641        }
1642
1643        Ok(params)
1644    }
1645
1646    /// Parse stream pattern condition
1647    /// Example: "login: LoginEvent from stream(\"logins\") over window(10 min, sliding)"
1648    #[cfg(feature = "streaming")]
1649    fn parse_stream_pattern_condition(&self, clause: &str) -> Result<ConditionGroup> {
1650        use crate::engine::rule::{StreamWindow, StreamWindowType};
1651        use crate::parser::grl::stream_syntax::parse_stream_pattern;
1652
1653        // Parse using nom parser
1654        let parse_result =
1655            parse_stream_pattern(clause).map_err(|e| RuleEngineError::ParseError {
1656                message: format!("Failed to parse stream pattern: {:?}", e),
1657            })?;
1658
1659        let (_, pattern) = parse_result;
1660
1661        // Convert WindowType from parser to StreamWindowType
1662        let window = pattern.source.window.map(|w| StreamWindow {
1663            duration: w.duration,
1664            window_type: match w.window_type {
1665                crate::parser::grl::stream_syntax::WindowType::Sliding => StreamWindowType::Sliding,
1666                crate::parser::grl::stream_syntax::WindowType::Tumbling => {
1667                    StreamWindowType::Tumbling
1668                }
1669                crate::parser::grl::stream_syntax::WindowType::Session { timeout } => {
1670                    StreamWindowType::Session { timeout }
1671                }
1672            },
1673        });
1674
1675        Ok(ConditionGroup::stream_pattern(
1676            pattern.var_name,
1677            pattern.event_type,
1678            pattern.source.stream_name,
1679            window,
1680        ))
1681    }
1682}
1683
1684#[cfg(test)]
1685mod tests {
1686    use super::GRLParser;
1687
1688    #[test]
1689    fn test_parse_simple_rule() {
1690        let grl = r#"
1691        rule "CheckAge" salience 10 {
1692            when
1693                User.Age >= 18
1694            then
1695                log("User is adult");
1696        }
1697        "#;
1698
1699        let rules = GRLParser::parse_rules(grl).unwrap();
1700        assert_eq!(rules.len(), 1);
1701        let rule = &rules[0];
1702        assert_eq!(rule.name, "CheckAge");
1703        assert_eq!(rule.salience, 10);
1704        assert_eq!(rule.actions.len(), 1);
1705    }
1706
1707    #[test]
1708    fn test_parse_complex_condition() {
1709        let grl = r#"
1710        rule "ComplexRule" {
1711            when
1712                User.Age >= 18 && User.Country == "US"
1713            then
1714                User.Qualified = true;
1715        }
1716        "#;
1717
1718        let rules = GRLParser::parse_rules(grl).unwrap();
1719        assert_eq!(rules.len(), 1);
1720        let rule = &rules[0];
1721        assert_eq!(rule.name, "ComplexRule");
1722    }
1723
1724    #[test]
1725    fn test_parse_new_syntax_with_parentheses() {
1726        let grl = r#"
1727        rule "Default Rule" salience 10 {
1728            when
1729                (user.age >= 18)
1730            then
1731                set(user.status, "approved");
1732        }
1733        "#;
1734
1735        let rules = GRLParser::parse_rules(grl).unwrap();
1736        assert_eq!(rules.len(), 1);
1737        let rule = &rules[0];
1738        assert_eq!(rule.name, "Default Rule");
1739        assert_eq!(rule.salience, 10);
1740        assert_eq!(rule.actions.len(), 1);
1741
1742        // Check that the action is parsed as a Custom action (set is now custom)
1743        match &rule.actions[0] {
1744            crate::types::ActionType::Custom {
1745                action_type,
1746                params,
1747            } => {
1748                assert_eq!(action_type, "set");
1749                assert_eq!(
1750                    params.get("0"),
1751                    Some(&crate::types::Value::String("user.status".to_string()))
1752                );
1753                assert_eq!(
1754                    params.get("1"),
1755                    Some(&crate::types::Value::String("approved".to_string()))
1756                );
1757            }
1758            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1759        }
1760    }
1761
1762    #[test]
1763    fn test_parse_complex_nested_conditions() {
1764        let grl = r#"
1765        rule "Complex Business Rule" salience 10 {
1766            when
1767                (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1768            then
1769                apply_discount(20000);
1770        }
1771        "#;
1772
1773        let rules = GRLParser::parse_rules(grl).unwrap();
1774        assert_eq!(rules.len(), 1);
1775        let rule = &rules[0];
1776        assert_eq!(rule.name, "Complex Business Rule");
1777        assert_eq!(rule.salience, 10);
1778        assert_eq!(rule.actions.len(), 1);
1779
1780        // Check that the action is parsed as a Custom action (apply_discount is now custom)
1781        match &rule.actions[0] {
1782            crate::types::ActionType::Custom {
1783                action_type,
1784                params,
1785            } => {
1786                assert_eq!(action_type, "apply_discount");
1787                assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1788            }
1789            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1790        }
1791    }
1792
1793    #[test]
1794    fn test_parse_no_loop_attribute() {
1795        let grl = r#"
1796        rule "NoLoopRule" no-loop salience 15 {
1797            when
1798                User.Score < 100
1799            then
1800                set(User.Score, User.Score + 10);
1801        }
1802        "#;
1803
1804        let rules = GRLParser::parse_rules(grl).unwrap();
1805        assert_eq!(rules.len(), 1);
1806        let rule = &rules[0];
1807        assert_eq!(rule.name, "NoLoopRule");
1808        assert_eq!(rule.salience, 15);
1809        assert!(rule.no_loop, "Rule should have no-loop=true");
1810    }
1811
1812    #[test]
1813    fn test_parse_no_loop_different_positions() {
1814        // Test no-loop before salience
1815        let grl1 = r#"
1816        rule "Rule1" no-loop salience 10 {
1817            when User.Age >= 18
1818            then log("adult");
1819        }
1820        "#;
1821
1822        // Test no-loop after salience
1823        let grl2 = r#"
1824        rule "Rule2" salience 10 no-loop {
1825            when User.Age >= 18
1826            then log("adult");
1827        }
1828        "#;
1829
1830        let rules1 = GRLParser::parse_rules(grl1).unwrap();
1831        let rules2 = GRLParser::parse_rules(grl2).unwrap();
1832
1833        assert_eq!(rules1.len(), 1);
1834        assert_eq!(rules2.len(), 1);
1835
1836        assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1837        assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1838
1839        assert_eq!(rules1[0].salience, 10);
1840        assert_eq!(rules2[0].salience, 10);
1841    }
1842
1843    #[test]
1844    fn test_parse_without_no_loop() {
1845        let grl = r#"
1846        rule "RegularRule" salience 5 {
1847            when
1848                User.Active == true
1849            then
1850                log("active user");
1851        }
1852        "#;
1853
1854        let rules = GRLParser::parse_rules(grl).unwrap();
1855        assert_eq!(rules.len(), 1);
1856        let rule = &rules[0];
1857        assert_eq!(rule.name, "RegularRule");
1858        assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1859    }
1860
1861    #[test]
1862    fn test_parse_exists_pattern() {
1863        let grl = r#"
1864        rule "ExistsRule" salience 20 {
1865            when
1866                exists(Customer.tier == "VIP")
1867            then
1868                System.premiumActive = true;
1869        }
1870        "#;
1871
1872        let rules = GRLParser::parse_rules(grl).unwrap();
1873        assert_eq!(rules.len(), 1);
1874        let rule = &rules[0];
1875        assert_eq!(rule.name, "ExistsRule");
1876        assert_eq!(rule.salience, 20);
1877
1878        // Check that condition is EXISTS pattern
1879        match &rule.conditions {
1880            crate::engine::rule::ConditionGroup::Exists(_) => {
1881                // Test passes
1882            }
1883            _ => panic!(
1884                "Expected EXISTS condition group, got: {:?}",
1885                rule.conditions
1886            ),
1887        }
1888    }
1889
1890    #[test]
1891    fn test_parse_forall_pattern() {
1892        let grl = r#"
1893        rule "ForallRule" salience 15 {
1894            when
1895                forall(Order.status == "processed")
1896            then
1897                Shipping.enabled = true;
1898        }
1899        "#;
1900
1901        let rules = GRLParser::parse_rules(grl).unwrap();
1902        assert_eq!(rules.len(), 1);
1903        let rule = &rules[0];
1904        assert_eq!(rule.name, "ForallRule");
1905
1906        // Check that condition is FORALL pattern
1907        match &rule.conditions {
1908            crate::engine::rule::ConditionGroup::Forall(_) => {
1909                // Test passes
1910            }
1911            _ => panic!(
1912                "Expected FORALL condition group, got: {:?}",
1913                rule.conditions
1914            ),
1915        }
1916    }
1917
1918    #[test]
1919    fn test_parse_combined_patterns() {
1920        let grl = r#"
1921        rule "CombinedRule" salience 25 {
1922            when
1923                exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1924            then
1925                System.vipMode = true;
1926        }
1927        "#;
1928
1929        let rules = GRLParser::parse_rules(grl).unwrap();
1930        assert_eq!(rules.len(), 1);
1931        let rule = &rules[0];
1932        assert_eq!(rule.name, "CombinedRule");
1933
1934        // Check that condition is AND with EXISTS and NOT(EXISTS) patterns
1935        match &rule.conditions {
1936            crate::engine::rule::ConditionGroup::Compound {
1937                left,
1938                operator,
1939                right,
1940            } => {
1941                assert_eq!(*operator, crate::types::LogicalOperator::And);
1942
1943                // Left should be EXISTS
1944                match left.as_ref() {
1945                    crate::engine::rule::ConditionGroup::Exists(_) => {
1946                        // Expected
1947                    }
1948                    _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1949                }
1950
1951                // Right should be NOT(EXISTS)
1952                match right.as_ref() {
1953                    crate::engine::rule::ConditionGroup::Not(inner) => {
1954                        match inner.as_ref() {
1955                            crate::engine::rule::ConditionGroup::Exists(_) => {
1956                                // Expected
1957                            }
1958                            _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1959                        }
1960                    }
1961                    _ => panic!("Expected NOT in right side, got: {:?}", right),
1962                }
1963            }
1964            _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1965        }
1966    }
1967}