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