mod error;
mod parser;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::io;
use std::num::NonZero;
use std::rc::Rc;
pub use error::*;
use crate::IntAtomic;
use crate::SignedIntValue;
use crate::Step;
#[derive(Debug)]
pub struct ProofReader<Source, Int> {
source: Source,
atomics: BTreeMap<NonZero<u32>, IntAtomic<Rc<str>, Int>>,
identifiers: HashSet<Rc<str>>,
line_buffer: Vec<u8>,
line_nr: usize,
}
impl<Source, Int> ProofReader<Source, Int> {
pub fn new(source: Source) -> Self {
ProofReader {
source,
atomics: BTreeMap::default(),
identifiers: HashSet::default(),
line_buffer: Vec::new(),
line_nr: 0,
}
}
}
pub type ReadAtomic<Int> = IntAtomic<Rc<str>, Int>;
pub type ReadStep<Int> = Step<Rc<str>, Int, Rc<str>>;
impl<Source, Int> ProofReader<Source, Int>
where
Source: io::BufRead,
Int: SignedIntValue,
{
pub fn next_step(&mut self) -> Result<Option<ReadStep<Int>>, Error> {
loop {
self.line_buffer.clear();
let read_bytes = self.source.read_until(b'\n', &mut self.line_buffer)?;
if read_bytes == 0 {
return Ok(None);
}
self.line_nr += 1;
let line_parser = parser::LineParser::new(
&self.line_buffer,
self.line_nr,
&mut self.identifiers,
&mut self.atomics,
);
let Some(proof_line) = line_parser.parse()? else {
continue;
};
return Ok(Some(proof_line));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Conclusion;
use crate::Deduction;
use crate::Inference;
use crate::IntComparison::*;
#[test]
fn inference_with_consequent() {
let source = r#"
a 1 [x1 >= 0]
a 2 [x2 >= 0]
i 2 1 0 2 c:1 l:inf_name
"#;
let a1 = IntAtomic {
name: Rc::from("x1".to_owned()),
comparison: GreaterEqual,
value: 0,
};
let a2 = IntAtomic {
name: Rc::from("x2".to_owned()),
comparison: GreaterEqual,
value: 0,
};
test_single_proof_line(
source,
Step::Inference(Inference {
constraint_id: NonZero::new(2).unwrap(),
premises: vec![a1],
consequent: Some(a2),
generated_by: Some(NonZero::new(1).unwrap()),
label: Some("inf_name".into()),
}),
);
}
#[test]
fn inference_with_consequent_hints_reversed() {
let source = r#"
a 1 [x1 >= 0]
a 2 [x2 >= 0]
i 2 1 0 2 l:inf_name c:1
"#;
let a1 = IntAtomic {
name: Rc::from("x1".to_owned()),
comparison: GreaterEqual,
value: 0,
};
let a2 = IntAtomic {
name: Rc::from("x2".to_owned()),
comparison: GreaterEqual,
value: 0,
};
test_single_proof_line(
source,
Step::Inference(Inference {
constraint_id: NonZero::new(2).unwrap(),
premises: vec![a1],
consequent: Some(a2),
generated_by: Some(NonZero::new(1).unwrap()),
label: Some("inf_name".into()),
}),
);
}
#[test]
fn inference_with_consequent_and_negative_atomic_values() {
let source = r#"
a 1 [x1 >= -1]
a 2 [x2 >= -10]
i 2 1 0 2 c:1 l:inf_name
"#;
let a1 = IntAtomic {
name: Rc::from("x1".to_owned()),
comparison: GreaterEqual,
value: -1,
};
let a2 = IntAtomic {
name: Rc::from("x2".to_owned()),
comparison: GreaterEqual,
value: -10,
};
test_single_proof_line(
source,
Step::Inference(Inference {
constraint_id: NonZero::new(2).unwrap(),
premises: vec![a1],
consequent: Some(a2),
generated_by: Some(NonZero::new(1).unwrap()),
label: Some("inf_name".into()),
}),
);
}
#[test]
fn inference_without_consequent() {
let source = r#"
a 1 [x1 >= 0]
a 2 [x2 >= 0]
i 2 1 -2 0 c:1 l:inf_name
"#;
let a1 = IntAtomic {
name: Rc::from("x1".to_owned()),
comparison: GreaterEqual,
value: 0,
};
let a2 = IntAtomic {
name: Rc::from("x2".to_owned()),
comparison: LessEqual,
value: -1,
};
test_single_proof_line(
source,
Step::Inference(Inference {
constraint_id: NonZero::new(2).unwrap(),
premises: vec![a1, a2],
consequent: None,
generated_by: Some(NonZero::new(1).unwrap()),
label: Some("inf_name".into()),
}),
);
}
#[test]
fn deduction_without_inferences() {
let source = r#"
a 1 [x1 >= 0]
a 2 [x2 >= 0]
n 2 1 -2 0
"#;
let a1 = IntAtomic {
name: Rc::from("x1".to_owned()),
comparison: GreaterEqual,
value: 0,
};
let a2 = IntAtomic {
name: Rc::from("x2".to_owned()),
comparison: LessEqual,
value: -1,
};
test_single_proof_line(
source,
Step::Deduction(Deduction {
constraint_id: NonZero::new(2).unwrap(),
premises: vec![a1, a2],
sequence: vec![],
}),
);
}
#[test]
fn deduction_with_inferences() {
let source = r#"
a 1 [x1 >= 0]
a 2 [x2 >= 0]
n 5 1 -2 0 1 3 2 4
"#;
let a1 = IntAtomic {
name: Rc::from("x1".to_owned()),
comparison: GreaterEqual,
value: 0,
};
let a2 = IntAtomic {
name: Rc::from("x2".to_owned()),
comparison: LessEqual,
value: -1,
};
test_single_proof_line(
source,
Step::Deduction(Deduction {
constraint_id: NonZero::new(5).unwrap(),
premises: vec![a1, a2],
sequence: vec![
NonZero::new(1).unwrap(),
NonZero::new(3).unwrap(),
NonZero::new(2).unwrap(),
NonZero::new(4).unwrap(),
],
}),
);
}
#[test]
fn conclusion_unsat() {
let source = r#"
c UNSAT
"#;
test_single_proof_line(source, Step::Conclusion(Conclusion::Unsat));
}
#[test]
fn conclusion_dual_bound() {
let source = r#"
a 1 [x1 >= 4]
c 1
"#;
let a1 = IntAtomic {
name: Rc::from("x1".to_owned()),
comparison: GreaterEqual,
value: 4,
};
test_single_proof_line(source, Step::Conclusion(Conclusion::DualBound(a1)));
}
fn test_single_proof_line(source: &str, expected_step: ReadStep<i32>) {
let mut reader = ProofReader::<_, i32>::new(source.as_bytes());
let parsed_step = reader
.next_step()
.expect("no error reading")
.expect("there is one proof step");
assert_eq!(expected_step, parsed_step);
}
}