1use 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#[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#[derive(Debug, Clone)]
110pub struct QueryAction {
111 pub assignments: Vec<(String, String)>,
113 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 pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
127 for (var_name, value_str) in &self.assignments {
129 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 let cleaned = value_str.trim_matches('"');
139 Value::String(cleaned.to_string())
140 };
141
142 facts.set(var_name, value);
143 }
144
145 for call in &self.calls {
147 self.execute_function_call(call)?;
148 }
149
150 Ok(())
151 }
152
153 fn execute_function_call(&self, call: &str) -> Result<(), RuleEngineError> {
155 let call = call.trim();
156
157 if let Some(open_paren) = call.find('(') {
159 let func_name = call[..open_paren].trim();
160
161 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 let message = args_str.trim().trim_matches('"').trim_matches('\'');
169 println!("[LOG] {}", message);
170 }
171 "Request" => {
172 let message = args_str.trim().trim_matches('"').trim_matches('\'');
174 println!("[REQUEST] {}", message);
175 }
176 "Print" => {
177 let message = args_str.trim().trim_matches('"').trim_matches('\'');
179 println!("{}", message);
180 }
181 "Debug" => {
182 let message = args_str.trim().trim_matches('"').trim_matches('\'');
184 eprintln!("[DEBUG] {}", message);
185 }
186 other => {
187 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#[derive(Debug, Clone)]
208pub struct GRLQuery {
209 pub name: String,
211
212 pub goal: String,
214
215 pub strategy: GRLSearchStrategy,
217
218 pub max_depth: usize,
220
221 pub max_solutions: usize,
223
224 pub enable_memoization: bool,
226
227 pub on_success: Option<QueryAction>,
229
230 pub on_failure: Option<QueryAction>,
232
233 pub on_missing: Option<QueryAction>,
235
236 pub params: HashMap<String, String>, pub when_condition: Option<String>,
241}
242
243impl GRLQuery {
244 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 pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
263 self.strategy = strategy;
264 self
265 }
266
267 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
269 self.max_depth = max_depth;
270 self
271 }
272
273 pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
275 self.max_solutions = max_solutions;
276 self
277 }
278
279 pub fn with_memoization(mut self, enable: bool) -> Self {
281 self.enable_memoization = enable;
282 self
283 }
284
285 pub fn with_on_success(mut self, action: QueryAction) -> Self {
287 self.on_success = Some(action);
288 self
289 }
290
291 pub fn with_on_failure(mut self, action: QueryAction) -> Self {
293 self.on_failure = Some(action);
294 self
295 }
296
297 pub fn with_on_missing(mut self, action: QueryAction) -> Self {
299 self.on_missing = Some(action);
300 self
301 }
302
303 pub fn with_param(mut self, name: String, type_name: String) -> Self {
305 self.params.insert(name, type_name);
306 self
307 }
308
309 pub fn with_when(mut self, condition: String) -> Self {
311 self.when_condition = Some(condition);
312 self
313 }
314
315 pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
317 if self.when_condition.is_none() {
319 return Ok(true);
320 }
321
322 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 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 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 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 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
376pub struct GRLQueryParser;
378
379impl GRLQueryParser {
380 pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
393 let input = input.trim();
394
395 let name = Self::extract_query_name(input)?;
397
398 let goal = Self::extract_goal(input)?;
400
401 let mut query = GRLQuery::new(name, goal);
403
404 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 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 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 let re = regex::Regex::new(r"goal:\s*([^\n}]+)").unwrap();
455 if let Some(caps) = re.captures(input) {
456 let goal_str = caps[1].trim().to_string();
457 Ok(goal_str)
458 } else {
459 Err(RuleEngineError::ParseError {
460 message: "Invalid query syntax: missing goal".to_string(),
461 })
462 }
463 }
464
465 fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
466 let re = regex::Regex::new(r"strategy:\s*([a-z-]+)").unwrap();
467 re.captures(input).and_then(|caps| {
468 match caps[1].trim() {
469 "depth-first" => Some(GRLSearchStrategy::DepthFirst),
470 "breadth-first" => Some(GRLSearchStrategy::BreadthFirst),
471 "iterative" => Some(GRLSearchStrategy::Iterative),
472 _ => None,
473 }
474 })
475 }
476
477 fn extract_max_depth(input: &str) -> Option<usize> {
478 let re = regex::Regex::new(r"max-depth:\s*(\d+)").unwrap();
479 re.captures(input)
480 .and_then(|caps| caps[1].parse().ok())
481 }
482
483 fn extract_max_solutions(input: &str) -> Option<usize> {
484 let re = regex::Regex::new(r"max-solutions:\s*(\d+)").unwrap();
485 re.captures(input)
486 .and_then(|caps| caps[1].parse().ok())
487 }
488
489 fn extract_memoization(input: &str) -> Option<bool> {
490 let re = regex::Regex::new(r"enable-memoization:\s*(true|false)").unwrap();
491 re.captures(input).and_then(|caps| {
492 match caps[1].trim() {
493 "true" => Some(true),
494 "false" => Some(false),
495 _ => None,
496 }
497 })
498 }
499
500 fn extract_on_success(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
501 Self::extract_action_block(input, "on-success")
502 }
503
504 fn extract_on_failure(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
505 Self::extract_action_block(input, "on-failure")
506 }
507
508 fn extract_on_missing(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
509 Self::extract_action_block(input, "on-missing")
510 }
511
512 fn extract_action_block(input: &str, action_name: &str) -> Result<Option<QueryAction>, RuleEngineError> {
513 let pattern = format!(r"{}:\s*\{{([^}}]+)\}}", action_name);
514 let re = regex::Regex::new(&pattern).unwrap();
515
516 if let Some(caps) = re.captures(input) {
517 let block = caps[1].trim();
518 let mut action = QueryAction::new();
519
520 let assign_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*([^;]+);").unwrap();
522 for caps in assign_re.captures_iter(block) {
523 let var_name = caps[1].trim().to_string();
524 let value_str = caps[2].trim().to_string();
525 action.assignments.push((var_name, value_str));
526 }
527
528 let call_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_]*\([^)]*\));").unwrap();
530 for caps in call_re.captures_iter(block) {
531 action.calls.push(caps[1].trim().to_string());
532 }
533
534 Ok(Some(action))
535 } else {
536 Ok(None)
537 }
538 }
539
540 fn extract_when_condition(input: &str) -> Result<Option<String>, RuleEngineError> {
541 let re = regex::Regex::new(r"when:\s*([^\n}]+)").unwrap();
542 if let Some(caps) = re.captures(input) {
543 let condition_str = caps[1].trim().to_string();
544 Ok(Some(condition_str))
545 } else {
546 Ok(None)
547 }
548 }
549
550 pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
552 let mut queries = Vec::new();
553
554 let parts: Vec<&str> = input.split("query").collect();
557
558 for part in parts.iter().skip(1) { let query_str = format!("query{}", part);
560 if let Some(end_idx) = find_matching_brace(&query_str) {
562 let complete_query = &query_str[..end_idx];
563 if let Ok(query) = Self::parse(complete_query) {
564 queries.push(query);
565 }
566 }
567 }
568
569 Ok(queries)
570 }
571}
572
573fn find_matching_brace(input: &str) -> Option<usize> {
575 let mut depth = 0;
576 let mut in_string = false;
577 let mut escape_next = false;
578
579 for (i, ch) in input.chars().enumerate() {
580 if escape_next {
581 escape_next = false;
582 continue;
583 }
584
585 match ch {
586 '\\' => escape_next = true,
587 '"' => in_string = !in_string,
588 '{' if !in_string => depth += 1,
589 '}' if !in_string => {
590 depth -= 1;
591 if depth == 0 {
592 return Some(i + 1);
593 }
594 }
595 _ => {}
596 }
597 }
598
599 None
600}
601
602pub struct GRLQueryExecutor;
604
605impl GRLQueryExecutor {
606 pub fn execute(
608 query: &GRLQuery,
609 bc_engine: &mut BackwardEngine,
610 facts: &mut Facts,
611 ) -> Result<QueryResult, RuleEngineError> {
612 if !query.should_execute(facts)? {
614 return Ok(QueryResult {
615 provable: false,
616 bindings: HashMap::new(),
617 proof_trace: ProofTrace { goal: String::new(), steps: Vec::new() },
618 missing_facts: Vec::new(),
619 stats: QueryStats::default(),
620 solutions: Vec::new(),
621 });
622 }
623
624 bc_engine.set_config(query.to_config());
626
627 let result = if query.goal.contains("&&") {
629 Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
631 } else {
632 bc_engine.query(&query.goal, facts)?
634 };
635
636 if result.provable {
638 query.execute_success_actions(facts)?;
639 } else if !result.missing_facts.is_empty() {
640 query.execute_missing_actions(facts)?;
641 } else {
642 query.execute_failure_actions(facts)?;
643 }
644
645 Ok(result)
646 }
647
648 fn execute_compound_and_goal(
650 goal_expr: &str,
651 bc_engine: &mut BackwardEngine,
652 facts: &mut Facts,
653 ) -> Result<QueryResult, RuleEngineError> {
654 let sub_goals: Vec<&str> = goal_expr.split("&&").map(|s| s.trim()).collect();
655
656 let mut all_provable = true;
657 let mut combined_bindings = HashMap::new();
658 let mut all_missing = Vec::new();
659 let mut combined_stats = QueryStats::default();
660
661 for (i, sub_goal) in sub_goals.iter().enumerate() {
662 let goal_satisfied = if sub_goal.contains("!=") {
664 use crate::backward::expression::ExpressionParser;
666
667 match ExpressionParser::parse(sub_goal) {
668 Ok(expr) => expr.is_satisfied(facts),
669 Err(_) => false,
670 }
671 } else {
672 let result = bc_engine.query(sub_goal, facts)?;
674 result.provable
675 };
676
677 if !goal_satisfied {
678 all_provable = false;
679 }
680
681 }
684
685 Ok(QueryResult {
686 provable: all_provable,
687 bindings: combined_bindings,
688 proof_trace: ProofTrace {
689 goal: goal_expr.to_string(),
690 steps: Vec::new()
691 },
692 missing_facts: all_missing,
693 stats: combined_stats,
694 solutions: Vec::new(),
695 })
696 }
697
698 pub fn execute_queries(
700 queries: &[GRLQuery],
701 bc_engine: &mut BackwardEngine,
702 facts: &mut Facts,
703 ) -> Result<Vec<QueryResult>, RuleEngineError> {
704 let mut results = Vec::new();
705
706 for query in queries {
707 let result = Self::execute(query, bc_engine, facts)?;
708 results.push(result);
709 }
710
711 Ok(results)
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 #[test]
720 fn test_parse_simple_query() {
721 let input = r#"
722 query "TestQuery" {
723 goal: User.IsVIP == true
724 }
725 "#;
726
727 let query = GRLQueryParser::parse(input).unwrap();
728 assert_eq!(query.name, "TestQuery");
729 assert_eq!(query.strategy, GRLSearchStrategy::DepthFirst);
730 assert_eq!(query.max_depth, 10);
731 }
732
733 #[test]
734 fn test_parse_query_with_strategy() {
735 let input = r#"
736 query "TestQuery" {
737 goal: User.IsVIP == true
738 strategy: breadth-first
739 max-depth: 5
740 }
741 "#;
742
743 let query = GRLQueryParser::parse(input).unwrap();
744 assert_eq!(query.strategy, GRLSearchStrategy::BreadthFirst);
745 assert_eq!(query.max_depth, 5);
746 }
747
748 #[test]
749 fn test_parse_query_with_actions() {
750 let input = r#"
751 query "TestQuery" {
752 goal: User.IsVIP == true
753 on-success: {
754 User.DiscountRate = 0.2;
755 LogMessage("VIP confirmed");
756 }
757 }
758 "#;
759
760 let query = GRLQueryParser::parse(input).unwrap();
761 assert!(query.on_success.is_some());
762
763 let action = query.on_success.unwrap();
764 assert_eq!(action.assignments.len(), 1);
765 assert_eq!(action.calls.len(), 1);
766 }
767
768 #[test]
769 fn test_parse_query_with_when_condition() {
770 let input = r#"
771 query "TestQuery" {
772 goal: User.IsVIP == true
773 when: Environment.Mode == "Production"
774 }
775 "#;
776
777 let query = GRLQueryParser::parse(input).unwrap();
778 assert!(query.when_condition.is_some());
779 }
780
781 #[test]
782 fn test_parse_multiple_queries() {
783 let input = r#"
784 query "Query1" {
785 goal: A == true
786 }
787
788 query "Query2" {
789 goal: B == true
790 strategy: breadth-first
791 }
792 "#;
793
794 let queries = GRLQueryParser::parse_queries(input).unwrap();
795 assert_eq!(queries.len(), 2);
796 assert_eq!(queries[0].name, "Query1");
797 assert_eq!(queries[1].name, "Query2");
798 }
799
800 #[test]
801 fn test_query_config_conversion() {
802 let query = GRLQuery::new(
803 "Test".to_string(),
804 "X == true".to_string(),
805 )
806 .with_strategy(GRLSearchStrategy::BreadthFirst)
807 .with_max_depth(15)
808 .with_memoization(false);
809
810 let config = query.to_config();
811 assert_eq!(config.max_depth, 15);
812 assert_eq!(config.enable_memoization, false);
813 }
814
815 #[test]
816 fn test_action_execution() {
817 let mut facts = Facts::new();
818
819 let mut action = QueryAction::new();
820 action.assignments.push((
821 "User.DiscountRate".to_string(),
822 "0.2".to_string(),
823 ));
824
825 action.execute(&mut facts).unwrap();
826
827 let value = facts.get("User.DiscountRate");
829 assert!(value.is_some());
830 }
831
832 #[test]
833 fn test_should_execute_no_condition() {
834 let query = GRLQuery::new("Q".to_string(), "X == true".to_string());
835 let facts = Facts::new();
836 let res = query.should_execute(&facts).unwrap();
838 assert!(res);
839 }
840
841 #[test]
842 fn test_should_execute_condition_true() {
843 let mut facts = Facts::new();
844 facts.set("Environment.Mode", Value::String("Production".to_string()));
845
846 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
847 .with_when("Environment.Mode == \"Production\"".to_string());
848
849 let res = query.should_execute(&facts).unwrap();
850 assert!(res, "expected when condition to be satisfied");
851 }
852
853 #[test]
854 fn test_should_execute_condition_false() {
855 let mut facts = Facts::new();
856 facts.set("Environment.Mode", Value::String("Development".to_string()));
857
858 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
859 .with_when("Environment.Mode == \"Production\"".to_string());
860
861 let res = query.should_execute(&facts).unwrap();
862 assert!(!res, "expected when condition to be unsatisfied");
863 }
864
865 #[test]
866 fn test_should_execute_parse_error_propagates() {
867 let facts = Facts::new();
868 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
870 .with_when("Environment.Mode == \"Production".to_string());
871
872 let res = query.should_execute(&facts);
873 assert!(res.is_err(), "expected parse error to propagate");
874 }
875}