rust_rule_engine/backward/
goal.rs

1//! Goal system for backward chaining
2
3use crate::types::Value;
4use std::collections::HashMap;
5use super::expression::Expression;
6
7/// Represents a goal to prove/achieve in backward chaining
8#[derive(Debug, Clone)]
9pub struct Goal {
10    /// Target pattern to prove (e.g., "User.IsVIP == true")
11    pub pattern: String,
12
13    /// Parsed expression AST (if available)
14    pub expression: Option<Expression>,
15
16    /// Current status of this goal
17    pub status: GoalStatus,
18
19    /// Sub-goals that need to be proven first
20    pub sub_goals: Vec<Goal>,
21
22    /// Rules that can potentially derive this goal
23    pub candidate_rules: Vec<String>,
24
25    /// Variable bindings accumulated during proof
26    pub bindings: HashMap<String, Value>,
27
28    /// Depth of this goal in the search tree
29    pub depth: usize,
30}
31
32/// Status of a goal in the proof process
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum GoalStatus {
35    /// Goal has not been attempted yet
36    Pending,
37    
38    /// Goal is currently being proven (to detect cycles)
39    InProgress,
40    
41    /// Goal has been successfully proven
42    Proven,
43    
44    /// Goal cannot be proven with available rules/facts
45    Unprovable,
46}
47
48impl Goal {
49    /// Create a new goal with the given pattern
50    pub fn new(pattern: String) -> Self {
51        Self {
52            pattern,
53            expression: None,
54            status: GoalStatus::Pending,
55            sub_goals: Vec::new(),
56            candidate_rules: Vec::new(),
57            bindings: HashMap::new(),
58            depth: 0,
59        }
60    }
61
62    /// Create a new goal with parsed expression
63    pub fn with_expression(pattern: String, expression: Expression) -> Self {
64        Self {
65            pattern,
66            expression: Some(expression),
67            status: GoalStatus::Pending,
68            sub_goals: Vec::new(),
69            candidate_rules: Vec::new(),
70            bindings: HashMap::new(),
71            depth: 0,
72        }
73    }
74    
75    /// Check if this goal is proven
76    pub fn is_proven(&self) -> bool {
77        self.status == GoalStatus::Proven
78    }
79    
80    /// Check if this goal is unprovable
81    pub fn is_unprovable(&self) -> bool {
82        self.status == GoalStatus::Unprovable
83    }
84    
85    /// Check if all sub-goals are proven
86    pub fn all_subgoals_proven(&self) -> bool {
87        self.sub_goals.iter().all(|g| g.is_proven())
88    }
89    
90    /// Add a sub-goal
91    pub fn add_subgoal(&mut self, goal: Goal) {
92        self.sub_goals.push(goal);
93    }
94    
95    /// Add a candidate rule that can derive this goal
96    pub fn add_candidate_rule(&mut self, rule_name: String) {
97        if !self.candidate_rules.contains(&rule_name) {
98            self.candidate_rules.push(rule_name);
99        }
100    }
101}
102
103/// Manager for goal-driven reasoning
104#[derive(Debug)]
105pub struct GoalManager {
106    /// Active goals being pursued
107    goals: Vec<Goal>,
108    
109    /// Maximum depth for goal search (prevent infinite recursion)
110    max_depth: usize,
111    
112    /// Cache of proven goals (memoization)
113    proven_cache: HashMap<String, bool>,
114}
115
116impl GoalManager {
117    /// Create a new goal manager
118    pub fn new(max_depth: usize) -> Self {
119        Self {
120            goals: Vec::new(),
121            max_depth,
122            proven_cache: HashMap::new(),
123        }
124    }
125    
126    /// Add a new goal to pursue
127    pub fn add_goal(&mut self, goal: Goal) {
128        self.goals.push(goal);
129    }
130    
131    /// Get the next pending goal to work on
132    pub fn next_pending(&mut self) -> Option<&mut Goal> {
133        self.goals.iter_mut()
134            .find(|g| g.status == GoalStatus::Pending)
135    }
136    
137    /// Check if a goal pattern has been proven before (memoization)
138    pub fn is_cached(&self, pattern: &str) -> Option<bool> {
139        self.proven_cache.get(pattern).copied()
140    }
141    
142    /// Cache the result of proving a goal
143    pub fn cache_result(&mut self, pattern: String, proven: bool) {
144        self.proven_cache.insert(pattern, proven);
145    }
146    
147    /// Check if we've exceeded maximum depth
148    pub fn is_too_deep(&self, depth: usize) -> bool {
149        depth > self.max_depth
150    }
151    
152    /// Get all goals
153    pub fn goals(&self) -> &[Goal] {
154        &self.goals
155    }
156    
157    /// Clear all goals and cache
158    pub fn clear(&mut self) {
159        self.goals.clear();
160        self.proven_cache.clear();
161    }
162}
163
164impl Default for GoalManager {
165    fn default() -> Self {
166        Self::new(10) // Default max depth of 10
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    
174    #[test]
175    fn test_goal_creation() {
176        let goal = Goal::new("User.IsVIP == true".to_string());
177        assert_eq!(goal.status, GoalStatus::Pending);
178        assert_eq!(goal.depth, 0);
179        assert!(goal.sub_goals.is_empty());
180    }
181    
182    #[test]
183    fn test_goal_status_checks() {
184        let mut goal = Goal::new("test".to_string());
185        assert!(!goal.is_proven());
186        
187        goal.status = GoalStatus::Proven;
188        assert!(goal.is_proven());
189        
190        goal.status = GoalStatus::Unprovable;
191        assert!(goal.is_unprovable());
192    }
193    
194    #[test]
195    fn test_subgoal_management() {
196        let mut parent = Goal::new("parent".to_string());
197        let mut child1 = Goal::new("child1".to_string());
198        let mut child2 = Goal::new("child2".to_string());
199        
200        child1.status = GoalStatus::Proven;
201        child2.status = GoalStatus::Proven;
202        
203        parent.add_subgoal(child1);
204        parent.add_subgoal(child2);
205        
206        assert_eq!(parent.sub_goals.len(), 2);
207        assert!(parent.all_subgoals_proven());
208    }
209    
210    #[test]
211    fn test_goal_manager() {
212        let mut manager = GoalManager::new(5);
213        
214        let goal1 = Goal::new("goal1".to_string());
215        let goal2 = Goal::new("goal2".to_string());
216        
217        manager.add_goal(goal1);
218        manager.add_goal(goal2);
219        
220        assert_eq!(manager.goals().len(), 2);
221        
222        // Test caching
223        assert!(manager.is_cached("goal1").is_none());
224        manager.cache_result("goal1".to_string(), true);
225        assert_eq!(manager.is_cached("goal1"), Some(true));
226        
227        // Test depth check
228        assert!(!manager.is_too_deep(3));
229        assert!(manager.is_too_deep(10));
230    }
231    
232    #[test]
233    fn test_candidate_rules() {
234        let mut goal = Goal::new("test".to_string());
235        
236        goal.add_candidate_rule("Rule1".to_string());
237        goal.add_candidate_rule("Rule2".to_string());
238        goal.add_candidate_rule("Rule1".to_string()); // Duplicate
239        
240        assert_eq!(goal.candidate_rules.len(), 2);
241    }
242}