1mod error;
83mod parser;
84
85use std::collections::BTreeMap;
86use std::collections::HashSet;
87use std::io;
88use std::num::NonZero;
89use std::rc::Rc;
90
91pub use error::*;
92
93use crate::IntAtomic;
94use crate::SignedIntValue;
95use crate::Step;
96
97#[derive(Debug)]
100pub struct ProofReader<Source, Int> {
101 source: Source,
103
104 atomics: BTreeMap<NonZero<u32>, IntAtomic<Rc<str>, Int>>,
106
107 identifiers: HashSet<Rc<str>>,
109
110 line_buffer: Vec<u8>,
112
113 line_nr: usize,
117}
118
119impl<Source, Int> ProofReader<Source, Int> {
120 pub fn new(source: Source) -> Self {
125 ProofReader {
126 source,
127 atomics: BTreeMap::default(),
128 identifiers: HashSet::default(),
129 line_buffer: Vec::new(),
130 line_nr: 0,
131 }
132 }
133}
134
135pub type ReadAtomic<Int> = IntAtomic<Rc<str>, Int>;
137pub type ReadStep<Int> = Step<Rc<str>, Int, Rc<str>>;
139
140impl<Source, Int> ProofReader<Source, Int>
141where
142 Source: io::BufRead,
143 Int: SignedIntValue,
144{
145 pub fn next_step(&mut self) -> Result<Option<ReadStep<Int>>, Error> {
149 loop {
150 self.line_buffer.clear();
151 let read_bytes = self.source.read_until(b'\n', &mut self.line_buffer)?;
152
153 if read_bytes == 0 {
155 return Ok(None);
156 }
157
158 self.line_nr += 1;
159
160 let line_parser = parser::LineParser::new(
161 &self.line_buffer,
162 self.line_nr,
163 &mut self.identifiers,
164 &mut self.atomics,
165 );
166
167 let Some(proof_line) = line_parser.parse()? else {
168 continue;
169 };
170
171 return Ok(Some(proof_line));
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::Conclusion;
180 use crate::Deduction;
181 use crate::Inference;
182 use crate::IntComparison::*;
183
184 #[test]
185 fn inference_with_consequent() {
186 let source = r#"
187 a 1 [x1 >= 0]
188 a 2 [x2 >= 0]
189 i 2 1 0 2 c:1 l:inf_name
190 "#;
191
192 let a1 = IntAtomic {
193 name: Rc::from("x1".to_owned()),
194 comparison: GreaterEqual,
195 value: 0,
196 };
197
198 let a2 = IntAtomic {
199 name: Rc::from("x2".to_owned()),
200 comparison: GreaterEqual,
201 value: 0,
202 };
203
204 test_single_proof_line(
205 source,
206 Step::Inference(Inference {
207 constraint_id: NonZero::new(2).unwrap(),
208 premises: vec![a1],
209 consequent: Some(a2),
210 generated_by: Some(NonZero::new(1).unwrap()),
211 label: Some("inf_name".into()),
212 }),
213 );
214 }
215
216 #[test]
217 fn inference_with_consequent_hints_reversed() {
218 let source = r#"
219 a 1 [x1 >= 0]
220 a 2 [x2 >= 0]
221 i 2 1 0 2 l:inf_name c:1
222 "#;
223
224 let a1 = IntAtomic {
225 name: Rc::from("x1".to_owned()),
226 comparison: GreaterEqual,
227 value: 0,
228 };
229
230 let a2 = IntAtomic {
231 name: Rc::from("x2".to_owned()),
232 comparison: GreaterEqual,
233 value: 0,
234 };
235
236 test_single_proof_line(
237 source,
238 Step::Inference(Inference {
239 constraint_id: NonZero::new(2).unwrap(),
240 premises: vec![a1],
241 consequent: Some(a2),
242 generated_by: Some(NonZero::new(1).unwrap()),
243 label: Some("inf_name".into()),
244 }),
245 );
246 }
247 #[test]
248 fn inference_with_consequent_and_negative_atomic_values() {
249 let source = r#"
250 a 1 [x1 >= -1]
251 a 2 [x2 >= -10]
252 i 2 1 0 2 c:1 l:inf_name
253 "#;
254
255 let a1 = IntAtomic {
256 name: Rc::from("x1".to_owned()),
257 comparison: GreaterEqual,
258 value: -1,
259 };
260
261 let a2 = IntAtomic {
262 name: Rc::from("x2".to_owned()),
263 comparison: GreaterEqual,
264 value: -10,
265 };
266
267 test_single_proof_line(
268 source,
269 Step::Inference(Inference {
270 constraint_id: NonZero::new(2).unwrap(),
271 premises: vec![a1],
272 consequent: Some(a2),
273 generated_by: Some(NonZero::new(1).unwrap()),
274 label: Some("inf_name".into()),
275 }),
276 );
277 }
278
279 #[test]
280 fn inference_without_consequent() {
281 let source = r#"
282 a 1 [x1 >= 0]
283 a 2 [x2 >= 0]
284 i 2 1 -2 0 c:1 l:inf_name
285 "#;
286
287 let a1 = IntAtomic {
288 name: Rc::from("x1".to_owned()),
289 comparison: GreaterEqual,
290 value: 0,
291 };
292
293 let a2 = IntAtomic {
294 name: Rc::from("x2".to_owned()),
295 comparison: LessEqual,
296 value: -1,
297 };
298
299 test_single_proof_line(
300 source,
301 Step::Inference(Inference {
302 constraint_id: NonZero::new(2).unwrap(),
303 premises: vec![a1, a2],
304 consequent: None,
305 generated_by: Some(NonZero::new(1).unwrap()),
306 label: Some("inf_name".into()),
307 }),
308 );
309 }
310
311 #[test]
312 fn deduction_without_inferences() {
313 let source = r#"
314 a 1 [x1 >= 0]
315 a 2 [x2 >= 0]
316 n 2 1 -2 0
317 "#;
318
319 let a1 = IntAtomic {
320 name: Rc::from("x1".to_owned()),
321 comparison: GreaterEqual,
322 value: 0,
323 };
324
325 let a2 = IntAtomic {
326 name: Rc::from("x2".to_owned()),
327 comparison: LessEqual,
328 value: -1,
329 };
330
331 test_single_proof_line(
332 source,
333 Step::Deduction(Deduction {
334 constraint_id: NonZero::new(2).unwrap(),
335 premises: vec![a1, a2],
336 sequence: vec![],
337 }),
338 );
339 }
340
341 #[test]
342 fn deduction_with_inferences() {
343 let source = r#"
344 a 1 [x1 >= 0]
345 a 2 [x2 >= 0]
346 n 5 1 -2 0 1 3 2 4
347 "#;
348
349 let a1 = IntAtomic {
350 name: Rc::from("x1".to_owned()),
351 comparison: GreaterEqual,
352 value: 0,
353 };
354
355 let a2 = IntAtomic {
356 name: Rc::from("x2".to_owned()),
357 comparison: LessEqual,
358 value: -1,
359 };
360
361 test_single_proof_line(
362 source,
363 Step::Deduction(Deduction {
364 constraint_id: NonZero::new(5).unwrap(),
365 premises: vec![a1, a2],
366 sequence: vec![
367 NonZero::new(1).unwrap(),
368 NonZero::new(3).unwrap(),
369 NonZero::new(2).unwrap(),
370 NonZero::new(4).unwrap(),
371 ],
372 }),
373 );
374 }
375
376 #[test]
377 fn conclusion_unsat() {
378 let source = r#"
379 c UNSAT
380 "#;
381
382 test_single_proof_line(source, Step::Conclusion(Conclusion::Unsat));
383 }
384
385 #[test]
386 fn conclusion_dual_bound() {
387 let source = r#"
388 a 1 [x1 >= 4]
389 c 1
390 "#;
391
392 let a1 = IntAtomic {
393 name: Rc::from("x1".to_owned()),
394 comparison: GreaterEqual,
395 value: 4,
396 };
397
398 test_single_proof_line(source, Step::Conclusion(Conclusion::DualBound(a1)));
399 }
400
401 fn test_single_proof_line(source: &str, expected_step: ReadStep<i32>) {
402 let mut reader = ProofReader::<_, i32>::new(source.as_bytes());
403
404 let parsed_step = reader
405 .next_step()
406 .expect("no error reading")
407 .expect("there is one proof step");
408
409 assert_eq!(expected_step, parsed_step);
410 }
411}