1#![allow(dead_code)] use crate::proof::{Proof, ProofNode, ProofNodeId, ProofStep};
8use crate::theory::TheoryProof;
9use rustc_hash::FxHashMap;
10
11#[derive(Debug)]
13pub struct EnhancedLeanExporter {
14 node_to_ident: FxHashMap<ProofNodeId, String>,
16 node_to_proof_term: FxHashMap<ProofNodeId, LeanProofTerm>,
18 name_counter: usize,
20 prefer_term_mode: bool,
22}
23
24#[derive(Debug, Clone)]
26pub enum LeanProofTerm {
27 Var(String),
29 App {
31 function: Box<LeanProofTerm>,
32 args: Vec<LeanProofTerm>,
33 },
34 Lambda {
36 params: Vec<(String, LeanType)>,
37 body: Box<LeanProofTerm>,
38 },
39 Tactic(LeanTactic),
41 Have {
43 name: String,
44 ty: Option<LeanType>,
45 proof: Box<LeanProofTerm>,
46 body: Box<LeanProofTerm>,
47 },
48}
49
50#[derive(Debug, Clone)]
52pub enum LeanType {
53 Prop,
55 Type,
57 Sort,
59 Arrow(Box<LeanType>, Box<LeanType>),
61 Named(String),
63 App(Box<LeanType>, Vec<LeanType>),
65}
66
67#[derive(Debug, Clone)]
69pub enum LeanTactic {
70 Apply(String),
72 Exact(Box<LeanProofTerm>),
74 Intro(Vec<String>),
76 Cases(String),
78 Split,
80 Constructor(usize),
82 Rfl,
84 Simp { lemmas: Vec<String> },
86 Omega,
88 Decide,
90 Seq(Vec<LeanTactic>),
92}
93
94impl LeanProofTerm {
95 pub fn to_lean(&self) -> String {
97 match self {
98 LeanProofTerm::Var(v) => v.clone(),
99 LeanProofTerm::App { function, args } => {
100 let func_str = function.to_lean();
101 if args.is_empty() {
102 func_str
103 } else {
104 let args_str = args
105 .iter()
106 .map(|a| a.to_lean())
107 .collect::<Vec<_>>()
108 .join(" ");
109 format!("({} {})", func_str, args_str)
110 }
111 }
112 LeanProofTerm::Lambda { params, body } => {
113 let params_str = params
114 .iter()
115 .map(|(name, ty)| format!("({} : {})", name, ty.to_lean()))
116 .collect::<Vec<_>>()
117 .join(" ");
118 format!("fun {} => {}", params_str, body.to_lean())
119 }
120 LeanProofTerm::Tactic(tactic) => format!("by\n {}", tactic.to_lean()),
121 LeanProofTerm::Have {
122 name,
123 ty,
124 proof,
125 body,
126 } => {
127 let ty_str = ty
128 .as_ref()
129 .map(|t| format!(" : {}", t.to_lean()))
130 .unwrap_or_default();
131 format!(
132 "have {}{} := {}\n{}",
133 name,
134 ty_str,
135 proof.to_lean(),
136 body.to_lean()
137 )
138 }
139 }
140 }
141}
142
143impl LeanType {
144 pub fn to_lean(&self) -> String {
146 match self {
147 LeanType::Prop => "Prop".to_string(),
148 LeanType::Type => "Type".to_string(),
149 LeanType::Sort => "Sort".to_string(),
150 LeanType::Arrow(a, b) => format!("({} → {})", a.to_lean(), b.to_lean()),
151 LeanType::Named(n) => n.clone(),
152 LeanType::App(ty, args) => {
153 if args.is_empty() {
154 ty.to_lean()
155 } else {
156 let args_str = args
157 .iter()
158 .map(|a| a.to_lean())
159 .collect::<Vec<_>>()
160 .join(" ");
161 format!("({} {})", ty.to_lean(), args_str)
162 }
163 }
164 }
165 }
166}
167
168impl LeanTactic {
169 pub fn to_lean(&self) -> String {
171 match self {
172 LeanTactic::Apply(name) => format!("apply {}", name),
173 LeanTactic::Exact(term) => format!("exact {}", term.to_lean()),
174 LeanTactic::Intro(names) => {
175 if names.is_empty() {
176 "intro".to_string()
177 } else {
178 format!("intro {}", names.join(" "))
179 }
180 }
181 LeanTactic::Cases(name) => format!("cases {}", name),
182 LeanTactic::Split => "split".to_string(),
183 LeanTactic::Constructor(n) => {
184 if *n == 0 {
185 "constructor".to_string()
186 } else {
187 format!("apply Constructor.mk{}", n)
188 }
189 }
190 LeanTactic::Rfl => "rfl".to_string(),
191 LeanTactic::Simp { lemmas } => {
192 if lemmas.is_empty() {
193 "simp".to_string()
194 } else {
195 format!("simp [{}]", lemmas.join(", "))
196 }
197 }
198 LeanTactic::Omega => "omega".to_string(),
199 LeanTactic::Decide => "decide".to_string(),
200 LeanTactic::Seq(tactics) => tactics
201 .iter()
202 .map(|t| t.to_lean())
203 .collect::<Vec<_>>()
204 .join("\n "),
205 }
206 }
207}
208
209impl EnhancedLeanExporter {
210 pub fn new() -> Self {
212 Self {
213 node_to_ident: FxHashMap::default(),
214 node_to_proof_term: FxHashMap::default(),
215 name_counter: 0,
216 prefer_term_mode: false,
217 }
218 }
219
220 pub fn with_term_mode() -> Self {
222 Self {
223 node_to_ident: FxHashMap::default(),
224 node_to_proof_term: FxHashMap::default(),
225 name_counter: 0,
226 prefer_term_mode: true,
227 }
228 }
229
230 fn fresh_ident(&mut self, prefix: &str) -> String {
232 let ident = format!("{}_{}", prefix, self.name_counter);
233 self.name_counter += 1;
234 ident
235 }
236
237 fn build_inference_proof_term(
239 &mut self,
240 rule: &str,
241 premises: &[ProofNodeId],
242 ) -> LeanProofTerm {
243 let tactic = match rule {
244 "resolution" => {
245 LeanTactic::Apply("resolution_rule".to_string())
247 }
248 "modus_ponens" | "mp" => {
249 if premises.len() >= 2 {
251 if let Some(impl_ident) =
252 premises.get(1).and_then(|id| self.node_to_ident.get(id))
253 {
254 LeanTactic::Apply(impl_ident.clone())
255 } else {
256 LeanTactic::Apply("mp_rule".to_string())
257 }
258 } else {
259 LeanTactic::Apply("mp_rule".to_string())
260 }
261 }
262 "and_intro" => LeanTactic::Seq(vec![
263 LeanTactic::Split,
264 LeanTactic::Apply("And.intro".to_string()),
265 ]),
266 "and_elim_left" => LeanTactic::Apply("And.left".to_string()),
267 "and_elim_right" => LeanTactic::Apply("And.right".to_string()),
268 "or_intro_left" => LeanTactic::Apply("Or.inl".to_string()),
269 "or_intro_right" => LeanTactic::Apply("Or.inr".to_string()),
270 "refl" | "eq_refl" => LeanTactic::Rfl,
271 "symm" | "eq_symm" => LeanTactic::Apply("Eq.symm".to_string()),
272 "trans" | "eq_trans" => LeanTactic::Apply("Eq.trans".to_string()),
273 "congruence" | "cong" => LeanTactic::Apply("congrArg".to_string()),
274 "lia" | "linear_arithmetic" => LeanTactic::Omega,
275 "lra" | "linear_real_arithmetic" => LeanTactic::Omega,
276 "decide" => LeanTactic::Decide,
277 _ => LeanTactic::Simp { lemmas: vec![] },
278 };
279
280 LeanProofTerm::Tactic(tactic)
281 }
282
283 fn export_node(&mut self, _proof: &Proof, node_id: ProofNodeId, node: &ProofNode) -> String {
285 let ident = self.fresh_ident("step");
286 self.node_to_ident.insert(node_id, ident.clone());
287
288 match &node.step {
289 ProofStep::Axiom { conclusion } => {
290 format!("axiom {} : PropOf \"{}\"", ident, conclusion)
291 }
292 ProofStep::Inference {
293 rule,
294 premises,
295 conclusion,
296 ..
297 } => {
298 let proof_term = self.build_inference_proof_term(rule, premises);
299 self.node_to_proof_term.insert(node_id, proof_term.clone());
300
301 let premise_idents: Vec<String> = premises
302 .iter()
303 .filter_map(|&p| self.node_to_ident.get(&p).cloned())
304 .collect();
305
306 let prop = format!("PropOf \"{}\"", conclusion);
307
308 if premise_idents.is_empty() {
309 format!("axiom {} : {}", ident, prop)
310 } else {
311 let premises_str = premise_idents.join(" → ");
312 format!(
313 "theorem {} : {} → {} :=\n -- Rule: {}\n {}",
314 ident,
315 premises_str,
316 prop,
317 rule,
318 proof_term.to_lean()
319 )
320 }
321 }
322 }
323 }
324
325 pub fn export_proof(&mut self, proof: &Proof) -> String {
327 let mut output = String::new();
328
329 output.push_str("-- Enhanced Lean 4 proof with complete proof terms\n");
331 output.push_str("-- Generated by oxiz-proof enhanced exporter\n\n");
332
333 output.push_str("import Std.Logic\n");
335 output.push_str("import Std.Data.Int.Basic\n");
336 output.push_str("import Std.Data.Rat.Basic\n");
337 output.push_str("import Std.Tactic.Omega\n\n");
338
339 output.push_str("-- Proposition representation\n");
341 output.push_str("def PropOf (s : String) : Prop := True\n\n");
342
343 output.push_str("-- Resolution rule\n");
345 output.push_str(
346 "axiom resolution_rule : ∀ (C1 C2 p : Prop), (C1 ∨ p) → (C2 ∨ ¬p) → (C1 ∨ C2)\n\n",
347 );
348 output.push_str("-- Modus ponens\n");
349 output.push_str("axiom mp_rule : ∀ (P Q : Prop), P → (P → Q) → Q\n\n");
350
351 let nodes = proof.nodes();
353
354 output.push_str("-- Proof steps with complete proof terms\n");
355 for node in nodes {
356 let node_def = self.export_node(proof, node.id, node);
357 output.push_str(&node_def);
358 output.push_str("\n\n");
359 }
360
361 if let Some(root_id) = proof.root()
363 && let Some(root_ident) = self.node_to_ident.get(&root_id)
364 {
365 output.push_str("-- Main result\n");
366 output.push_str("theorem main_result : ∃ P, P := by\n");
367 output.push_str(&format!(" use {}\n", root_ident));
368 if let Some(proof_term) = self.node_to_proof_term.get(&root_id)
369 && let LeanProofTerm::Tactic(tactic) = proof_term
370 {
371 output.push_str(&format!(" {}\n", tactic.to_lean()));
372 }
373 }
374
375 output
376 }
377
378 pub fn export_theory_proof(&mut self, theory_proof: &TheoryProof) -> String {
380 let mut output = String::new();
381
382 output.push_str("-- Enhanced Lean 4 theory proof\n\n");
383 output.push_str("import Std.Data.Int.Basic\n");
384 output.push_str("import Std.Data.Rat.Basic\n");
385 output.push_str("import Std.Tactic.Omega\n\n");
386
387 output.push_str("-- Theory axioms and lemmas\n");
388 for step in theory_proof.steps() {
389 let step_name = self.fresh_ident("theory_step");
390 output.push_str(&format!("-- Step {}: {:?}\n", step.id.0, step.rule));
391 output.push_str(&format!("axiom {} : Prop\n\n", step_name));
392 }
393
394 output.push_str("-- Theory proof complete\n");
395 output
396 }
397}
398
399impl Default for EnhancedLeanExporter {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405pub fn export_to_lean_enhanced(proof: &Proof) -> String {
407 let mut exporter = EnhancedLeanExporter::new();
408 exporter.export_proof(proof)
409}
410
411pub fn export_to_lean_term_mode(proof: &Proof) -> String {
413 let mut exporter = EnhancedLeanExporter::with_term_mode();
414 exporter.export_proof(proof)
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_lean_proof_term_var() {
423 let term = LeanProofTerm::Var("H".to_string());
424 assert_eq!(term.to_lean(), "H");
425 }
426
427 #[test]
428 fn test_lean_proof_term_lambda() {
429 let term = LeanProofTerm::Lambda {
430 params: vec![("x".to_string(), LeanType::Prop)],
431 body: Box::new(LeanProofTerm::Var("x".to_string())),
432 };
433 assert_eq!(term.to_lean(), "fun (x : Prop) => x");
434 }
435
436 #[test]
437 fn test_lean_tactic_rfl() {
438 let tactic = LeanTactic::Rfl;
439 assert_eq!(tactic.to_lean(), "rfl");
440 }
441
442 #[test]
443 fn test_lean_tactic_apply() {
444 let tactic = LeanTactic::Apply("foo".to_string());
445 assert_eq!(tactic.to_lean(), "apply foo");
446 }
447
448 #[test]
449 fn test_enhanced_exporter_creation() {
450 let exporter = EnhancedLeanExporter::new();
451 assert!(!exporter.prefer_term_mode);
452 }
453
454 #[test]
455 fn test_term_mode_exporter() {
456 let exporter = EnhancedLeanExporter::with_term_mode();
457 assert!(exporter.prefer_term_mode);
458 }
459
460 #[test]
461 fn test_export_with_proof_terms() {
462 let mut proof = Proof::new();
463 let axiom = proof.add_axiom("P");
464 let _conclusion = proof.add_inference("refl", vec![axiom], "P = P");
465
466 let lean_code = export_to_lean_enhanced(&proof);
467 assert!(lean_code.contains("rfl"));
468 assert!(lean_code.contains("complete proof terms"));
469 }
470}