use crate::alethe::{AletheProof, AletheRule, AletheStep, StepIndex, TermRef};
use rustc_hash::FxHashSet;
use std::io::{self, Write};
#[derive(Debug, Default)]
pub struct CarcaraProof {
proof: AletheProof,
defined_steps: FxHashSet<StepIndex>,
next_index: StepIndex,
}
impl CarcaraProof {
#[must_use]
pub fn new() -> Self {
Self {
proof: AletheProof::new(),
defined_steps: FxHashSet::default(),
next_index: 1,
}
}
#[allow(dead_code)]
fn alloc_index(&mut self) -> StepIndex {
let idx = self.next_index;
self.next_index += 1;
idx
}
pub fn add_assume(&mut self, term: impl Into<TermRef>) -> Result<StepIndex, String> {
let index = self.proof.assume(term);
if !self.defined_steps.insert(index) {
return Err(format!("Step index {} already defined", index));
}
self.next_index = index + 1;
Ok(index)
}
#[allow(clippy::too_many_arguments)]
pub fn add_step(
&mut self,
clause: Vec<TermRef>,
rule: AletheRule,
premises: Vec<StepIndex>,
args: Vec<TermRef>,
) -> Result<StepIndex, String> {
for &premise in &premises {
if !self.defined_steps.contains(&premise) {
return Err(format!("Premise {} not yet defined", premise));
}
}
let index = self.proof.step(clause, rule, premises, args);
if !self.defined_steps.insert(index) {
return Err(format!("Step index {} already defined", index));
}
self.next_index = index + 1;
Ok(index)
}
pub fn add_anchor(&mut self, args: Vec<(String, TermRef)>) -> Result<StepIndex, String> {
let index = self.proof.anchor(args);
if !self.defined_steps.insert(index) {
return Err(format!("Step index {} already defined", index));
}
self.next_index = index + 1;
Ok(index)
}
pub fn validate(&self) -> Result<(), String> {
let mut seen_indices = FxHashSet::default();
for step in self.proof.steps() {
let index = match step {
AletheStep::Assume { index, .. } => *index,
AletheStep::Step {
index, premises, ..
} => {
for &premise in premises {
if !seen_indices.contains(&premise) {
return Err(format!(
"Step {} uses undefined premise {}",
index, premise
));
}
}
*index
}
AletheStep::Anchor { step, .. } => *step,
AletheStep::DefineFun { .. } => continue,
};
if !seen_indices.insert(index) {
return Err(format!("Duplicate step index: {}", index));
}
}
Ok(())
}
#[must_use]
pub fn alethe_proof(&self) -> &AletheProof {
&self.proof
}
pub fn write<W: Write>(&self, writer: W) -> io::Result<()> {
self.proof.write(writer)
}
#[must_use]
pub fn len(&self) -> usize {
self.proof.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.proof.is_empty()
}
pub fn clear(&mut self) {
self.proof.clear();
self.defined_steps.clear();
self.next_index = 1;
}
}
pub fn validate_for_carcara(proof: &AletheProof) -> Result<(), String> {
let mut seen_indices = FxHashSet::default();
for step in proof.steps() {
let index = match step {
AletheStep::Assume { index, .. } => *index,
AletheStep::Step {
index, premises, ..
} => {
for &premise in premises {
if !seen_indices.contains(&premise) {
return Err(format!("Step {} uses undefined premise {}", index, premise));
}
}
*index
}
AletheStep::Anchor { step, .. } => *step,
AletheStep::DefineFun { .. } => continue,
};
if !seen_indices.insert(index) {
return Err(format!("Duplicate step index: {}", index));
}
}
Ok(())
}
pub fn to_carcara_format(proof: &AletheProof) -> Result<CarcaraProof, String> {
let mut carcara = CarcaraProof::new();
let mut index_map: rustc_hash::FxHashMap<StepIndex, StepIndex> =
rustc_hash::FxHashMap::default();
for step in proof.steps() {
match step {
AletheStep::Assume { index, term } => {
let new_index = carcara.add_assume(term.clone())?;
index_map.insert(*index, new_index);
}
AletheStep::Step {
index,
clause,
rule,
premises,
args,
} => {
let new_premises: Vec<StepIndex> = premises
.iter()
.map(|p| {
index_map
.get(p)
.copied()
.ok_or_else(|| format!("Premise {} not found in index map", p))
})
.collect::<Result<Vec<_>, String>>()?;
let new_index =
carcara.add_step(clause.clone(), *rule, new_premises, args.clone())?;
index_map.insert(*index, new_index);
}
AletheStep::Anchor { step, args } => {
let new_index = carcara.add_anchor(args.clone())?;
index_map.insert(*step, new_index);
}
AletheStep::DefineFun { .. } => {
continue;
}
}
}
Ok(carcara)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_carcara_proof_creation() {
let proof = CarcaraProof::new();
assert!(proof.is_empty());
assert_eq!(proof.len(), 0);
}
#[test]
fn test_add_assume() {
let mut proof = CarcaraProof::new();
let idx = proof
.add_assume("p")
.expect("test operation should succeed");
assert_eq!(idx, 1);
assert_eq!(proof.len(), 1);
}
#[test]
fn test_add_step_with_premise() {
let mut proof = CarcaraProof::new();
let p1 = proof
.add_assume("p")
.expect("test operation should succeed");
let p2 = proof
.add_assume("q")
.expect("test operation should succeed");
let step = proof.add_step(
vec!["(or p q)".to_string()],
AletheRule::Resolution,
vec![p1, p2],
vec![],
);
assert!(step.is_ok());
assert_eq!(proof.len(), 3);
}
#[test]
fn test_undefined_premise_error() {
let mut proof = CarcaraProof::new();
let result = proof.add_step(
vec!["p".to_string()],
AletheRule::Resolution,
vec![99],
vec![],
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("not yet defined"));
}
#[test]
fn test_validation() {
let mut proof = CarcaraProof::new();
proof
.add_assume("p")
.expect("test operation should succeed");
proof
.add_assume("q")
.expect("test operation should succeed");
let result = proof.validate();
assert!(result.is_ok());
}
#[test]
fn test_sequential_indices() {
let mut proof = CarcaraProof::new();
let i1 = proof
.add_assume("p")
.expect("test operation should succeed");
let i2 = proof
.add_assume("q")
.expect("test operation should succeed");
let i3 = proof
.add_assume("r")
.expect("test operation should succeed");
assert_eq!(i1, 1);
assert_eq!(i2, 2);
assert_eq!(i3, 3);
}
#[test]
fn test_clear() {
let mut proof = CarcaraProof::new();
proof
.add_assume("p")
.expect("test operation should succeed");
proof
.add_assume("q")
.expect("test operation should succeed");
assert_eq!(proof.len(), 2);
proof.clear();
assert!(proof.is_empty());
let idx = proof
.add_assume("r")
.expect("test operation should succeed");
assert_eq!(idx, 1);
}
#[test]
fn test_anchor() {
let mut proof = CarcaraProof::new();
let idx = proof
.add_anchor(vec![("x".to_string(), "Int".to_string())])
.expect("test operation should succeed");
assert_eq!(idx, 1);
assert_eq!(proof.len(), 1);
}
#[test]
fn test_validate_for_carcara() {
let mut alethe = AletheProof::new();
alethe.assume("p");
alethe.assume("q");
let result = validate_for_carcara(&alethe);
assert!(result.is_ok());
}
#[test]
fn test_to_carcara_format() {
let mut alethe = AletheProof::new();
let p = alethe.assume("p");
let q = alethe.assume("q");
alethe.step(
vec!["(or p q)".to_string()],
AletheRule::Resolution,
vec![p, q],
vec![],
);
let carcara = to_carcara_format(&alethe).expect("test operation should succeed");
assert_eq!(carcara.len(), 3);
assert!(carcara.validate().is_ok());
}
}