Skip to main content

oxiz_proof/
lean.rs

1//! Lean proof export
2//!
3//! This module provides utilities for exporting proofs to Lean format,
4//! enabling verification in the Lean theorem prover.
5//!
6//! ## Overview
7//!
8//! Lean is an interactive theorem prover and programming language based on
9//! dependent type theory. This module translates SMT proofs into Lean syntax
10//! that can be checked by Lean 4.
11//!
12//! ## References
13//!
14//! - Lean Documentation: <https://lean-lang.org/>
15//! - Lean 4 Manual: <https://leanprover.github.io/lean4/doc/>
16
17use crate::proof::{Proof, ProofNode, ProofNodeId, ProofStep};
18use crate::theory::TheoryProof;
19use std::collections::HashMap;
20
21/// Lean proof exporter (Lean 4 format)
22#[derive(Debug)]
23pub struct LeanExporter {
24    /// Mapping from proof nodes to Lean identifiers
25    node_to_ident: HashMap<ProofNodeId, String>,
26    /// Counter for generating unique names
27    name_counter: usize,
28    /// Lean 4 syntax
29    use_lean4: bool,
30}
31
32impl LeanExporter {
33    /// Create a new Lean exporter (defaults to Lean 4)
34    #[must_use]
35    pub fn new() -> Self {
36        Self {
37            node_to_ident: HashMap::new(),
38            name_counter: 0,
39            use_lean4: true,
40        }
41    }
42
43    /// Create a Lean 3 exporter
44    #[must_use]
45    pub fn lean3() -> Self {
46        Self {
47            node_to_ident: HashMap::new(),
48            name_counter: 0,
49            use_lean4: false,
50        }
51    }
52
53    /// Generate a fresh identifier
54    fn fresh_ident(&mut self, prefix: &str) -> String {
55        let ident = format!("{}_{}", prefix, self.name_counter);
56        self.name_counter += 1;
57        ident
58    }
59
60    /// Escape a string for use in Lean
61    fn escape_string(s: &str) -> String {
62        s.replace('\\', "\\\\")
63            .replace('"', "\\\"")
64            .replace('\n', "\\n")
65    }
66
67    /// Convert a conclusion to a Lean proposition
68    fn conclusion_to_prop(&self, conclusion: &str) -> String {
69        // For now, represent conclusions as propositions
70        // In a real implementation, this would parse and translate the formula
71        format!("PropOf \"{}\"", Self::escape_string(conclusion))
72    }
73
74    /// Export a proof node to Lean
75    fn export_node(&mut self, _proof: &Proof, node_id: ProofNodeId, node: &ProofNode) -> String {
76        let ident = self.fresh_ident("step");
77        self.node_to_ident.insert(node_id, ident.clone());
78
79        match &node.step {
80            ProofStep::Axiom { conclusion } => {
81                let prop = self.conclusion_to_prop(conclusion);
82                format!("axiom {} : {}", ident, prop)
83            }
84            ProofStep::Inference {
85                rule,
86                premises,
87                conclusion,
88                ..
89            } => {
90                let prop = self.conclusion_to_prop(conclusion);
91                let premise_idents: Vec<String> = premises
92                    .iter()
93                    .filter_map(|&p| self.node_to_ident.get(&p).cloned())
94                    .collect();
95
96                if premise_idents.is_empty() {
97                    format!("axiom {} : {}", ident, prop)
98                } else {
99                    let premises_str = premise_idents.join(" → ");
100                    if self.use_lean4 {
101                        format!(
102                            "theorem {} : {} → {} := by\n  -- Rule: {}\n  sorry",
103                            ident, premises_str, prop, rule
104                        )
105                    } else {
106                        format!(
107                            "theorem {} : {} → {} :=\nbegin\n  -- Rule: {}\n  sorry\nend",
108                            ident, premises_str, prop, rule
109                        )
110                    }
111                }
112            }
113        }
114    }
115
116    /// Export a proof to Lean format
117    pub fn export_proof(&mut self, proof: &Proof) -> String {
118        let mut output = String::new();
119
120        // Header
121        output.push_str("-- Generated Lean proof\n");
122        output.push_str("-- This proof was automatically exported from oxiz-proof\n\n");
123
124        if self.use_lean4 {
125            // Lean 4 imports
126            output.push_str("import Std.Logic\n\n");
127        } else {
128            // Lean 3 imports
129            output.push_str("import logic.basic\n\n");
130        }
131
132        // Define base types
133        output.push_str("-- Proposition representation\n");
134        if self.use_lean4 {
135            output.push_str("def PropOf (s : String) : Prop := True\n\n");
136        } else {
137            output.push_str("def PropOf (s : string) : Prop := true\n\n");
138        }
139
140        // Export nodes
141        let nodes = proof.nodes();
142
143        output.push_str("-- Proof steps\n");
144        for node in nodes {
145            let node_def = self.export_node(proof, node.id, node);
146            output.push_str(&node_def);
147            output.push_str("\n\n");
148        }
149
150        // Final theorem
151        if let Some(root_id) = proof.root()
152            && let Some(root_ident) = self.node_to_ident.get(&root_id)
153        {
154            output.push_str("-- Main result\n");
155            if self.use_lean4 {
156                output.push_str("theorem main_result : ∃ P, P := by\n");
157                output.push_str(&format!("  use {}\n", root_ident));
158            } else {
159                output.push_str("theorem main_result : ∃ P, P :=\n");
160                output.push_str(&format!("⟨{}, _⟩\n", root_ident));
161            }
162        }
163
164        output
165    }
166
167    /// Export a theory proof to Lean format
168    pub fn export_theory_proof(&mut self, theory_proof: &TheoryProof) -> String {
169        let mut output = String::new();
170
171        output.push_str("-- Generated Lean theory proof\n\n");
172
173        if self.use_lean4 {
174            output.push_str("import Std.Data.Int\n");
175            output.push_str("import Std.Data.Rat\n\n");
176        } else {
177            output.push_str("import data.int.basic\n");
178            output.push_str("import data.rat.basic\n\n");
179        }
180
181        output.push_str("-- Theory axioms and lemmas\n");
182        for step in theory_proof.steps() {
183            let step_name = self.fresh_ident("theory_step");
184            output.push_str(&format!("-- Step {}: {:?}\n", step.id.0, step.rule));
185            output.push_str(&format!("axiom {} : Prop\n\n", step_name));
186        }
187
188        output.push_str("-- Theory proof complete\n");
189        output
190    }
191}
192
193impl Default for LeanExporter {
194    fn default() -> Self {
195        Self::new()
196    }
197}
198
199/// Export a proof to Lean 4 format
200#[must_use]
201pub fn export_to_lean(proof: &Proof) -> String {
202    let mut exporter = LeanExporter::new();
203    exporter.export_proof(proof)
204}
205
206/// Export a proof to Lean 3 format
207#[must_use]
208pub fn export_to_lean3(proof: &Proof) -> String {
209    let mut exporter = LeanExporter::lean3();
210    exporter.export_proof(proof)
211}
212
213/// Export a theory proof to Lean 4 format
214#[must_use]
215pub fn export_theory_to_lean(theory_proof: &TheoryProof) -> String {
216    let mut exporter = LeanExporter::new();
217    exporter.export_theory_proof(theory_proof)
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn test_lean_exporter_creation() {
226        let exporter = LeanExporter::new();
227        assert!(exporter.use_lean4);
228    }
229
230    #[test]
231    fn test_lean3_exporter_creation() {
232        let exporter = LeanExporter::lean3();
233        assert!(!exporter.use_lean4);
234    }
235
236    #[test]
237    fn test_fresh_ident() {
238        let mut exporter = LeanExporter::new();
239        let id1 = exporter.fresh_ident("test");
240        let id2 = exporter.fresh_ident("test");
241        assert_ne!(id1, id2);
242        assert!(id1.starts_with("test_"));
243        assert!(id2.starts_with("test_"));
244    }
245
246    #[test]
247    fn test_escape_string() {
248        assert_eq!(LeanExporter::escape_string("hello"), "hello");
249        assert_eq!(LeanExporter::escape_string("a\\b"), "a\\\\b");
250        assert_eq!(LeanExporter::escape_string("a\"b"), "a\\\"b");
251    }
252
253    #[test]
254    fn test_export_simple_proof_lean4() {
255        let mut proof = Proof::new();
256        let _axiom = proof.add_axiom("P");
257
258        let lean_code = export_to_lean(&proof);
259        assert!(lean_code.contains("import Std"));
260        assert!(lean_code.contains("axiom"));
261        assert!(lean_code.contains("PropOf"));
262    }
263
264    #[test]
265    fn test_export_simple_proof_lean3() {
266        let mut proof = Proof::new();
267        let _axiom = proof.add_axiom("P");
268
269        let lean_code = export_to_lean3(&proof);
270        assert!(lean_code.contains("import logic"));
271        assert!(lean_code.contains("axiom"));
272    }
273
274    #[test]
275    fn test_export_inference_proof() {
276        let mut proof = Proof::new();
277        let axiom1 = proof.add_axiom("P");
278        let axiom2 = proof.add_axiom("P -> Q");
279        let _conclusion = proof.add_inference("modus_ponens", vec![axiom1, axiom2], "Q");
280
281        let lean_code = export_to_lean(&proof);
282        assert!(lean_code.contains("theorem"));
283        assert!(lean_code.contains("modus_ponens"));
284        assert!(lean_code.contains("by"));
285    }
286
287    #[test]
288    fn test_export_theory_proof() {
289        let theory_proof = TheoryProof::new();
290        let lean_code = export_theory_to_lean(&theory_proof);
291        assert!(lean_code.contains("theory proof"));
292        assert!(lean_code.contains("import Std"));
293    }
294}