use crate::proof::{Proof, ProofNode, ProofNodeId, ProofStep};
use rustc_hash::FxHashMap;
#[derive(Debug)]
#[allow(dead_code)] pub struct EnhancedCoqExporter {
node_to_ident: FxHashMap<ProofNodeId, String>,
node_to_proof_term: FxHashMap<ProofNodeId, CoqProofTerm>,
name_counter: usize,
type_defs: Vec<String>,
tactic_defs: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum CoqProofTerm {
Var(String),
App {
function: Box<CoqProofTerm>,
args: Vec<CoqProofTerm>,
},
Lambda {
params: Vec<(String, CoqType)>,
body: Box<CoqProofTerm>,
},
Assumption(String),
Tactic { name: String, args: Vec<String> },
Exact(Box<CoqProofTerm>),
}
#[derive(Debug, Clone)]
pub enum CoqType {
Prop,
Type,
Arrow(Box<CoqType>, Box<CoqType>),
Named(String),
App(Box<CoqType>, Vec<CoqType>),
}
impl CoqProofTerm {
pub fn to_coq(&self) -> String {
match self {
CoqProofTerm::Var(v) => v.clone(),
CoqProofTerm::App { function, args } => {
let func_str = function.to_coq();
let args_str = args
.iter()
.map(|a| a.to_coq())
.collect::<Vec<_>>()
.join(" ");
if args.is_empty() {
func_str
} else {
format!("({} {})", func_str, args_str)
}
}
CoqProofTerm::Lambda { params, body } => {
let params_str = params
.iter()
.map(|(name, ty)| format!("({} : {})", name, ty.to_coq()))
.collect::<Vec<_>>()
.join(" ");
format!("(fun {} => {})", params_str, body.to_coq())
}
CoqProofTerm::Assumption(name) => format!("assumption ({})", name),
CoqProofTerm::Tactic { name, args } => {
if args.is_empty() {
name.clone()
} else {
format!("{} {}", name, args.join(" "))
}
}
CoqProofTerm::Exact(term) => format!("exact {}", term.to_coq()),
}
}
}
impl CoqType {
pub fn to_coq(&self) -> String {
match self {
CoqType::Prop => "Prop".to_string(),
CoqType::Type => "Type".to_string(),
CoqType::Arrow(a, b) => format!("({} -> {})", a.to_coq(), b.to_coq()),
CoqType::Named(n) => n.clone(),
CoqType::App(ty, args) => {
let ty_str = ty.to_coq();
let args_str = args
.iter()
.map(|a| a.to_coq())
.collect::<Vec<_>>()
.join(" ");
format!("({} {})", ty_str, args_str)
}
}
}
}
impl EnhancedCoqExporter {
pub fn new() -> Self {
Self {
node_to_ident: FxHashMap::default(),
node_to_proof_term: FxHashMap::default(),
name_counter: 0,
type_defs: Vec::new(),
tactic_defs: Vec::new(),
}
}
fn fresh_ident(&mut self, prefix: &str) -> String {
let ident = format!("{}_{}", prefix, self.name_counter);
self.name_counter += 1;
ident
}
fn build_proof_term(&mut self, node: &ProofNode, premises: &[ProofNodeId]) -> CoqProofTerm {
match &node.step {
ProofStep::Axiom { conclusion: _ } => {
let ident = self
.node_to_ident
.get(&node.id)
.cloned()
.unwrap_or_else(|| format!("axiom_{}", node.id.0));
CoqProofTerm::Assumption(ident)
}
ProofStep::Inference {
rule,
premises: _,
conclusion: _,
..
} => {
self.build_inference_proof_term(rule, premises)
}
}
}
fn build_inference_proof_term(&mut self, rule: &str, premises: &[ProofNodeId]) -> CoqProofTerm {
match rule {
"resolution" => {
if premises.len() >= 2 {
let p1 = premises
.first()
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| CoqProofTerm::Var(s.clone()))
.unwrap_or_else(|| CoqProofTerm::Var("H1".to_string()));
let p2 = premises
.get(1)
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| CoqProofTerm::Var(s.clone()))
.unwrap_or_else(|| CoqProofTerm::Var("H2".to_string()));
CoqProofTerm::Tactic {
name: "apply resolution_rule".to_string(),
args: vec![p1.to_coq(), p2.to_coq()],
}
} else {
CoqProofTerm::Tactic {
name: "auto".to_string(),
args: vec![],
}
}
}
"modus_ponens" | "mp" => {
if premises.len() >= 2 {
let p = premises
.first()
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| CoqProofTerm::Var(s.clone()))
.unwrap_or_else(|| CoqProofTerm::Var("H1".to_string()));
let impl_pq = premises
.get(1)
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| CoqProofTerm::Var(s.clone()))
.unwrap_or_else(|| CoqProofTerm::Var("H2".to_string()));
CoqProofTerm::App {
function: Box::new(impl_pq),
args: vec![p],
}
} else {
CoqProofTerm::Tactic {
name: "auto".to_string(),
args: vec![],
}
}
}
"and_intro" => {
if premises.len() >= 2 {
let p1 = premises
.first()
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| CoqProofTerm::Var(s.clone()))
.unwrap_or_else(|| CoqProofTerm::Var("H1".to_string()));
let p2 = premises
.get(1)
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| CoqProofTerm::Var(s.clone()))
.unwrap_or_else(|| CoqProofTerm::Var("H2".to_string()));
CoqProofTerm::Tactic {
name: "split".to_string(),
args: vec![p1.to_coq(), p2.to_coq()],
}
} else {
CoqProofTerm::Tactic {
name: "auto".to_string(),
args: vec![],
}
}
}
"refl" | "eq_refl" => {
CoqProofTerm::Tactic {
name: "reflexivity".to_string(),
args: vec![],
}
}
"symm" | "eq_symm" => {
if let Some(&premise) = premises.first() {
if let Some(ident) = self.node_to_ident.get(&premise) {
CoqProofTerm::Tactic {
name: "symmetry".to_string(),
args: vec![ident.clone()],
}
} else {
CoqProofTerm::Tactic {
name: "symmetry".to_string(),
args: vec![],
}
}
} else {
CoqProofTerm::Tactic {
name: "symmetry".to_string(),
args: vec![],
}
}
}
"trans" | "eq_trans" => {
if premises.len() >= 2 {
let p1_ident = premises
.first()
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| s.as_str())
.unwrap_or("H1");
let p2_ident = premises
.get(1)
.and_then(|&id| self.node_to_ident.get(&id))
.map(|s| s.as_str())
.unwrap_or("H2");
CoqProofTerm::Tactic {
name: "transitivity".to_string(),
args: vec![p1_ident.to_string(), p2_ident.to_string()],
}
} else {
CoqProofTerm::Tactic {
name: "auto".to_string(),
args: vec![],
}
}
}
"congruence" | "cong" => {
CoqProofTerm::Tactic {
name: "congruence".to_string(),
args: vec![],
}
}
"lia" | "linear_arithmetic" => {
CoqProofTerm::Tactic {
name: "lia".to_string(),
args: vec![],
}
}
"lra" | "linear_real_arithmetic" => {
CoqProofTerm::Tactic {
name: "lra".to_string(),
args: vec![],
}
}
_ => {
let premise_terms: Vec<CoqProofTerm> = premises
.iter()
.filter_map(|&id| {
self.node_to_ident
.get(&id)
.map(|s| CoqProofTerm::Var(s.clone()))
})
.collect();
if premise_terms.is_empty() {
CoqProofTerm::Tactic {
name: "auto".to_string(),
args: vec![],
}
} else if premise_terms.len() == 1 {
CoqProofTerm::Exact(Box::new(premise_terms[0].clone()))
} else {
CoqProofTerm::Tactic {
name: "auto".to_string(),
args: premise_terms.iter().map(|t| t.to_coq()).collect(),
}
}
}
}
}
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 } => {
format!("Axiom {} : Prop_of_string \"{}\".", ident, conclusion)
}
ProofStep::Inference {
rule,
premises,
conclusion,
..
} => {
let proof_term = self.build_proof_term(node, premises);
self.node_to_proof_term.insert(node_id, proof_term.clone());
let premise_idents: Vec<String> = premises
.iter()
.filter_map(|&p| self.node_to_ident.get(&p).cloned())
.collect();
let prop = format!("Prop_of_string \"{}\"", conclusion);
if premise_idents.is_empty() {
format!("Axiom {} : {}.", ident, prop)
} else {
let premises_str = premise_idents.join(" -> ");
format!(
"Lemma {} : {} -> {}.\nProof.\n (* Rule: {} *)\n {}.\nQed.",
ident,
premises_str,
prop,
rule,
proof_term.to_coq()
)
}
}
}
}
pub fn export_proof(&mut self, proof: &Proof) -> String {
let mut output = String::new();
output.push_str("(* Enhanced Coq proof with complete proof terms *)\n");
output.push_str("(* Generated by oxiz-proof enhanced exporter *)\n\n");
output.push_str("Require Import Coq.Init.Prelude.\n");
output.push_str("Require Import Coq.Logic.Classical.\n");
output.push_str("Require Import Coq.micromega.Lia.\n");
output.push_str("Require Import Coq.micromega.Lra.\n\n");
output.push_str("(* Proposition representation *)\n");
output.push_str("Parameter Prop_of_string : string -> Prop.\n\n");
output.push_str("(* Resolution rule *)\n");
output.push_str(
"Axiom resolution_rule : forall (C1 C2 : Prop) (p : Prop),\n (C1 \\/ p) -> (C2 \\/ ~p) -> (C1 \\/ C2).\n\n",
);
let nodes = proof.nodes();
output.push_str("(* Proof steps with complete proof terms *)\n");
for node in nodes {
let node_def = self.export_node(proof, node.id, node);
output.push_str(&node_def);
output.push('\n');
}
if let Some(root_id) = proof.root()
&& let Some(root_ident) = self.node_to_ident.get(&root_id)
{
output.push_str("\n(* Main result *)\n");
output.push_str("Theorem main_result : exists P, P.\n");
output.push_str("Proof.\n");
output.push_str(&format!(" exists {}.\n", root_ident));
if let Some(proof_term) = self.node_to_proof_term.get(&root_id) {
output.push_str(&format!(" {}.\n", proof_term.to_coq()));
} else {
output.push_str(&format!(" exact {}.\n", root_ident));
}
output.push_str("Qed.\n");
}
output
}
}
impl Default for EnhancedCoqExporter {
fn default() -> Self {
Self::new()
}
}
pub fn export_to_coq_enhanced(proof: &Proof) -> String {
let mut exporter = EnhancedCoqExporter::new();
exporter.export_proof(proof)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coq_proof_term_var() {
let term = CoqProofTerm::Var("H".to_string());
assert_eq!(term.to_coq(), "H");
}
#[test]
fn test_coq_proof_term_app() {
let term = CoqProofTerm::App {
function: Box::new(CoqProofTerm::Var("f".to_string())),
args: vec![
CoqProofTerm::Var("x".to_string()),
CoqProofTerm::Var("y".to_string()),
],
};
assert_eq!(term.to_coq(), "(f x y)");
}
#[test]
fn test_coq_proof_term_lambda() {
let term = CoqProofTerm::Lambda {
params: vec![("x".to_string(), CoqType::Prop)],
body: Box::new(CoqProofTerm::Var("x".to_string())),
};
assert_eq!(term.to_coq(), "(fun (x : Prop) => x)");
}
#[test]
fn test_coq_type_arrow() {
let ty = CoqType::Arrow(Box::new(CoqType::Prop), Box::new(CoqType::Prop));
assert_eq!(ty.to_coq(), "(Prop -> Prop)");
}
#[test]
fn test_enhanced_exporter_creation() {
let exporter = EnhancedCoqExporter::new();
assert_eq!(exporter.node_to_ident.len(), 0);
}
#[test]
fn test_export_with_proof_terms() {
let mut proof = Proof::new();
let axiom = proof.add_axiom("P");
let _conclusion = proof.add_inference("refl", vec![axiom], "P = P");
let coq_code = export_to_coq_enhanced(&proof);
assert!(coq_code.contains("reflexivity"));
assert!(coq_code.contains("complete proof terms"));
}
}