1use 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#[derive(Debug, Clone, PartialEq, Default)]
96pub enum GRLSearchStrategy {
97 #[default]
98 DepthFirst,
99 BreadthFirst,
100 Iterative,
101}
102
103#[derive(Debug, Clone)]
105pub struct QueryAction {
106 pub assignments: Vec<(String, String)>,
108 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 pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
128 for (var_name, value_str) in &self.assignments {
130 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 let cleaned = value_str.trim_matches('"');
140 Value::String(cleaned.to_string())
141 };
142
143 facts.set(var_name, value);
144 }
145
146 for call in &self.calls {
148 self.execute_function_call(call)?;
149 }
150
151 Ok(())
152 }
153
154 fn execute_function_call(&self, call: &str) -> Result<(), RuleEngineError> {
156 let call = call.trim();
157
158 if let Some(open_paren) = call.find('(') {
160 let func_name = call[..open_paren].trim();
161
162 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 let message = args_str.trim().trim_matches('"').trim_matches('\'');
170 println!("[LOG] {}", message);
171 }
172 "Request" => {
173 let message = args_str.trim().trim_matches('"').trim_matches('\'');
175 println!("[REQUEST] {}", message);
176 }
177 "Print" => {
178 let message = args_str.trim().trim_matches('"').trim_matches('\'');
180 println!("{}", message);
181 }
182 "Debug" => {
183 let message = args_str.trim().trim_matches('"').trim_matches('\'');
185 eprintln!("[DEBUG] {}", message);
186 }
187 other => {
188 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#[derive(Debug, Clone)]
212pub struct GRLQuery {
213 pub name: String,
215
216 pub goal: String,
218
219 pub strategy: GRLSearchStrategy,
221
222 pub max_depth: usize,
224
225 pub max_solutions: usize,
227
228 pub enable_memoization: bool,
230
231 pub enable_optimization: bool,
233
234 pub on_success: Option<QueryAction>,
236
237 pub on_failure: Option<QueryAction>,
239
240 pub on_missing: Option<QueryAction>,
242
243 pub params: HashMap<String, String>, pub when_condition: Option<String>,
248}
249
250impl GRLQuery {
251 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 pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
271 self.strategy = strategy;
272 self
273 }
274
275 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
277 self.max_depth = max_depth;
278 self
279 }
280
281 pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
283 self.max_solutions = max_solutions;
284 self
285 }
286
287 pub fn with_memoization(mut self, enable: bool) -> Self {
289 self.enable_memoization = enable;
290 self
291 }
292
293 pub fn with_optimization(mut self, enable: bool) -> Self {
295 self.enable_optimization = enable;
296 self
297 }
298
299 pub fn with_on_success(mut self, action: QueryAction) -> Self {
301 self.on_success = Some(action);
302 self
303 }
304
305 pub fn with_on_failure(mut self, action: QueryAction) -> Self {
307 self.on_failure = Some(action);
308 self
309 }
310
311 pub fn with_on_missing(mut self, action: QueryAction) -> Self {
313 self.on_missing = Some(action);
314 self
315 }
316
317 pub fn with_param(mut self, name: String, type_name: String) -> Self {
319 self.params.insert(name, type_name);
320 self
321 }
322
323 pub fn with_when(mut self, condition: String) -> Self {
325 self.when_condition = Some(condition);
326 self
327 }
328
329 pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
331 if self.when_condition.is_none() {
333 return Ok(true);
334 }
335
336 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 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 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 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 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
390pub struct GRLQueryParser;
392
393impl GRLQueryParser {
394 pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
407 let input = input.trim();
408
409 let name = Self::extract_query_name(input)?;
411
412 let goal = Self::extract_goal(input)?;
414
415 let mut query = GRLQuery::new(name, goal);
417
418 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 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 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 if let Some(query_pos) = input.find("query") {
463 let after_query = &input[query_pos + 5..].trim_start();
464
465 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 if let Some(goal_start) = input.find("goal:") {
482 let after_goal = &input[goal_start + 5..]; 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 Ok(input.len())
548 }
549
550 fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
551 if let Some(pos) = input.find("strategy:") {
553 let after_strategy = input[pos + 9..].trim_start();
554
555 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 if let Some(pos) = input.find("max-depth:") {
575 let after_label = input[pos + 10..].trim_start();
576
577 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 if let Some(pos) = input.find("max-solutions:") {
592 let after_label = input[pos + 14..].trim_start();
593
594 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 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 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 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 if let Some(brace_pos) = after_label.find('{') {
663 let after_brace = &after_label[brace_pos + 1..];
664
665 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 for line in block.lines() {
698 let line = line.trim();
699 if line.is_empty() {
700 continue;
701 }
702
703 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 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 if let Some(when_pos) = input.find("when:") {
728 let after_when = &input[when_pos + 5..]; 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 pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
744 let mut queries = Vec::new();
745
746 let parts: Vec<&str> = input.split("query").collect();
749
750 for part in parts.iter().skip(1) {
751 let query_str = format!("query{}", part);
753 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
766fn 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
795pub struct GRLQueryExecutor;
797
798impl GRLQueryExecutor {
799 pub fn execute(
801 query: &GRLQuery,
802 bc_engine: &mut BackwardEngine,
803 facts: &mut Facts,
804 ) -> Result<QueryResult, RuleEngineError> {
805 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 bc_engine.set_config(query.to_config());
822
823 let result = if query.goal.contains("&&") && query.goal.contains("||") {
825 Self::execute_complex_goal(&query.goal, bc_engine, facts)?
828 } else if query.goal.contains("||") {
829 Self::execute_compound_or_goal(&query.goal, bc_engine, facts)?
831 } else if query.goal.contains("&&") {
832 Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
834 } else {
835 bc_engine.query(&query.goal, facts)?
837 };
838
839 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 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 let goal_satisfied = if sub_goal.contains("!=") {
867 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 let result = bc_engine.query(sub_goal, facts)?;
877 result.provable
878 };
879
880 if !goal_satisfied {
881 all_provable = false;
882 }
883
884 }
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 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 let (goal_satisfied, result_opt) = if sub_goal.contains("!=") {
918 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 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 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 fn strip_outer_parens(expr: &str) -> &str {
965 let trimmed = expr.trim();
966 if trimmed.starts_with('(') && trimmed.ends_with(')') {
967 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 return trimmed;
978 }
979 }
980 _ => {}
981 }
982 }
983 if depth == 0 {
984 return inner.trim();
986 }
987 }
988 trimmed
989 }
990
991 fn execute_complex_goal(
995 goal_expr: &str,
996 bc_engine: &mut BackwardEngine,
997 facts: &mut Facts,
998 ) -> Result<QueryResult, RuleEngineError> {
999 let cleaned_expr = Self::strip_outer_parens(goal_expr);
1001
1002 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 let cleaned_part = Self::strip_outer_parens(or_part);
1014
1015 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 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 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 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 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 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}