rust_rule_engine/backward/
grl_query.rs

1//! GRL Query Syntax Implementation
2//!
3//! This module provides parsing and execution of backward chaining queries defined in GRL
4//! (Goal-driven Rule Language) syntax. GRL queries allow you to define goal-driven reasoning
5//! tasks with configurable search strategies and action handlers.
6//!
7//! # Features
8//!
9//! - **Declarative query syntax** - Define queries in a readable, structured format
10//! - **Multiple search strategies** - Choose between depth-first, breadth-first, or iterative deepening
11//! - **Action handlers** - Execute actions on query success, failure, or missing facts
12//! - **Conditional execution** - Use `when` clauses to conditionally execute queries
13//! - **Parameterized queries** - Support for query parameters (future enhancement)
14//!
15//! # GRL Query Syntax
16//!
17//! ```grl
18//! query "QueryName" {
19//!     goal: <expression>                    // Required: Goal to prove
20//!     strategy: <depth-first|breadth-first|iterative>  // Optional: Search strategy
21//!     max-depth: <number>                   // Optional: Maximum search depth
22//!     max-solutions: <number>               // Optional: Maximum solutions to find
23//!     enable-memoization: <true|false>      // Optional: Enable result caching
24//!
25//!     when: <condition>                     // Optional: Only execute if condition is true
26//!
27//!     on-success: {                         // Optional: Actions on successful proof
28//!         <variable> = <value>;
29//!         <FunctionName>(<args>);
30//!     }
31//!
32//!     on-failure: {                         // Optional: Actions on proof failure
33//!         <actions>
34//!     }
35//!
36//!     on-missing: {                         // Optional: Actions when facts are missing
37//!         <actions>
38//!     }
39//! }
40//! ```
41//!
42//! # Example
43//!
44//! ```rust
45//! use rust_rule_engine::backward::grl_query::{GRLQueryParser, GRLQueryExecutor};
46//! use rust_rule_engine::backward::BackwardEngine;
47//! use rust_rule_engine::{KnowledgeBase, Facts, Value};
48//!
49//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
50//! let query_str = r#"
51//! query "CheckVIPStatus" {
52//!     goal: User.IsVIP == true
53//!     strategy: depth-first
54//!     max-depth: 10
55//!     on-success: {
56//!         User.DiscountRate = 0.2;
57//!         LogMessage("VIP confirmed");
58//!     }
59//!     on-failure: {
60//!         LogMessage("Not a VIP user");
61//!     }
62//! }
63//! "#;
64//!
65//! let query = GRLQueryParser::parse(query_str)?;
66//! let mut bc_engine = BackwardEngine::new(KnowledgeBase::new("kb"));
67//! let mut facts = Facts::new();
68//! facts.set("User.LoyaltyPoints", Value::Number(1500.0));
69//!
70//! let result = GRLQueryExecutor::execute(&query, &mut bc_engine, &mut facts)?;
71//!
72//! if result.provable {
73//!     println!("Goal proven!");
74//! }
75//! # Ok(())
76//! # }
77//! ```
78//!
79//! # Supported Functions in Actions
80//!
81//! - `LogMessage(message)` - Print a log message
82//! - `Request(message)` - Send a request message
83//! - `Print(message)` - Print output
84//! - `Debug(message)` - Print debug output to stderr
85
86use crate::errors::RuleEngineError;
87use crate::{Facts, Value};
88use super::backward_engine::{BackwardEngine, BackwardConfig};
89use super::search::SearchStrategy;
90use super::query::{QueryResult, QueryStats, ProofTrace};
91
92use std::collections::HashMap;
93
94/// Search strategy option for queries
95#[derive(Debug, Clone, PartialEq)]
96pub enum GRLSearchStrategy {
97    DepthFirst,
98    BreadthFirst,
99    Iterative,
100}
101
102impl Default for GRLSearchStrategy {
103    fn default() -> Self {
104        GRLSearchStrategy::DepthFirst
105    }
106}
107
108/// Action to execute based on query result
109#[derive(Debug, Clone)]
110pub struct QueryAction {
111    /// Assignment: Variable = Value (as string to be parsed)
112    pub assignments: Vec<(String, String)>,
113    /// Function/method calls
114    pub calls: Vec<String>,
115}
116
117impl QueryAction {
118    pub fn new() -> Self {
119        QueryAction {
120            assignments: Vec::new(),
121            calls: Vec::new(),
122        }
123    }
124
125    /// Execute the action on the given facts
126    pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
127        // Execute assignments - for now just log them
128        for (var_name, value_str) in &self.assignments {
129            // Simple value parsing
130            let value = if value_str == "true" {
131                Value::Boolean(true)
132            } else if value_str == "false" {
133                Value::Boolean(false)
134            } else if let Ok(n) = value_str.parse::<f64>() {
135                Value::Number(n)
136            } else {
137                // Remove quotes if present
138                let cleaned = value_str.trim_matches('"');
139                Value::String(cleaned.to_string())
140            };
141            
142            facts.set(var_name, value);
143        }
144
145        // Execute function calls
146        for call in &self.calls {
147            self.execute_function_call(call)?;
148        }
149
150        Ok(())
151    }
152
153    /// Execute a single function call
154    fn execute_function_call(&self, call: &str) -> Result<(), RuleEngineError> {
155        let call = call.trim();
156
157        // Parse function name and arguments
158        if let Some(open_paren) = call.find('(') {
159            let func_name = call[..open_paren].trim();
160
161            // Extract arguments (everything between first ( and last ))
162            if let Some(close_paren) = call.rfind(')') {
163                let args_str = &call[open_paren + 1..close_paren];
164
165                match func_name {
166                    "LogMessage" => {
167                        // Parse string argument (remove quotes if present)
168                        let message = args_str.trim().trim_matches('"').trim_matches('\'');
169                        println!("[LOG] {}", message);
170                    }
171                    "Request" => {
172                        // Parse request call
173                        let message = args_str.trim().trim_matches('"').trim_matches('\'');
174                        println!("[REQUEST] {}", message);
175                    }
176                    "Print" => {
177                        // Generic print function
178                        let message = args_str.trim().trim_matches('"').trim_matches('\'');
179                        println!("{}", message);
180                    }
181                    "Debug" => {
182                        // Debug output
183                        let message = args_str.trim().trim_matches('"').trim_matches('\'');
184                        eprintln!("[DEBUG] {}", message);
185                    }
186                    other => {
187                        // Unknown function - log warning but don't fail
188                        eprintln!("[WARNING] Unknown function call in query action: {}({})", other, args_str);
189                    }
190                }
191            } else {
192                return Err(RuleEngineError::ParseError {
193                    message: format!("Malformed function call (missing closing paren): {}", call),
194                });
195            }
196        } else {
197            return Err(RuleEngineError::ParseError {
198                message: format!("Malformed function call (missing opening paren): {}", call),
199            });
200        }
201
202        Ok(())
203    }
204}
205
206/// A GRL Query definition
207#[derive(Debug, Clone)]
208pub struct GRLQuery {
209    /// Query name
210    pub name: String,
211    
212    /// Goal pattern to prove (as string expression)
213    pub goal: String,
214    
215    /// Search strategy
216    pub strategy: GRLSearchStrategy,
217    
218    /// Maximum search depth
219    pub max_depth: usize,
220    
221    /// Maximum number of solutions
222    pub max_solutions: usize,
223    
224    /// Enable memoization
225    pub enable_memoization: bool,
226    
227    /// Action on success
228    pub on_success: Option<QueryAction>,
229    
230    /// Action on failure
231    pub on_failure: Option<QueryAction>,
232    
233    /// Action on missing facts
234    pub on_missing: Option<QueryAction>,
235    
236    /// Parameters for parameterized queries
237    pub params: HashMap<String, String>, // param_name -> type
238    
239    /// Conditional execution (as string condition)
240    pub when_condition: Option<String>,
241}
242
243impl GRLQuery {
244    /// Create a new query with defaults
245    pub fn new(name: String, goal: String) -> Self {
246        GRLQuery {
247            name,
248            goal,
249            strategy: GRLSearchStrategy::default(),
250            max_depth: 10,
251            max_solutions: 1,
252            enable_memoization: true,
253            on_success: None,
254            on_failure: None,
255            on_missing: None,
256            params: HashMap::new(),
257            when_condition: None,
258        }
259    }
260
261    /// Set search strategy
262    pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
263        self.strategy = strategy;
264        self
265    }
266
267    /// Set max depth
268    pub fn with_max_depth(mut self, max_depth: usize) -> Self {
269        self.max_depth = max_depth;
270        self
271    }
272
273    /// Set max solutions
274    pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
275        self.max_solutions = max_solutions;
276        self
277    }
278
279    /// Set memoization
280    pub fn with_memoization(mut self, enable: bool) -> Self {
281        self.enable_memoization = enable;
282        self
283    }
284
285    /// Add success action
286    pub fn with_on_success(mut self, action: QueryAction) -> Self {
287        self.on_success = Some(action);
288        self
289    }
290
291    /// Add failure action
292    pub fn with_on_failure(mut self, action: QueryAction) -> Self {
293        self.on_failure = Some(action);
294        self
295    }
296
297    /// Add missing facts action
298    pub fn with_on_missing(mut self, action: QueryAction) -> Self {
299        self.on_missing = Some(action);
300        self
301    }
302
303    /// Add parameter
304    pub fn with_param(mut self, name: String, type_name: String) -> Self {
305        self.params.insert(name, type_name);
306        self
307    }
308
309    /// Set conditional execution
310    pub fn with_when(mut self, condition: String) -> Self {
311        self.when_condition = Some(condition);
312        self
313    }
314
315    /// Check if query should execute based on when condition
316    pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
317        // If there's no when condition, execute by default
318        if self.when_condition.is_none() {
319            return Ok(true);
320        }
321
322        // Parse and evaluate the when condition expression against the current facts
323        if let Some(ref cond_str) = self.when_condition {
324            use crate::backward::expression::ExpressionParser;
325
326            match ExpressionParser::parse(cond_str) {
327                Ok(expr) => Ok(expr.is_satisfied(_facts)),
328                Err(e) => Err(e),
329            }
330        } else {
331            Ok(true)
332        }
333    }
334
335    /// Execute success actions
336    pub fn execute_success_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
337        if let Some(ref action) = self.on_success {
338            action.execute(facts)?;
339        }
340        Ok(())
341    }
342
343    /// Execute failure actions
344    pub fn execute_failure_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
345        if let Some(ref action) = self.on_failure {
346            action.execute(facts)?;
347        }
348        Ok(())
349    }
350
351    /// Execute missing facts actions
352    pub fn execute_missing_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
353        if let Some(ref action) = self.on_missing {
354            action.execute(facts)?;
355        }
356        Ok(())
357    }
358
359    /// Convert to BackwardConfig
360    pub fn to_config(&self) -> BackwardConfig {
361        let search_strategy = match self.strategy {
362            GRLSearchStrategy::DepthFirst => SearchStrategy::DepthFirst,
363            GRLSearchStrategy::BreadthFirst => SearchStrategy::BreadthFirst,
364            GRLSearchStrategy::Iterative => SearchStrategy::Iterative,
365        };
366
367        BackwardConfig {
368            strategy: search_strategy,
369            max_depth: self.max_depth,
370            enable_memoization: self.enable_memoization,
371            max_solutions: self.max_solutions,
372        }
373    }
374}
375
376/// Parser for GRL Query syntax
377pub struct GRLQueryParser;
378
379impl GRLQueryParser {
380    /// Parse a query from string
381    /// 
382    /// # Example
383    /// ```
384    /// let query_str = r#"
385    /// query "CheckVIP" {
386    ///     goal: User.IsVIP == true
387    ///     strategy: depth-first
388    /// }
389    /// "#;
390    /// let query = rust_rule_engine::backward::GRLQueryParser::parse(query_str).unwrap();
391    /// ```
392    pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
393        let input = input.trim();
394
395        // Extract query name
396        let name = Self::extract_query_name(input)?;
397
398        // Extract goal
399        let goal = Self::extract_goal(input)?;
400
401        // Create base query
402        let mut query = GRLQuery::new(name, goal);
403
404        // Parse optional attributes
405        if let Some(strategy) = Self::extract_strategy(input) {
406            query.strategy = strategy;
407        }
408
409        if let Some(max_depth) = Self::extract_max_depth(input) {
410            query.max_depth = max_depth;
411        }
412
413        if let Some(max_solutions) = Self::extract_max_solutions(input) {
414            query.max_solutions = max_solutions;
415        }
416
417        if let Some(enable_memo) = Self::extract_memoization(input) {
418            query.enable_memoization = enable_memo;
419        }
420
421        // Parse actions
422        if let Some(action) = Self::extract_on_success(input)? {
423            query.on_success = Some(action);
424        }
425
426        if let Some(action) = Self::extract_on_failure(input)? {
427            query.on_failure = Some(action);
428        }
429
430        if let Some(action) = Self::extract_on_missing(input)? {
431            query.on_missing = Some(action);
432        }
433
434        // Parse when condition
435        if let Some(condition) = Self::extract_when_condition(input)? {
436            query.when_condition = Some(condition);
437        }
438
439        Ok(query)
440    }
441
442    fn extract_query_name(input: &str) -> Result<String, RuleEngineError> {
443        let re = regex::Regex::new(r#"query\s+"([^"]+)"\s*\{"#).unwrap();
444        if let Some(caps) = re.captures(input) {
445            Ok(caps[1].to_string())
446        } else {
447            Err(RuleEngineError::ParseError {
448                message: "Invalid query syntax: missing query name".to_string(),
449            })
450        }
451    }
452
453    fn extract_goal(input: &str) -> Result<String, RuleEngineError> {
454        // Find goal: line
455        if let Some(goal_start) = input.find("goal:") {
456            let after_goal = &input[goal_start + 5..]; // Skip "goal:"
457
458            // Find the end of the goal line (newline or end of attributes section)
459            // Goal ends at newline, but we need to handle parentheses carefully
460            let goal_end = Self::find_goal_end(after_goal)?;
461            let goal_str = after_goal[..goal_end].trim().to_string();
462
463            if goal_str.is_empty() {
464                return Err(RuleEngineError::ParseError {
465                    message: "Invalid query syntax: empty goal".to_string(),
466                });
467            }
468
469            Ok(goal_str)
470        } else {
471            Err(RuleEngineError::ParseError {
472                message: "Invalid query syntax: missing goal".to_string(),
473            })
474        }
475    }
476
477    fn find_goal_end(input: &str) -> Result<usize, RuleEngineError> {
478        let mut paren_depth = 0;
479        let mut in_string = false;
480        let mut escape_next = false;
481
482        for (i, ch) in input.chars().enumerate() {
483            if escape_next {
484                escape_next = false;
485                continue;
486            }
487
488            match ch {
489                '\\' if in_string => escape_next = true,
490                '"' => in_string = !in_string,
491                '(' if !in_string => paren_depth += 1,
492                ')' if !in_string => {
493                    if paren_depth == 0 {
494                        return Err(RuleEngineError::ParseError {
495                            message: format!("Parse error: Unexpected closing parenthesis at position {}", i),
496                        });
497                    }
498                    paren_depth -= 1;
499                }
500                '\n' if !in_string && paren_depth == 0 => return Ok(i),
501                _ => {}
502            }
503        }
504
505        if in_string {
506            return Err(RuleEngineError::ParseError {
507                message: "Parse error: Unclosed string in goal".to_string(),
508            });
509        }
510
511        if paren_depth > 0 {
512            return Err(RuleEngineError::ParseError {
513                message: format!("Parse error: {} unclosed parentheses in goal", paren_depth),
514            });
515        }
516
517        // If we reach here, goal extends to end of input
518        Ok(input.len())
519    }
520
521    fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
522        let re = regex::Regex::new(r"strategy:\s*([a-z-]+)").unwrap();
523        re.captures(input).and_then(|caps| {
524            match caps[1].trim() {
525                "depth-first" => Some(GRLSearchStrategy::DepthFirst),
526                "breadth-first" => Some(GRLSearchStrategy::BreadthFirst),
527                "iterative" => Some(GRLSearchStrategy::Iterative),
528                _ => None,
529            }
530        })
531    }
532
533    fn extract_max_depth(input: &str) -> Option<usize> {
534        let re = regex::Regex::new(r"max-depth:\s*(\d+)").unwrap();
535        re.captures(input)
536            .and_then(|caps| caps[1].parse().ok())
537    }
538
539    fn extract_max_solutions(input: &str) -> Option<usize> {
540        let re = regex::Regex::new(r"max-solutions:\s*(\d+)").unwrap();
541        re.captures(input)
542            .and_then(|caps| caps[1].parse().ok())
543    }
544
545    fn extract_memoization(input: &str) -> Option<bool> {
546        let re = regex::Regex::new(r"enable-memoization:\s*(true|false)").unwrap();
547        re.captures(input).and_then(|caps| {
548            match caps[1].trim() {
549                "true" => Some(true),
550                "false" => Some(false),
551                _ => None,
552            }
553        })
554    }
555
556    fn extract_on_success(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
557        Self::extract_action_block(input, "on-success")
558    }
559
560    fn extract_on_failure(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
561        Self::extract_action_block(input, "on-failure")
562    }
563
564    fn extract_on_missing(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
565        Self::extract_action_block(input, "on-missing")
566    }
567
568    fn extract_action_block(input: &str, action_name: &str) -> Result<Option<QueryAction>, RuleEngineError> {
569        let pattern = format!(r"{}:\s*\{{([^}}]+)\}}", action_name);
570        let re = regex::Regex::new(&pattern).unwrap();
571        
572        if let Some(caps) = re.captures(input) {
573            let block = caps[1].trim();
574            let mut action = QueryAction::new();
575
576            // Parse assignments: Variable = Value
577            let assign_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*([^;]+);").unwrap();
578            for caps in assign_re.captures_iter(block) {
579                let var_name = caps[1].trim().to_string();
580                let value_str = caps[2].trim().to_string();
581                action.assignments.push((var_name, value_str));
582            }
583
584            // Parse function calls: Function(...)
585            let call_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_]*\([^)]*\));").unwrap();
586            for caps in call_re.captures_iter(block) {
587                action.calls.push(caps[1].trim().to_string());
588            }
589
590            Ok(Some(action))
591        } else {
592            Ok(None)
593        }
594    }
595
596    fn extract_when_condition(input: &str) -> Result<Option<String>, RuleEngineError> {
597        let re = regex::Regex::new(r"when:\s*([^\n}]+)").unwrap();
598        if let Some(caps) = re.captures(input) {
599            let condition_str = caps[1].trim().to_string();
600            Ok(Some(condition_str))
601        } else {
602            Ok(None)
603        }
604    }
605
606    /// Parse multiple queries from a file
607    pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
608        let mut queries = Vec::new();
609        
610        // Find all query blocks - use simpler approach
611        // Split by "query" keyword and process each block
612        let parts: Vec<&str> = input.split("query").collect();
613        
614        for part in parts.iter().skip(1) { // Skip first empty part
615            let query_str = format!("query{}", part);
616            // Find the matching closing brace
617            if let Some(end_idx) = find_matching_brace(&query_str) {
618                let complete_query = &query_str[..end_idx];
619                if let Ok(query) = Self::parse(complete_query) {
620                    queries.push(query);
621                }
622            }
623        }
624
625        Ok(queries)
626    }
627}
628
629// Helper function to find matching closing brace
630fn find_matching_brace(input: &str) -> Option<usize> {
631    let mut depth = 0;
632    let mut in_string = false;
633    let mut escape_next = false;
634    
635    for (i, ch) in input.chars().enumerate() {
636        if escape_next {
637            escape_next = false;
638            continue;
639        }
640        
641        match ch {
642            '\\' => escape_next = true,
643            '"' => in_string = !in_string,
644            '{' if !in_string => depth += 1,
645            '}' if !in_string => {
646                depth -= 1;
647                if depth == 0 {
648                    return Some(i + 1);
649                }
650            }
651            _ => {}
652        }
653    }
654    
655    None
656}
657
658/// Executor for GRL queries
659pub struct GRLQueryExecutor;
660
661impl GRLQueryExecutor {
662    /// Execute a single query
663    pub fn execute(
664        query: &GRLQuery,
665        bc_engine: &mut BackwardEngine,
666        facts: &mut Facts,
667    ) -> Result<QueryResult, RuleEngineError> {
668        // Check when condition
669        if !query.should_execute(facts)? {
670            return Ok(QueryResult {
671                provable: false,
672                bindings: HashMap::new(),
673                proof_trace: ProofTrace { goal: String::new(), steps: Vec::new() },
674                missing_facts: Vec::new(),
675                stats: QueryStats::default(),
676                solutions: Vec::new(),
677            });
678        }
679
680        // Apply config
681        bc_engine.set_config(query.to_config());
682
683        // Parse compound goals (support &&, ||, and !=)
684        let result = if query.goal.contains("&&") && query.goal.contains("||") {
685            // Complex expression with both AND and OR - need proper parsing
686            // For now, evaluate left-to-right with precedence: AND before OR
687            Self::execute_complex_goal(&query.goal, bc_engine, facts)?
688        } else if query.goal.contains("||") {
689            // Split on || and check any goal (OR logic)
690            Self::execute_compound_or_goal(&query.goal, bc_engine, facts)?
691        } else if query.goal.contains("&&") {
692            // Split on && and check all goals (AND logic)
693            Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
694        } else {
695            // Single goal
696            bc_engine.query(&query.goal, facts)?
697        };
698
699        // Execute appropriate actions
700        if result.provable {
701            query.execute_success_actions(facts)?;
702        } else if !result.missing_facts.is_empty() {
703            query.execute_missing_actions(facts)?;
704        } else {
705            query.execute_failure_actions(facts)?;
706        }
707
708        Ok(result)
709    }
710
711    /// Execute compound AND goal (all must be true)
712    fn execute_compound_and_goal(
713        goal_expr: &str,
714        bc_engine: &mut BackwardEngine,
715        facts: &mut Facts,
716    ) -> Result<QueryResult, RuleEngineError> {
717        let sub_goals: Vec<&str> = goal_expr.split("&&").map(|s| s.trim()).collect();
718
719        let mut all_provable = true;
720        let mut combined_bindings = HashMap::new();
721        let mut all_missing = Vec::new();
722        let mut combined_stats = QueryStats::default();
723
724        for (i, sub_goal) in sub_goals.iter().enumerate() {
725            // Handle != by using expression parser directly
726            let goal_satisfied = if sub_goal.contains("!=") {
727                // Parse and evaluate the expression directly
728                use crate::backward::expression::ExpressionParser;
729
730                match ExpressionParser::parse(sub_goal) {
731                    Ok(expr) => expr.is_satisfied(facts),
732                    Err(_) => false,
733                }
734            } else {
735                // Normal == comparison, use backward chaining
736                let result = bc_engine.query(sub_goal, facts)?;
737                result.provable
738            };
739
740            if !goal_satisfied {
741                all_provable = false;
742            }
743
744            // Note: For compound goals with !=, we don't track missing facts well yet
745            // This is a simplification for now
746        }
747
748        Ok(QueryResult {
749            provable: all_provable,
750            bindings: combined_bindings,
751            proof_trace: ProofTrace {
752                goal: goal_expr.to_string(),
753                steps: Vec::new()
754            },
755            missing_facts: all_missing,
756            stats: combined_stats,
757            solutions: Vec::new(),
758        })
759    }
760
761    /// Execute compound OR goal (any must be true)
762    fn execute_compound_or_goal(
763        goal_expr: &str,
764        bc_engine: &mut BackwardEngine,
765        facts: &mut Facts,
766    ) -> Result<QueryResult, RuleEngineError> {
767        let sub_goals: Vec<&str> = goal_expr.split("||").map(|s| s.trim()).collect();
768
769        let mut any_provable = false;
770        let mut combined_bindings = HashMap::new();
771        let mut all_missing = Vec::new();
772        let mut combined_stats = QueryStats::default();
773        let mut all_solutions = Vec::new();
774
775        for sub_goal in sub_goals.iter() {
776            // Handle != by using expression parser directly
777            let (goal_satisfied, result_opt) = if sub_goal.contains("!=") {
778                // Parse and evaluate the expression directly
779                use crate::backward::expression::ExpressionParser;
780
781                match ExpressionParser::parse(sub_goal) {
782                    Ok(expr) => (expr.is_satisfied(facts), None),
783                    Err(_) => (false, None),
784                }
785            } else {
786                // Normal == comparison, use backward chaining
787                let result = bc_engine.query(sub_goal, facts)?;
788                let provable = result.provable;
789                (provable, Some(result))
790            };
791
792            if goal_satisfied {
793                any_provable = true;
794
795                // Merge results from successful branch
796                if let Some(result) = result_opt {
797                    combined_bindings.extend(result.bindings);
798                    all_missing.extend(result.missing_facts);
799                    combined_stats.goals_explored += result.stats.goals_explored;
800                    combined_stats.rules_evaluated += result.stats.rules_evaluated;
801                    if let Some(dur) = result.stats.duration_ms {
802                        combined_stats.duration_ms = Some(combined_stats.duration_ms.unwrap_or(0) + dur);
803                    }
804                    all_solutions.extend(result.solutions);
805                }
806            }
807        }
808
809        Ok(QueryResult {
810            provable: any_provable,
811            bindings: combined_bindings,
812            proof_trace: ProofTrace {
813                goal: goal_expr.to_string(),
814                steps: Vec::new()
815            },
816            missing_facts: all_missing,
817            stats: combined_stats,
818            solutions: all_solutions,
819        })
820    }
821
822    /// Strip outer parentheses from expression
823    fn strip_outer_parens(expr: &str) -> &str {
824        let trimmed = expr.trim();
825        if trimmed.starts_with('(') && trimmed.ends_with(')') {
826            // Check if these are matching outer parens
827            let inner = &trimmed[1..trimmed.len()-1];
828            let mut depth = 0;
829            for ch in inner.chars() {
830                match ch {
831                    '(' => depth += 1,
832                    ')' => {
833                        depth -= 1;
834                        if depth < 0 {
835                            // Closing paren in middle, so outer parens don't match
836                            return trimmed;
837                        }
838                    }
839                    _ => {}
840                }
841            }
842            if depth == 0 {
843                // Outer parens match, return inner
844                return inner.trim();
845            }
846        }
847        trimmed
848    }
849
850    /// Execute complex goal with both AND and OR operators
851    /// Precedence: AND is evaluated before OR (like multiplication before addition)
852    /// Example: "A || B && C" is evaluated as "A || (B && C)"
853    fn execute_complex_goal(
854        goal_expr: &str,
855        bc_engine: &mut BackwardEngine,
856        facts: &mut Facts,
857    ) -> Result<QueryResult, RuleEngineError> {
858        // Strip outer parentheses first
859        let cleaned_expr = Self::strip_outer_parens(goal_expr);
860
861        // Split by || first (lowest precedence)
862        let or_parts: Vec<&str> = cleaned_expr.split("||").map(|s| s.trim()).collect();
863
864        let mut any_provable = false;
865        let mut combined_bindings = HashMap::new();
866        let mut all_missing = Vec::new();
867        let mut combined_stats = QueryStats::default();
868        let mut all_solutions = Vec::new();
869
870        for or_part in or_parts.iter() {
871            // Strip parentheses from each part
872            let cleaned_part = Self::strip_outer_parens(or_part);
873
874            // Each OR part might contain AND clauses
875            let result = if cleaned_part.contains("&&") {
876                Self::execute_compound_and_goal(cleaned_part, bc_engine, facts)?
877            } else {
878                bc_engine.query(cleaned_part, facts)?
879            };
880
881            if result.provable {
882                any_provable = true;
883                combined_bindings.extend(result.bindings);
884                all_missing.extend(result.missing_facts);
885                combined_stats.goals_explored += result.stats.goals_explored;
886                combined_stats.rules_evaluated += result.stats.rules_evaluated;
887                if let Some(dur) = result.stats.duration_ms {
888                    combined_stats.duration_ms = Some(combined_stats.duration_ms.unwrap_or(0) + dur);
889                }
890                all_solutions.extend(result.solutions);
891            }
892        }
893
894        Ok(QueryResult {
895            provable: any_provable,
896            bindings: combined_bindings,
897            proof_trace: ProofTrace {
898                goal: goal_expr.to_string(),
899                steps: Vec::new()
900            },
901            missing_facts: all_missing,
902            stats: combined_stats,
903            solutions: all_solutions,
904        })
905    }
906
907    /// Execute multiple queries
908    pub fn execute_queries(
909        queries: &[GRLQuery],
910        bc_engine: &mut BackwardEngine,
911        facts: &mut Facts,
912    ) -> Result<Vec<QueryResult>, RuleEngineError> {
913        let mut results = Vec::new();
914
915        for query in queries {
916            let result = Self::execute(query, bc_engine, facts)?;
917            results.push(result);
918        }
919
920        Ok(results)
921    }
922}
923
924#[cfg(test)]
925mod tests {
926    use super::*;
927
928    #[test]
929    fn test_parse_simple_query() {
930        let input = r#"
931        query "TestQuery" {
932            goal: User.IsVIP == true
933        }
934        "#;
935
936        let query = GRLQueryParser::parse(input).unwrap();
937        assert_eq!(query.name, "TestQuery");
938        assert_eq!(query.strategy, GRLSearchStrategy::DepthFirst);
939        assert_eq!(query.max_depth, 10);
940    }
941
942    #[test]
943    fn test_parse_query_with_strategy() {
944        let input = r#"
945        query "TestQuery" {
946            goal: User.IsVIP == true
947            strategy: breadth-first
948            max-depth: 5
949        }
950        "#;
951
952        let query = GRLQueryParser::parse(input).unwrap();
953        assert_eq!(query.strategy, GRLSearchStrategy::BreadthFirst);
954        assert_eq!(query.max_depth, 5);
955    }
956
957    #[test]
958    fn test_parse_query_with_actions() {
959        let input = r#"
960        query "TestQuery" {
961            goal: User.IsVIP == true
962            on-success: {
963                User.DiscountRate = 0.2;
964                LogMessage("VIP confirmed");
965            }
966        }
967        "#;
968
969        let query = GRLQueryParser::parse(input).unwrap();
970        assert!(query.on_success.is_some());
971        
972        let action = query.on_success.unwrap();
973        assert_eq!(action.assignments.len(), 1);
974        assert_eq!(action.calls.len(), 1);
975    }
976
977    #[test]
978    fn test_parse_query_with_when_condition() {
979        let input = r#"
980        query "TestQuery" {
981            goal: User.IsVIP == true
982            when: Environment.Mode == "Production"
983        }
984        "#;
985
986        let query = GRLQueryParser::parse(input).unwrap();
987        assert!(query.when_condition.is_some());
988    }
989
990    #[test]
991    fn test_parse_multiple_queries() {
992        let input = r#"
993        query "Query1" {
994            goal: A == true
995        }
996        
997        query "Query2" {
998            goal: B == true
999            strategy: breadth-first
1000        }
1001        "#;
1002
1003        let queries = GRLQueryParser::parse_queries(input).unwrap();
1004        assert_eq!(queries.len(), 2);
1005        assert_eq!(queries[0].name, "Query1");
1006        assert_eq!(queries[1].name, "Query2");
1007    }
1008
1009    #[test]
1010    fn test_query_config_conversion() {
1011        let query = GRLQuery::new(
1012            "Test".to_string(),
1013            "X == true".to_string(),
1014        )
1015        .with_strategy(GRLSearchStrategy::BreadthFirst)
1016        .with_max_depth(15)
1017        .with_memoization(false);
1018
1019        let config = query.to_config();
1020        assert_eq!(config.max_depth, 15);
1021        assert_eq!(config.enable_memoization, false);
1022    }
1023
1024    #[test]
1025    fn test_action_execution() {
1026        let mut facts = Facts::new();
1027
1028        let mut action = QueryAction::new();
1029        action.assignments.push((
1030            "User.DiscountRate".to_string(),
1031            "0.2".to_string(),
1032        ));
1033
1034        action.execute(&mut facts).unwrap();
1035
1036        // Check that assignment was executed
1037        let value = facts.get("User.DiscountRate");
1038        assert!(value.is_some());
1039    }
1040
1041    #[test]
1042    fn test_should_execute_no_condition() {
1043        let query = GRLQuery::new("Q".to_string(), "X == true".to_string());
1044        let facts = Facts::new();
1045        // No when condition -> should execute
1046        let res = query.should_execute(&facts).unwrap();
1047        assert!(res);
1048    }
1049
1050    #[test]
1051    fn test_should_execute_condition_true() {
1052        let mut facts = Facts::new();
1053        facts.set("Environment.Mode", Value::String("Production".to_string()));
1054
1055        let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1056            .with_when("Environment.Mode == \"Production\"".to_string());
1057
1058        let res = query.should_execute(&facts).unwrap();
1059        assert!(res, "expected when condition to be satisfied");
1060    }
1061
1062    #[test]
1063    fn test_should_execute_condition_false() {
1064        let mut facts = Facts::new();
1065        facts.set("Environment.Mode", Value::String("Development".to_string()));
1066
1067        let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1068            .with_when("Environment.Mode == \"Production\"".to_string());
1069
1070        let res = query.should_execute(&facts).unwrap();
1071        assert!(!res, "expected when condition to be unsatisfied");
1072    }
1073
1074    #[test]
1075    fn test_should_execute_parse_error_propagates() {
1076        let facts = Facts::new();
1077        // Use an unterminated string literal to force a parse error from the expression parser
1078        let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1079            .with_when("Environment.Mode == \"Production".to_string());
1080
1081        let res = query.should_execute(&facts);
1082        assert!(res.is_err(), "expected parse error to propagate");
1083    }
1084
1085    #[test]
1086    fn test_parse_query_with_or_goal() {
1087        let input = r#"
1088        query "TestOR" {
1089            goal: User.IsVIP == true || User.TotalSpent > 10000
1090        }
1091        "#;
1092
1093        let query = GRLQueryParser::parse(input).unwrap();
1094        assert_eq!(query.name, "TestOR");
1095        assert!(query.goal.contains("||"));
1096    }
1097
1098    #[test]
1099    fn test_parse_query_with_complex_goal() {
1100        let input = r#"
1101        query "ComplexQuery" {
1102            goal: (User.IsVIP == true && User.Active == true) || User.TotalSpent > 10000
1103        }
1104        "#;
1105
1106        let query = GRLQueryParser::parse(input).unwrap();
1107        assert!(query.goal.contains("||"));
1108        assert!(query.goal.contains("&&"));
1109    }
1110
1111    #[test]
1112    fn test_parse_query_with_multiple_or_branches() {
1113        let input = r#"
1114        query "MultiOR" {
1115            goal: Employee.IsManager == true || Employee.IsSenior == true || Employee.IsDirector == true
1116        }
1117        "#;
1118
1119        let query = GRLQueryParser::parse(input).unwrap();
1120        let branches: Vec<&str> = query.goal.split("||").collect();
1121        assert_eq!(branches.len(), 3);
1122    }
1123
1124    #[test]
1125    fn test_parse_query_with_parentheses() {
1126        let input = r#"
1127        query "ParenQuery" {
1128            goal: (User.IsVIP == true && User.Active == true) || User.TotalSpent > 10000
1129        }
1130        "#;
1131
1132        let query = GRLQueryParser::parse(input).unwrap();
1133        assert!(query.goal.contains("("));
1134        assert!(query.goal.contains(")"));
1135        assert!(query.goal.contains("||"));
1136        assert!(query.goal.contains("&&"));
1137    }
1138
1139    #[test]
1140    fn test_parse_query_with_nested_parentheses() {
1141        let input = r#"
1142        query "NestedParen" {
1143            goal: ((A == true && B == true) || C == true) && D == true
1144        }
1145        "#;
1146
1147        let query = GRLQueryParser::parse(input).unwrap();
1148        assert_eq!(query.name, "NestedParen");
1149        // Check that parentheses are preserved
1150        assert!(query.goal.starts_with("(("));
1151    }
1152
1153    #[test]
1154    fn test_parse_query_unclosed_parenthesis() {
1155        let input = r#"
1156        query "BadParen" {
1157            goal: (User.IsVIP == true && User.Active == true
1158        }
1159        "#;
1160
1161        let result = GRLQueryParser::parse(input);
1162        assert!(result.is_err());
1163        if let Err(e) = result {
1164            let msg = format!("{:?}", e);
1165            assert!(msg.contains("unclosed parentheses") || msg.contains("parenthesis"));
1166        }
1167    }
1168}