use crate::proof::{Proof, ProofNode, ProofNodeId, ProofStep};
use crate::theory::TheoryProof;
use std::collections::HashMap;
#[derive(Debug)]
pub struct LeanExporter {
node_to_ident: HashMap<ProofNodeId, String>,
name_counter: usize,
use_lean4: bool,
}
impl LeanExporter {
#[must_use]
pub fn new() -> Self {
Self {
node_to_ident: HashMap::new(),
name_counter: 0,
use_lean4: true,
}
}
#[must_use]
pub fn lean3() -> Self {
Self {
node_to_ident: HashMap::new(),
name_counter: 0,
use_lean4: false,
}
}
fn fresh_ident(&mut self, prefix: &str) -> String {
let ident = format!("{}_{}", prefix, self.name_counter);
self.name_counter += 1;
ident
}
fn escape_string(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
}
fn conclusion_to_prop(&self, conclusion: &str) -> String {
format!("PropOf \"{}\"", Self::escape_string(conclusion))
}
fn export_node(&mut self, _proof: &Proof, node_id: ProofNodeId, node: &ProofNode) -> String {
let ident = self.fresh_ident("step");
self.node_to_ident.insert(node_id, ident.clone());
match &node.step {
ProofStep::Axiom { conclusion } => {
let prop = self.conclusion_to_prop(conclusion);
format!("axiom {} : {}", ident, prop)
}
ProofStep::Inference {
rule,
premises,
conclusion,
..
} => {
let prop = self.conclusion_to_prop(conclusion);
let premise_idents: Vec<String> = premises
.iter()
.filter_map(|&p| self.node_to_ident.get(&p).cloned())
.collect();
if premise_idents.is_empty() {
format!("axiom {} : {}", ident, prop)
} else {
let premises_str = premise_idents.join(" → ");
if self.use_lean4 {
format!(
"theorem {} : {} → {} := by\n -- Rule: {}\n sorry",
ident, premises_str, prop, rule
)
} else {
format!(
"theorem {} : {} → {} :=\nbegin\n -- Rule: {}\n sorry\nend",
ident, premises_str, prop, rule
)
}
}
}
}
}
pub fn export_proof(&mut self, proof: &Proof) -> String {
let mut output = String::new();
output.push_str("-- Generated Lean proof\n");
output.push_str("-- This proof was automatically exported from oxiz-proof\n\n");
if self.use_lean4 {
output.push_str("import Std.Logic\n\n");
} else {
output.push_str("import logic.basic\n\n");
}
output.push_str("-- Proposition representation\n");
if self.use_lean4 {
output.push_str("def PropOf (s : String) : Prop := True\n\n");
} else {
output.push_str("def PropOf (s : string) : Prop := true\n\n");
}
let nodes = proof.nodes();
output.push_str("-- Proof steps\n");
for node in nodes {
let node_def = self.export_node(proof, node.id, node);
output.push_str(&node_def);
output.push_str("\n\n");
}
if let Some(root_id) = proof.root()
&& let Some(root_ident) = self.node_to_ident.get(&root_id)
{
output.push_str("-- Main result\n");
if self.use_lean4 {
output.push_str("theorem main_result : ∃ P, P := by\n");
output.push_str(&format!(" use {}\n", root_ident));
} else {
output.push_str("theorem main_result : ∃ P, P :=\n");
output.push_str(&format!("⟨{}, _⟩\n", root_ident));
}
}
output
}
pub fn export_theory_proof(&mut self, theory_proof: &TheoryProof) -> String {
let mut output = String::new();
output.push_str("-- Generated Lean theory proof\n\n");
if self.use_lean4 {
output.push_str("import Std.Data.Int\n");
output.push_str("import Std.Data.Rat\n\n");
} else {
output.push_str("import data.int.basic\n");
output.push_str("import data.rat.basic\n\n");
}
output.push_str("-- Theory axioms and lemmas\n");
for step in theory_proof.steps() {
let step_name = self.fresh_ident("theory_step");
output.push_str(&format!("-- Step {}: {:?}\n", step.id.0, step.rule));
output.push_str(&format!("axiom {} : Prop\n\n", step_name));
}
output.push_str("-- Theory proof complete\n");
output
}
}
impl Default for LeanExporter {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn export_to_lean(proof: &Proof) -> String {
let mut exporter = LeanExporter::new();
exporter.export_proof(proof)
}
#[must_use]
pub fn export_to_lean3(proof: &Proof) -> String {
let mut exporter = LeanExporter::lean3();
exporter.export_proof(proof)
}
#[must_use]
pub fn export_theory_to_lean(theory_proof: &TheoryProof) -> String {
let mut exporter = LeanExporter::new();
exporter.export_theory_proof(theory_proof)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lean_exporter_creation() {
let exporter = LeanExporter::new();
assert!(exporter.use_lean4);
}
#[test]
fn test_lean3_exporter_creation() {
let exporter = LeanExporter::lean3();
assert!(!exporter.use_lean4);
}
#[test]
fn test_fresh_ident() {
let mut exporter = LeanExporter::new();
let id1 = exporter.fresh_ident("test");
let id2 = exporter.fresh_ident("test");
assert_ne!(id1, id2);
assert!(id1.starts_with("test_"));
assert!(id2.starts_with("test_"));
}
#[test]
fn test_escape_string() {
assert_eq!(LeanExporter::escape_string("hello"), "hello");
assert_eq!(LeanExporter::escape_string("a\\b"), "a\\\\b");
assert_eq!(LeanExporter::escape_string("a\"b"), "a\\\"b");
}
#[test]
fn test_export_simple_proof_lean4() {
let mut proof = Proof::new();
let _axiom = proof.add_axiom("P");
let lean_code = export_to_lean(&proof);
assert!(lean_code.contains("import Std"));
assert!(lean_code.contains("axiom"));
assert!(lean_code.contains("PropOf"));
}
#[test]
fn test_export_simple_proof_lean3() {
let mut proof = Proof::new();
let _axiom = proof.add_axiom("P");
let lean_code = export_to_lean3(&proof);
assert!(lean_code.contains("import logic"));
assert!(lean_code.contains("axiom"));
}
#[test]
fn test_export_inference_proof() {
let mut proof = Proof::new();
let axiom1 = proof.add_axiom("P");
let axiom2 = proof.add_axiom("P -> Q");
let _conclusion = proof.add_inference("modus_ponens", vec![axiom1, axiom2], "Q");
let lean_code = export_to_lean(&proof);
assert!(lean_code.contains("theorem"));
assert!(lean_code.contains("modus_ponens"));
assert!(lean_code.contains("by"));
}
#[test]
fn test_export_theory_proof() {
let theory_proof = TheoryProof::new();
let lean_code = export_theory_to_lean(&theory_proof);
assert!(lean_code.contains("theory proof"));
assert!(lean_code.contains("import Std"));
}
}