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;
6use super::unification::Bindings;
7
8/// Represents a goal to prove/achieve in backward chaining
9#[derive(Debug, Clone)]
10pub struct Goal {
11    /// Target pattern to prove (e.g., "User.IsVIP == true")
12    pub pattern: String,
13
14    /// Parsed expression AST (if available)
15    pub expression: Option<Expression>,
16
17    /// Current status of this goal
18    pub status: GoalStatus,
19
20    /// Sub-goals that need to be proven first
21    pub sub_goals: Vec<Goal>,
22
23    /// Rules that can potentially derive this goal
24    pub candidate_rules: Vec<String>,
25
26    /// Variable bindings accumulated during proof
27    pub bindings: Bindings,
28
29    /// Depth of this goal in the search tree
30    pub depth: usize,
31
32    /// Whether this is a negated goal (NOT keyword)
33    /// When true, the goal succeeds if it CANNOT be proven (closed-world assumption)
34    pub is_negated: bool,
35}
36
37/// Status of a goal in the proof process
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum GoalStatus {
40    /// Goal has not been attempted yet
41    Pending,
42    
43    /// Goal is currently being proven (to detect cycles)
44    InProgress,
45    
46    /// Goal has been successfully proven
47    Proven,
48    
49    /// Goal cannot be proven with available rules/facts
50    Unprovable,
51}
52
53impl Goal {
54    /// Create a new goal with the given pattern
55    pub fn new(pattern: String) -> Self {
56        Self {
57            pattern,
58            expression: None,
59            status: GoalStatus::Pending,
60            sub_goals: Vec::new(),
61            candidate_rules: Vec::new(),
62            bindings: Bindings::new(),
63            depth: 0,
64            is_negated: false,
65        }
66    }
67
68    /// Create a new goal with parsed expression
69    pub fn with_expression(pattern: String, expression: Expression) -> Self {
70        Self {
71            pattern,
72            expression: Some(expression),
73            status: GoalStatus::Pending,
74            sub_goals: Vec::new(),
75            candidate_rules: Vec::new(),
76            bindings: Bindings::new(),
77            depth: 0,
78            is_negated: false,
79        }
80    }
81
82    /// Create a negated goal (NOT goal)
83    pub fn negated(pattern: String) -> Self {
84        Self {
85            pattern,
86            expression: None,
87            status: GoalStatus::Pending,
88            sub_goals: Vec::new(),
89            candidate_rules: Vec::new(),
90            bindings: Bindings::new(),
91            depth: 0,
92            is_negated: true,
93        }
94    }
95
96    /// Create a negated goal with expression
97    pub fn negated_with_expression(pattern: String, expression: Expression) -> Self {
98        Self {
99            pattern,
100            expression: Some(expression),
101            status: GoalStatus::Pending,
102            sub_goals: Vec::new(),
103            candidate_rules: Vec::new(),
104            bindings: Bindings::new(),
105            depth: 0,
106            is_negated: true,
107        }
108    }
109    
110    /// Check if this goal is proven
111    pub fn is_proven(&self) -> bool {
112        self.status == GoalStatus::Proven
113    }
114    
115    /// Check if this goal is unprovable
116    pub fn is_unprovable(&self) -> bool {
117        self.status == GoalStatus::Unprovable
118    }
119    
120    /// Check if all sub-goals are proven
121    pub fn all_subgoals_proven(&self) -> bool {
122        self.sub_goals.iter().all(|g| g.is_proven())
123    }
124    
125    /// Add a sub-goal
126    pub fn add_subgoal(&mut self, goal: Goal) {
127        self.sub_goals.push(goal);
128    }
129    
130    /// Add a candidate rule that can derive this goal
131    pub fn add_candidate_rule(&mut self, rule_name: String) {
132        if !self.candidate_rules.contains(&rule_name) {
133            self.candidate_rules.push(rule_name);
134        }
135    }
136}
137
138/// Manager for goal-driven reasoning
139#[derive(Debug)]
140pub struct GoalManager {
141    /// Active goals being pursued
142    goals: Vec<Goal>,
143    
144    /// Maximum depth for goal search (prevent infinite recursion)
145    max_depth: usize,
146    
147    /// Cache of proven goals (memoization)
148    proven_cache: HashMap<String, bool>,
149}
150
151impl GoalManager {
152    /// Create a new goal manager
153    pub fn new(max_depth: usize) -> Self {
154        Self {
155            goals: Vec::new(),
156            max_depth,
157            proven_cache: HashMap::new(),
158        }
159    }
160    
161    /// Add a new goal to pursue
162    pub fn add_goal(&mut self, goal: Goal) {
163        self.goals.push(goal);
164    }
165    
166    /// Get the next pending goal to work on
167    pub fn next_pending(&mut self) -> Option<&mut Goal> {
168        self.goals.iter_mut()
169            .find(|g| g.status == GoalStatus::Pending)
170    }
171    
172    /// Check if a goal pattern has been proven before (memoization)
173    pub fn is_cached(&self, pattern: &str) -> Option<bool> {
174        self.proven_cache.get(pattern).copied()
175    }
176    
177    /// Cache the result of proving a goal
178    pub fn cache_result(&mut self, pattern: String, proven: bool) {
179        self.proven_cache.insert(pattern, proven);
180    }
181    
182    /// Check if we've exceeded maximum depth
183    pub fn is_too_deep(&self, depth: usize) -> bool {
184        depth > self.max_depth
185    }
186    
187    /// Get all goals
188    pub fn goals(&self) -> &[Goal] {
189        &self.goals
190    }
191    
192    /// Clear all goals and cache
193    pub fn clear(&mut self) {
194        self.goals.clear();
195        self.proven_cache.clear();
196    }
197}
198
199impl Default for GoalManager {
200    fn default() -> Self {
201        Self::new(10) // Default max depth of 10
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    
209    #[test]
210    fn test_goal_creation() {
211        let goal = Goal::new("User.IsVIP == true".to_string());
212        assert_eq!(goal.status, GoalStatus::Pending);
213        assert_eq!(goal.depth, 0);
214        assert!(goal.sub_goals.is_empty());
215    }
216    
217    #[test]
218    fn test_goal_status_checks() {
219        let mut goal = Goal::new("test".to_string());
220        assert!(!goal.is_proven());
221        
222        goal.status = GoalStatus::Proven;
223        assert!(goal.is_proven());
224        
225        goal.status = GoalStatus::Unprovable;
226        assert!(goal.is_unprovable());
227    }
228    
229    #[test]
230    fn test_subgoal_management() {
231        let mut parent = Goal::new("parent".to_string());
232        let mut child1 = Goal::new("child1".to_string());
233        let mut child2 = Goal::new("child2".to_string());
234        
235        child1.status = GoalStatus::Proven;
236        child2.status = GoalStatus::Proven;
237        
238        parent.add_subgoal(child1);
239        parent.add_subgoal(child2);
240        
241        assert_eq!(parent.sub_goals.len(), 2);
242        assert!(parent.all_subgoals_proven());
243    }
244    
245    #[test]
246    fn test_goal_manager() {
247        let mut manager = GoalManager::new(5);
248        
249        let goal1 = Goal::new("goal1".to_string());
250        let goal2 = Goal::new("goal2".to_string());
251        
252        manager.add_goal(goal1);
253        manager.add_goal(goal2);
254        
255        assert_eq!(manager.goals().len(), 2);
256        
257        // Test caching
258        assert!(manager.is_cached("goal1").is_none());
259        manager.cache_result("goal1".to_string(), true);
260        assert_eq!(manager.is_cached("goal1"), Some(true));
261        
262        // Test depth check
263        assert!(!manager.is_too_deep(3));
264        assert!(manager.is_too_deep(10));
265    }
266    
267    #[test]
268    fn test_candidate_rules() {
269        let mut goal = Goal::new("test".to_string());
270
271        goal.add_candidate_rule("Rule1".to_string());
272        goal.add_candidate_rule("Rule2".to_string());
273        goal.add_candidate_rule("Rule1".to_string()); // Duplicate
274
275        assert_eq!(goal.candidate_rules.len(), 2);
276    }
277
278    #[test]
279    fn test_goal_with_expression() {
280        use super::super::expression::Expression;
281        use crate::types::{Operator, Value};
282
283        let expr = Expression::Comparison {
284            left: Box::new(Expression::Field("User.IsVIP".to_string())),
285            operator: Operator::Equal,
286            right: Box::new(Expression::Literal(Value::Boolean(true))),
287        };
288
289        let goal = Goal::with_expression("User.IsVIP == true".to_string(), expr);
290        assert!(goal.expression.is_some());
291        assert_eq!(goal.pattern, "User.IsVIP == true");
292        assert_eq!(goal.status, GoalStatus::Pending);
293    }
294
295    #[test]
296    fn test_subgoals_not_all_proven() {
297        let mut parent = Goal::new("parent".to_string());
298        let mut child1 = Goal::new("child1".to_string());
299        let mut child2 = Goal::new("child2".to_string());
300
301        child1.status = GoalStatus::Proven;
302        child2.status = GoalStatus::Pending; // Not proven yet
303
304        parent.add_subgoal(child1);
305        parent.add_subgoal(child2);
306
307        assert_eq!(parent.sub_goals.len(), 2);
308        assert!(!parent.all_subgoals_proven());
309    }
310
311    #[test]
312    fn test_goal_manager_next_pending() {
313        let mut manager = GoalManager::new(5);
314
315        let mut goal1 = Goal::new("goal1".to_string());
316        goal1.status = GoalStatus::Proven; // Already proven
317
318        let goal2 = Goal::new("goal2".to_string()); // Pending
319
320        let mut goal3 = Goal::new("goal3".to_string());
321        goal3.status = GoalStatus::InProgress;
322
323        manager.add_goal(goal1);
324        manager.add_goal(goal2);
325        manager.add_goal(goal3);
326
327        // Should return goal2 as it's the only pending one
328        let next = manager.next_pending();
329        assert!(next.is_some());
330        assert_eq!(next.unwrap().pattern, "goal2");
331    }
332
333    #[test]
334    fn test_goal_manager_clear() {
335        let mut manager = GoalManager::new(5);
336
337        manager.add_goal(Goal::new("goal1".to_string()));
338        manager.add_goal(Goal::new("goal2".to_string()));
339        manager.cache_result("goal1".to_string(), true);
340
341        assert_eq!(manager.goals().len(), 2);
342        assert!(manager.is_cached("goal1").is_some());
343
344        manager.clear();
345
346        assert_eq!(manager.goals().len(), 0);
347        assert!(manager.is_cached("goal1").is_none());
348    }
349
350    #[test]
351    fn test_goal_bindings() {
352        let mut goal = Goal::new("User.?X == true".to_string());
353
354        // Test that bindings start empty
355        assert!(goal.bindings.get("X").is_none());
356
357        // Add a binding
358        goal.bindings.bind("X".to_string(), Value::String("IsVIP".to_string())).ok();
359
360        // Verify binding was added
361        assert!(goal.bindings.get("X").is_some());
362        assert_eq!(goal.bindings.get("X"), Some(&Value::String("IsVIP".to_string())));
363    }
364
365    #[test]
366    fn test_goal_depth() {
367        let mut goal = Goal::new("test".to_string());
368        assert_eq!(goal.depth, 0);
369
370        goal.depth = 5;
371        assert_eq!(goal.depth, 5);
372    }
373
374    #[test]
375    fn test_goal_manager_default() {
376        let manager = GoalManager::default();
377        assert_eq!(manager.max_depth, 10);
378        assert_eq!(manager.goals().len(), 0);
379    }
380
381    #[test]
382    fn test_negated_goal() {
383        let goal = Goal::negated("User.IsBanned == true".to_string());
384        assert_eq!(goal.pattern, "User.IsBanned == true");
385        assert!(goal.is_negated);
386        assert_eq!(goal.status, GoalStatus::Pending);
387    }
388
389    #[test]
390    fn test_negated_goal_with_expression() {
391        use super::super::expression::Expression;
392        use crate::types::{Operator, Value};
393
394        let expr = Expression::Comparison {
395            left: Box::new(Expression::Field("User.IsBanned".to_string())),
396            operator: Operator::Equal,
397            right: Box::new(Expression::Literal(Value::Boolean(true))),
398        };
399
400        let goal = Goal::negated_with_expression("User.IsBanned == true".to_string(), expr);
401        assert!(goal.expression.is_some());
402        assert!(goal.is_negated);
403        assert_eq!(goal.pattern, "User.IsBanned == true");
404    }
405
406    #[test]
407    fn test_normal_goal_not_negated() {
408        let goal = Goal::new("test".to_string());
409        assert!(!goal.is_negated);
410
411        let expr = Expression::Field("test".to_string());
412        let goal_with_expr = Goal::with_expression("test".to_string(), expr);
413        assert!(!goal_with_expr.is_negated);
414    }
415}