rust_rule_engine/backward/
grl_query.rs1use crate::errors::RuleEngineError;
19use crate::{Facts, Value};
20use super::backward_engine::{BackwardEngine, BackwardConfig};
21use super::search::SearchStrategy;
22use super::query::{QueryResult, QueryStats, ProofTrace};
23
24use std::collections::HashMap;
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum GRLSearchStrategy {
29 DepthFirst,
30 BreadthFirst,
31 Iterative,
32}
33
34impl Default for GRLSearchStrategy {
35 fn default() -> Self {
36 GRLSearchStrategy::DepthFirst
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct QueryAction {
43 pub assignments: Vec<(String, String)>,
45 pub calls: Vec<String>,
47}
48
49impl QueryAction {
50 pub fn new() -> Self {
51 QueryAction {
52 assignments: Vec::new(),
53 calls: Vec::new(),
54 }
55 }
56
57 pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
59 for (var_name, value_str) in &self.assignments {
61 let value = if value_str == "true" {
63 Value::Boolean(true)
64 } else if value_str == "false" {
65 Value::Boolean(false)
66 } else if let Ok(n) = value_str.parse::<f64>() {
67 Value::Number(n)
68 } else {
69 let cleaned = value_str.trim_matches('"');
71 Value::String(cleaned.to_string())
72 };
73
74 facts.set(var_name, value);
75 }
76
77 for call in &self.calls {
79 if call.starts_with("LogMessage") {
81 println!("[Query Action] {}", call);
82 } else if call.starts_with("Request") {
83 println!("[Query Action] {}", call);
84 }
85 }
86
87 Ok(())
88 }
89}
90
91#[derive(Debug, Clone)]
93pub struct GRLQuery {
94 pub name: String,
96
97 pub goal: String,
99
100 pub strategy: GRLSearchStrategy,
102
103 pub max_depth: usize,
105
106 pub max_solutions: usize,
108
109 pub enable_memoization: bool,
111
112 pub on_success: Option<QueryAction>,
114
115 pub on_failure: Option<QueryAction>,
117
118 pub on_missing: Option<QueryAction>,
120
121 pub params: HashMap<String, String>, pub when_condition: Option<String>,
126}
127
128impl GRLQuery {
129 pub fn new(name: String, goal: String) -> Self {
131 GRLQuery {
132 name,
133 goal,
134 strategy: GRLSearchStrategy::default(),
135 max_depth: 10,
136 max_solutions: 1,
137 enable_memoization: true,
138 on_success: None,
139 on_failure: None,
140 on_missing: None,
141 params: HashMap::new(),
142 when_condition: None,
143 }
144 }
145
146 pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
148 self.strategy = strategy;
149 self
150 }
151
152 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
154 self.max_depth = max_depth;
155 self
156 }
157
158 pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
160 self.max_solutions = max_solutions;
161 self
162 }
163
164 pub fn with_memoization(mut self, enable: bool) -> Self {
166 self.enable_memoization = enable;
167 self
168 }
169
170 pub fn with_on_success(mut self, action: QueryAction) -> Self {
172 self.on_success = Some(action);
173 self
174 }
175
176 pub fn with_on_failure(mut self, action: QueryAction) -> Self {
178 self.on_failure = Some(action);
179 self
180 }
181
182 pub fn with_on_missing(mut self, action: QueryAction) -> Self {
184 self.on_missing = Some(action);
185 self
186 }
187
188 pub fn with_param(mut self, name: String, type_name: String) -> Self {
190 self.params.insert(name, type_name);
191 self
192 }
193
194 pub fn with_when(mut self, condition: String) -> Self {
196 self.when_condition = Some(condition);
197 self
198 }
199
200 pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
202 Ok(true)
205 }
206
207 pub fn execute_success_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
209 if let Some(ref action) = self.on_success {
210 action.execute(facts)?;
211 }
212 Ok(())
213 }
214
215 pub fn execute_failure_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
217 if let Some(ref action) = self.on_failure {
218 action.execute(facts)?;
219 }
220 Ok(())
221 }
222
223 pub fn execute_missing_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
225 if let Some(ref action) = self.on_missing {
226 action.execute(facts)?;
227 }
228 Ok(())
229 }
230
231 pub fn to_config(&self) -> BackwardConfig {
233 let search_strategy = match self.strategy {
234 GRLSearchStrategy::DepthFirst => SearchStrategy::DepthFirst,
235 GRLSearchStrategy::BreadthFirst => SearchStrategy::BreadthFirst,
236 GRLSearchStrategy::Iterative => SearchStrategy::DepthFirst, };
238
239 BackwardConfig {
240 strategy: search_strategy,
241 max_depth: self.max_depth,
242 enable_memoization: self.enable_memoization,
243 max_solutions: self.max_solutions,
244 }
245 }
246}
247
248pub struct GRLQueryParser;
250
251impl GRLQueryParser {
252 pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
265 let input = input.trim();
266
267 let name = Self::extract_query_name(input)?;
269
270 let goal = Self::extract_goal(input)?;
272
273 let mut query = GRLQuery::new(name, goal);
275
276 if let Some(strategy) = Self::extract_strategy(input) {
278 query.strategy = strategy;
279 }
280
281 if let Some(max_depth) = Self::extract_max_depth(input) {
282 query.max_depth = max_depth;
283 }
284
285 if let Some(max_solutions) = Self::extract_max_solutions(input) {
286 query.max_solutions = max_solutions;
287 }
288
289 if let Some(enable_memo) = Self::extract_memoization(input) {
290 query.enable_memoization = enable_memo;
291 }
292
293 if let Some(action) = Self::extract_on_success(input)? {
295 query.on_success = Some(action);
296 }
297
298 if let Some(action) = Self::extract_on_failure(input)? {
299 query.on_failure = Some(action);
300 }
301
302 if let Some(action) = Self::extract_on_missing(input)? {
303 query.on_missing = Some(action);
304 }
305
306 if let Some(condition) = Self::extract_when_condition(input)? {
308 query.when_condition = Some(condition);
309 }
310
311 Ok(query)
312 }
313
314 fn extract_query_name(input: &str) -> Result<String, RuleEngineError> {
315 let re = regex::Regex::new(r#"query\s+"([^"]+)"\s*\{"#).unwrap();
316 if let Some(caps) = re.captures(input) {
317 Ok(caps[1].to_string())
318 } else {
319 Err(RuleEngineError::ParseError {
320 message: "Invalid query syntax: missing query name".to_string(),
321 })
322 }
323 }
324
325 fn extract_goal(input: &str) -> Result<String, RuleEngineError> {
326 let re = regex::Regex::new(r"goal:\s*([^\n}]+)").unwrap();
327 if let Some(caps) = re.captures(input) {
328 let goal_str = caps[1].trim().to_string();
329 Ok(goal_str)
330 } else {
331 Err(RuleEngineError::ParseError {
332 message: "Invalid query syntax: missing goal".to_string(),
333 })
334 }
335 }
336
337 fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
338 let re = regex::Regex::new(r"strategy:\s*([a-z-]+)").unwrap();
339 re.captures(input).and_then(|caps| {
340 match caps[1].trim() {
341 "depth-first" => Some(GRLSearchStrategy::DepthFirst),
342 "breadth-first" => Some(GRLSearchStrategy::BreadthFirst),
343 "iterative" => Some(GRLSearchStrategy::Iterative),
344 _ => None,
345 }
346 })
347 }
348
349 fn extract_max_depth(input: &str) -> Option<usize> {
350 let re = regex::Regex::new(r"max-depth:\s*(\d+)").unwrap();
351 re.captures(input)
352 .and_then(|caps| caps[1].parse().ok())
353 }
354
355 fn extract_max_solutions(input: &str) -> Option<usize> {
356 let re = regex::Regex::new(r"max-solutions:\s*(\d+)").unwrap();
357 re.captures(input)
358 .and_then(|caps| caps[1].parse().ok())
359 }
360
361 fn extract_memoization(input: &str) -> Option<bool> {
362 let re = regex::Regex::new(r"enable-memoization:\s*(true|false)").unwrap();
363 re.captures(input).and_then(|caps| {
364 match caps[1].trim() {
365 "true" => Some(true),
366 "false" => Some(false),
367 _ => None,
368 }
369 })
370 }
371
372 fn extract_on_success(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
373 Self::extract_action_block(input, "on-success")
374 }
375
376 fn extract_on_failure(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
377 Self::extract_action_block(input, "on-failure")
378 }
379
380 fn extract_on_missing(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
381 Self::extract_action_block(input, "on-missing")
382 }
383
384 fn extract_action_block(input: &str, action_name: &str) -> Result<Option<QueryAction>, RuleEngineError> {
385 let pattern = format!(r"{}:\s*\{{([^}}]+)\}}", action_name);
386 let re = regex::Regex::new(&pattern).unwrap();
387
388 if let Some(caps) = re.captures(input) {
389 let block = caps[1].trim();
390 let mut action = QueryAction::new();
391
392 let assign_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*([^;]+);").unwrap();
394 for caps in assign_re.captures_iter(block) {
395 let var_name = caps[1].trim().to_string();
396 let value_str = caps[2].trim().to_string();
397 action.assignments.push((var_name, value_str));
398 }
399
400 let call_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_]*\([^)]*\));").unwrap();
402 for caps in call_re.captures_iter(block) {
403 action.calls.push(caps[1].trim().to_string());
404 }
405
406 Ok(Some(action))
407 } else {
408 Ok(None)
409 }
410 }
411
412 fn extract_when_condition(input: &str) -> Result<Option<String>, RuleEngineError> {
413 let re = regex::Regex::new(r"when:\s*([^\n}]+)").unwrap();
414 if let Some(caps) = re.captures(input) {
415 let condition_str = caps[1].trim().to_string();
416 Ok(Some(condition_str))
417 } else {
418 Ok(None)
419 }
420 }
421
422 pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
424 let mut queries = Vec::new();
425
426 let parts: Vec<&str> = input.split("query").collect();
429
430 for part in parts.iter().skip(1) { let query_str = format!("query{}", part);
432 if let Some(end_idx) = find_matching_brace(&query_str) {
434 let complete_query = &query_str[..end_idx];
435 if let Ok(query) = Self::parse(complete_query) {
436 queries.push(query);
437 }
438 }
439 }
440
441 Ok(queries)
442 }
443}
444
445fn find_matching_brace(input: &str) -> Option<usize> {
447 let mut depth = 0;
448 let mut in_string = false;
449 let mut escape_next = false;
450
451 for (i, ch) in input.chars().enumerate() {
452 if escape_next {
453 escape_next = false;
454 continue;
455 }
456
457 match ch {
458 '\\' => escape_next = true,
459 '"' => in_string = !in_string,
460 '{' if !in_string => depth += 1,
461 '}' if !in_string => {
462 depth -= 1;
463 if depth == 0 {
464 return Some(i + 1);
465 }
466 }
467 _ => {}
468 }
469 }
470
471 None
472}
473
474pub struct GRLQueryExecutor;
476
477impl GRLQueryExecutor {
478 pub fn execute(
480 query: &GRLQuery,
481 bc_engine: &mut BackwardEngine,
482 facts: &mut Facts,
483 ) -> Result<QueryResult, RuleEngineError> {
484 if !query.should_execute(facts)? {
486 return Ok(QueryResult {
487 provable: false,
488 bindings: HashMap::new(),
489 proof_trace: ProofTrace { goal: String::new(), steps: Vec::new() },
490 missing_facts: Vec::new(),
491 stats: QueryStats::default(),
492 });
493 }
494
495 bc_engine.set_config(query.to_config());
497
498 let result = if query.goal.contains("&&") {
500 Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
502 } else {
503 bc_engine.query(&query.goal, facts)?
505 };
506
507 if result.provable {
509 query.execute_success_actions(facts)?;
510 } else if !result.missing_facts.is_empty() {
511 query.execute_missing_actions(facts)?;
512 } else {
513 query.execute_failure_actions(facts)?;
514 }
515
516 Ok(result)
517 }
518
519 fn execute_compound_and_goal(
521 goal_expr: &str,
522 bc_engine: &mut BackwardEngine,
523 facts: &mut Facts,
524 ) -> Result<QueryResult, RuleEngineError> {
525 let sub_goals: Vec<&str> = goal_expr.split("&&").map(|s| s.trim()).collect();
526
527 let mut all_provable = true;
528 let mut combined_bindings = HashMap::new();
529 let mut all_missing = Vec::new();
530 let mut combined_stats = QueryStats::default();
531
532 for (i, sub_goal) in sub_goals.iter().enumerate() {
533 let goal_satisfied = if sub_goal.contains("!=") {
535 use crate::backward::expression::ExpressionParser;
537
538 match ExpressionParser::parse(sub_goal) {
539 Ok(expr) => expr.is_satisfied(facts),
540 Err(_) => false,
541 }
542 } else {
543 let result = bc_engine.query(sub_goal, facts)?;
545 result.provable
546 };
547
548 if !goal_satisfied {
549 all_provable = false;
550 }
551
552 }
555
556 Ok(QueryResult {
557 provable: all_provable,
558 bindings: combined_bindings,
559 proof_trace: ProofTrace {
560 goal: goal_expr.to_string(),
561 steps: Vec::new()
562 },
563 missing_facts: all_missing,
564 stats: combined_stats,
565 })
566 }
567
568 pub fn execute_queries(
570 queries: &[GRLQuery],
571 bc_engine: &mut BackwardEngine,
572 facts: &mut Facts,
573 ) -> Result<Vec<QueryResult>, RuleEngineError> {
574 let mut results = Vec::new();
575
576 for query in queries {
577 let result = Self::execute(query, bc_engine, facts)?;
578 results.push(result);
579 }
580
581 Ok(results)
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn test_parse_simple_query() {
591 let input = r#"
592 query "TestQuery" {
593 goal: User.IsVIP == true
594 }
595 "#;
596
597 let query = GRLQueryParser::parse(input).unwrap();
598 assert_eq!(query.name, "TestQuery");
599 assert_eq!(query.strategy, GRLSearchStrategy::DepthFirst);
600 assert_eq!(query.max_depth, 10);
601 }
602
603 #[test]
604 fn test_parse_query_with_strategy() {
605 let input = r#"
606 query "TestQuery" {
607 goal: User.IsVIP == true
608 strategy: breadth-first
609 max-depth: 5
610 }
611 "#;
612
613 let query = GRLQueryParser::parse(input).unwrap();
614 assert_eq!(query.strategy, GRLSearchStrategy::BreadthFirst);
615 assert_eq!(query.max_depth, 5);
616 }
617
618 #[test]
619 fn test_parse_query_with_actions() {
620 let input = r#"
621 query "TestQuery" {
622 goal: User.IsVIP == true
623 on-success: {
624 User.DiscountRate = 0.2;
625 LogMessage("VIP confirmed");
626 }
627 }
628 "#;
629
630 let query = GRLQueryParser::parse(input).unwrap();
631 assert!(query.on_success.is_some());
632
633 let action = query.on_success.unwrap();
634 assert_eq!(action.assignments.len(), 1);
635 assert_eq!(action.calls.len(), 1);
636 }
637
638 #[test]
639 fn test_parse_query_with_when_condition() {
640 let input = r#"
641 query "TestQuery" {
642 goal: User.IsVIP == true
643 when: Environment.Mode == "Production"
644 }
645 "#;
646
647 let query = GRLQueryParser::parse(input).unwrap();
648 assert!(query.when_condition.is_some());
649 }
650
651 #[test]
652 fn test_parse_multiple_queries() {
653 let input = r#"
654 query "Query1" {
655 goal: A == true
656 }
657
658 query "Query2" {
659 goal: B == true
660 strategy: breadth-first
661 }
662 "#;
663
664 let queries = GRLQueryParser::parse_queries(input).unwrap();
665 assert_eq!(queries.len(), 2);
666 assert_eq!(queries[0].name, "Query1");
667 assert_eq!(queries[1].name, "Query2");
668 }
669
670 #[test]
671 fn test_query_config_conversion() {
672 let query = GRLQuery::new(
673 "Test".to_string(),
674 "X == true".to_string(),
675 )
676 .with_strategy(GRLSearchStrategy::BreadthFirst)
677 .with_max_depth(15)
678 .with_memoization(false);
679
680 let config = query.to_config();
681 assert_eq!(config.max_depth, 15);
682 assert_eq!(config.enable_memoization, false);
683 }
684
685 #[test]
686 fn test_action_execution() {
687 let mut facts = Facts::new();
688
689 let mut action = QueryAction::new();
690 action.assignments.push((
691 "User.DiscountRate".to_string(),
692 "0.2".to_string(),
693 ));
694
695 action.execute(&mut facts).unwrap();
696
697 let value = facts.get("User.DiscountRate");
699 assert!(value.is_some());
700 }
701}