1use crate::proof::{Proof, ProofNode, ProofNodeId, ProofStep};
18use crate::theory::TheoryProof;
19use std::collections::HashMap;
20
21#[derive(Debug)]
23pub struct LeanExporter {
24 node_to_ident: HashMap<ProofNodeId, String>,
26 name_counter: usize,
28 use_lean4: bool,
30}
31
32impl LeanExporter {
33 #[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 #[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 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 fn escape_string(s: &str) -> String {
62 s.replace('\\', "\\\\")
63 .replace('"', "\\\"")
64 .replace('\n', "\\n")
65 }
66
67 fn conclusion_to_prop(&self, conclusion: &str) -> String {
69 format!("PropOf \"{}\"", Self::escape_string(conclusion))
72 }
73
74 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 pub fn export_proof(&mut self, proof: &Proof) -> String {
118 let mut output = String::new();
119
120 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 output.push_str("import Std.Logic\n\n");
127 } else {
128 output.push_str("import logic.basic\n\n");
130 }
131
132 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 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 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 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#[must_use]
201pub fn export_to_lean(proof: &Proof) -> String {
202 let mut exporter = LeanExporter::new();
203 exporter.export_proof(proof)
204}
205
206#[must_use]
208pub fn export_to_lean3(proof: &Proof) -> String {
209 let mut exporter = LeanExporter::lean3();
210 exporter.export_proof(proof)
211}
212
213#[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}