1use super::unification::Bindings;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ProofNode {
15 pub goal: String,
17
18 pub rule_name: Option<String>,
20
21 #[serde(skip_serializing_if = "HashMap::is_empty")]
23 pub bindings: HashMap<String, String>,
24
25 #[serde(skip_serializing_if = "Vec::is_empty")]
27 pub children: Vec<ProofNode>,
28
29 pub depth: usize,
31
32 pub proven: bool,
34
35 pub node_type: ProofNodeType,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub enum ProofNodeType {
42 Fact,
44
45 Rule,
47
48 Negation,
50
51 Failed,
53}
54
55impl ProofNode {
56 pub fn new(goal: String, depth: usize) -> Self {
58 ProofNode {
59 goal,
60 rule_name: None,
61 bindings: HashMap::new(),
62 children: Vec::new(),
63 depth,
64 proven: false,
65 node_type: ProofNodeType::Failed,
66 }
67 }
68
69 pub fn fact(goal: String, depth: usize) -> Self {
71 ProofNode {
72 goal,
73 rule_name: None,
74 bindings: HashMap::new(),
75 children: Vec::new(),
76 depth,
77 proven: true,
78 node_type: ProofNodeType::Fact,
79 }
80 }
81
82 pub fn rule(goal: String, rule_name: String, depth: usize) -> Self {
84 ProofNode {
85 goal,
86 rule_name: Some(rule_name),
87 bindings: HashMap::new(),
88 children: Vec::new(),
89 depth,
90 proven: true,
91 node_type: ProofNodeType::Rule,
92 }
93 }
94
95 pub fn negation(goal: String, depth: usize, proven: bool) -> Self {
97 ProofNode {
98 goal,
99 rule_name: None,
100 bindings: HashMap::new(),
101 children: Vec::new(),
102 depth,
103 proven,
104 node_type: ProofNodeType::Negation,
105 }
106 }
107
108 pub fn add_child(&mut self, child: ProofNode) {
110 self.children.push(child);
111 }
112
113 pub fn set_bindings(&mut self, bindings: &Bindings) {
115 let binding_map = bindings.to_map();
117 self.bindings = binding_map
118 .iter()
119 .map(|(k, v)| (k.clone(), format!("{:?}", v)))
120 .collect();
121 }
122
123 pub fn set_bindings_map(&mut self, bindings: HashMap<String, String>) {
125 self.bindings = bindings;
126 }
127
128 pub fn is_leaf(&self) -> bool {
130 self.children.is_empty()
131 }
132
133 pub fn print_tree(&self, indent: usize) {
135 let prefix = " ".repeat(indent);
136 let status = if self.proven { "✓" } else { "✗" };
137
138 println!("{}{} {}", prefix, status, self.goal);
139
140 if let Some(rule) = &self.rule_name {
141 println!("{} [Rule: {}]", prefix, rule);
142 }
143
144 match self.node_type {
145 ProofNodeType::Fact => println!("{} [FACT]", prefix),
146 ProofNodeType::Negation => println!("{} [NEGATION]", prefix),
147 _ => {}
148 }
149
150 if !self.bindings.is_empty() {
151 println!("{} Bindings: {:?}", prefix, self.bindings);
152 }
153
154 for child in &self.children {
155 child.print_tree(indent + 1);
156 }
157 }
158
159 pub fn height(&self) -> usize {
161 if self.children.is_empty() {
162 1
163 } else {
164 1 + self.children.iter().map(|c| c.height()).max().unwrap_or(0)
165 }
166 }
167
168 pub fn node_count(&self) -> usize {
170 1 + self.children.iter().map(|c| c.node_count()).sum::<usize>()
171 }
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct ProofTree {
177 pub root: ProofNode,
179
180 pub success: bool,
182
183 pub query: String,
185
186 pub stats: ProofStats,
188}
189
190#[derive(Debug, Clone, Default, Serialize, Deserialize)]
192pub struct ProofStats {
193 pub goals_explored: usize,
195
196 pub rules_evaluated: usize,
198
199 pub facts_checked: usize,
201
202 pub max_depth: usize,
204
205 pub total_nodes: usize,
207}
208
209impl ProofTree {
210 pub fn new(root: ProofNode, query: String) -> Self {
212 let success = root.proven;
213 let total_nodes = root.node_count();
214 let max_depth = root.height();
215
216 ProofTree {
217 root,
218 success,
219 query,
220 stats: ProofStats {
221 goals_explored: 0,
222 rules_evaluated: 0,
223 facts_checked: 0,
224 max_depth,
225 total_nodes,
226 },
227 }
228 }
229
230 pub fn set_stats(&mut self, stats: ProofStats) {
232 self.stats = stats;
233 }
234
235 pub fn print(&self) {
237 println!("Query: {}", self.query);
238 println!(
239 "Result: {}",
240 if self.success {
241 "✓ Proven"
242 } else {
243 "✗ Unprovable"
244 }
245 );
246 println!("\nProof Tree:");
247 println!("{}", "=".repeat(80));
248 self.root.print_tree(0);
249 println!("{}", "=".repeat(80));
250 self.print_stats();
251 }
252
253 pub fn print_stats(&self) {
255 println!("\nStatistics:");
256 println!(" Goals explored: {}", self.stats.goals_explored);
257 println!(" Rules evaluated: {}", self.stats.rules_evaluated);
258 println!(" Facts checked: {}", self.stats.facts_checked);
259 println!(" Max depth: {}", self.stats.max_depth);
260 println!(" Total nodes: {}", self.stats.total_nodes);
261 }
262
263 pub fn to_json(&self) -> Result<String, serde_json::Error> {
265 serde_json::to_string_pretty(self)
266 }
267
268 pub fn to_markdown(&self) -> String {
270 let mut md = String::new();
271
272 md.push_str("# Proof Explanation\n\n");
273 md.push_str(&format!("**Query:** `{}`\n\n", self.query));
274 md.push_str(&format!(
275 "**Result:** {}\n\n",
276 if self.success {
277 "✓ Proven"
278 } else {
279 "✗ Unprovable"
280 }
281 ));
282
283 md.push_str("## Proof Tree\n\n");
284 Self::node_to_markdown(&self.root, &mut md, 0);
285
286 md.push_str("\n## Statistics\n\n");
287 md.push_str(&format!(
288 "- **Goals explored:** {}\n",
289 self.stats.goals_explored
290 ));
291 md.push_str(&format!(
292 "- **Rules evaluated:** {}\n",
293 self.stats.rules_evaluated
294 ));
295 md.push_str(&format!(
296 "- **Facts checked:** {}\n",
297 self.stats.facts_checked
298 ));
299 md.push_str(&format!("- **Max depth:** {}\n", self.stats.max_depth));
300 md.push_str(&format!("- **Total nodes:** {}\n", self.stats.total_nodes));
301
302 md
303 }
304
305 fn node_to_markdown(node: &ProofNode, md: &mut String, depth: usize) {
307 let prefix = " ".repeat(depth);
308 let status = if node.proven { "✓" } else { "✗" };
309
310 md.push_str(&format!("{}* {} `{}`", prefix, status, node.goal));
311
312 if let Some(rule) = &node.rule_name {
313 md.push_str(&format!(" **[Rule: {}]**", rule));
314 }
315
316 match node.node_type {
317 ProofNodeType::Fact => md.push_str(" *[FACT]*"),
318 ProofNodeType::Negation => md.push_str(" *[NEGATION]*"),
319 _ => {}
320 }
321
322 md.push('\n');
323
324 if !node.bindings.is_empty() {
325 md.push_str(&format!("{} * Bindings: `{:?}`\n", prefix, node.bindings));
326 }
327
328 for child in &node.children {
329 Self::node_to_markdown(child, md, depth + 1);
330 }
331 }
332
333 pub fn to_html(&self) -> String {
335 let mut html = String::new();
336
337 html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
338 html.push_str(" <title>Proof Explanation</title>\n");
339 html.push_str(" <style>\n");
340 html.push_str(" body { font-family: 'Courier New', monospace; margin: 20px; }\n");
341 html.push_str(" .proven { color: green; }\n");
342 html.push_str(" .failed { color: red; }\n");
343 html.push_str(" .node { margin-left: 20px; }\n");
344 html.push_str(" .rule { color: blue; font-style: italic; }\n");
345 html.push_str(" .bindings { color: gray; font-size: 0.9em; }\n");
346 html.push_str(" .stats { margin-top: 20px; padding: 10px; background: #f0f0f0; }\n");
347 html.push_str(" </style>\n");
348 html.push_str("</head>\n<body>\n");
349
350 html.push_str("<h1>Proof Explanation</h1>\n");
351 html.push_str(&format!(
352 "<p><strong>Query:</strong> <code>{}</code></p>\n",
353 self.query
354 ));
355 html.push_str(&format!(
356 "<p><strong>Result:</strong> <span class=\"{}\">{}</span></p>\n",
357 if self.success { "proven" } else { "failed" },
358 if self.success {
359 "✓ Proven"
360 } else {
361 "✗ Unprovable"
362 }
363 ));
364
365 html.push_str("<h2>Proof Tree</h2>\n");
366 Self::node_to_html(&self.root, &mut html);
367
368 html.push_str("<div class=\"stats\">\n");
369 html.push_str("<h2>Statistics</h2>\n");
370 html.push_str(&format!(
371 "<p>Goals explored: {}</p>\n",
372 self.stats.goals_explored
373 ));
374 html.push_str(&format!(
375 "<p>Rules evaluated: {}</p>\n",
376 self.stats.rules_evaluated
377 ));
378 html.push_str(&format!(
379 "<p>Facts checked: {}</p>\n",
380 self.stats.facts_checked
381 ));
382 html.push_str(&format!("<p>Max depth: {}</p>\n", self.stats.max_depth));
383 html.push_str(&format!("<p>Total nodes: {}</p>\n", self.stats.total_nodes));
384 html.push_str("</div>\n");
385
386 html.push_str("</body>\n</html>");
387 html
388 }
389
390 fn node_to_html(node: &ProofNode, html: &mut String) {
392 let status = if node.proven { "✓" } else { "✗" };
393 let class = if node.proven { "proven" } else { "failed" };
394
395 html.push_str("<div class=\"node\">\n");
396 html.push_str(&format!(
397 " <span class=\"{}\">{} {}</span>",
398 class, status, node.goal
399 ));
400
401 if let Some(rule) = &node.rule_name {
402 html.push_str(&format!(" <span class=\"rule\">[Rule: {}]</span>", rule));
403 }
404
405 match node.node_type {
406 ProofNodeType::Fact => html.push_str(" <em>[FACT]</em>"),
407 ProofNodeType::Negation => html.push_str(" <em>[NEGATION]</em>"),
408 _ => {}
409 }
410
411 if !node.bindings.is_empty() {
412 html.push_str(&format!(
413 "<br><span class=\"bindings\">Bindings: {:?}</span>",
414 node.bindings
415 ));
416 }
417
418 html.push('\n');
419
420 for child in &node.children {
421 Self::node_to_html(child, html);
422 }
423
424 html.push_str("</div>\n");
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_proof_node_creation() {
434 let node = ProofNode::new("test_goal".to_string(), 0);
435 assert_eq!(node.goal, "test_goal");
436 assert_eq!(node.depth, 0);
437 assert!(!node.proven);
438 assert_eq!(node.node_type, ProofNodeType::Failed);
439 }
440
441 #[test]
442 fn test_fact_node() {
443 let node = ProofNode::fact("fact_goal".to_string(), 1);
444 assert!(node.proven);
445 assert_eq!(node.node_type, ProofNodeType::Fact);
446 assert!(node.is_leaf());
447 }
448
449 #[test]
450 fn test_rule_node() {
451 let node = ProofNode::rule("rule_goal".to_string(), "test_rule".to_string(), 2);
452 assert!(node.proven);
453 assert_eq!(node.node_type, ProofNodeType::Rule);
454 assert_eq!(node.rule_name, Some("test_rule".to_string()));
455 }
456
457 #[test]
458 fn test_add_child() {
459 let mut parent = ProofNode::rule("parent".to_string(), "rule1".to_string(), 0);
460 let child = ProofNode::fact("child".to_string(), 1);
461
462 parent.add_child(child);
463 assert_eq!(parent.children.len(), 1);
464 assert!(!parent.is_leaf());
465 }
466
467 #[test]
468 fn test_tree_height() {
469 let mut root = ProofNode::rule("root".to_string(), "rule1".to_string(), 0);
470 let mut child1 = ProofNode::rule("child1".to_string(), "rule2".to_string(), 1);
471 let child2 = ProofNode::fact("child2".to_string(), 2);
472
473 child1.add_child(child2);
474 root.add_child(child1);
475
476 assert_eq!(root.height(), 3);
477 }
478
479 #[test]
480 fn test_node_count() {
481 let mut root = ProofNode::rule("root".to_string(), "rule1".to_string(), 0);
482 let child1 = ProofNode::fact("child1".to_string(), 1);
483 let child2 = ProofNode::fact("child2".to_string(), 1);
484
485 root.add_child(child1);
486 root.add_child(child2);
487
488 assert_eq!(root.node_count(), 3);
489 }
490
491 #[test]
492 fn test_proof_tree_creation() {
493 let root = ProofNode::fact("test".to_string(), 0);
494 let tree = ProofTree::new(root, "test query".to_string());
495
496 assert!(tree.success);
497 assert_eq!(tree.query, "test query");
498 assert_eq!(tree.stats.total_nodes, 1);
499 }
500
501 #[test]
502 fn test_json_serialization() {
503 let root = ProofNode::fact("test".to_string(), 0);
504 let tree = ProofTree::new(root, "test query".to_string());
505
506 let json = tree.to_json().unwrap();
507 assert!(json.contains("test query"));
508 assert!(json.contains("Fact"));
509 }
510
511 #[test]
512 fn test_markdown_generation() {
513 let root = ProofNode::fact("test".to_string(), 0);
514 let tree = ProofTree::new(root, "test query".to_string());
515
516 let md = tree.to_markdown();
517 assert!(md.contains("# Proof Explanation"));
518 assert!(md.contains("test query"));
519 assert!(md.contains("✓"));
520 }
521
522 #[test]
523 fn test_html_generation() {
524 let root = ProofNode::fact("test".to_string(), 0);
525 let tree = ProofTree::new(root, "test query".to_string());
526
527 let html = tree.to_html();
528 assert!(html.contains("<!DOCTYPE html>"));
529 assert!(html.contains("test query"));
530 assert!(html.contains("✓"));
531 }
532}