1mod error;
83mod parser;
84
85use std::collections::BTreeMap;
86use std::io;
87use std::num::NonZero;
88use std::rc::Rc;
89
90use chumsky::Parser;
91pub use error::*;
92
93use crate::Conclusion;
94use crate::Deduction;
95use crate::Inference;
96use crate::IntAtomic;
97use crate::IntValue;
98use crate::Step;
99
100#[derive(Debug)]
103pub struct ProofReader<Source, Int> {
104 source: Source,
106
107 atomics: BTreeMap<NonZero<u32>, IntAtomic<Rc<str>, Int>>,
109
110 line_buffer: String,
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 line_buffer: String::new(),
129 line_nr: 0,
130 }
131 }
132
133 fn map_atomic_ids(&self, ids: Vec<NonZero<i32>>) -> Result<Vec<IntAtomic<Rc<str>, Int>>, Error>
134 where
135 Int: IntValue,
136 {
137 ids.into_iter()
138 .map(|code| self.map_atomic_id(code))
139 .collect()
140 }
141
142 fn map_atomic_id(&self, id: NonZero<i32>) -> Result<IntAtomic<Rc<str>, Int>, Error>
143 where
144 Int: IntValue,
145 {
146 self.atomics
147 .get(&id.unsigned_abs())
148 .cloned()
149 .map(|atomic| if id.is_negative() { !atomic } else { atomic })
150 .ok_or(Error::UndefinedAtomic {
151 line: self.line_nr,
152 code: id,
153 })
154 }
155}
156
157pub type ReadAtomic<Int> = IntAtomic<Rc<str>, Int>;
159pub type ReadStep<Int> = Step<Rc<str>, Int, Rc<str>>;
161
162impl<Source, Int> ProofReader<Source, Int>
163where
164 Source: io::BufRead,
165 Int: IntValue,
166{
167 pub fn next_step(&mut self) -> Result<Option<ReadStep<Int>>, Error> {
171 loop {
172 self.line_buffer.clear();
173 let read_bytes = self.source.read_line(&mut self.line_buffer)?;
174
175 if read_bytes == 0 {
177 return Ok(None);
178 }
179
180 self.line_nr += 1;
181
182 let trimmed_line = self.line_buffer.trim();
184
185 if trimmed_line.is_empty() {
187 continue;
188 }
189
190 let proof_line: parser::ProofLine<'_, Int> = parser::proof_line()
191 .parse(trimmed_line)
192 .into_result()
193 .map_err(|errs| {
194 assert_eq!(
195 errs.len(),
196 1,
197 "since we do no recovery, any error will terminate the parsing immediately"
198 );
199
200 let reason = format!("{}", errs[0]);
201 let span = errs[0].span();
202
203 Error::parse_error(self.line_nr, reason, *span)
204 })?;
205
206 match proof_line {
207 parser::ProofLine::AtomDefinition(atomic_id, atomic) => {
208 let IntAtomic {
209 name,
210 comparison,
211 value,
212 } = atomic;
213
214 let _ = self.atomics.insert(
215 atomic_id,
216 IntAtomic {
217 name: name.into(),
218 comparison,
219 value,
220 },
221 );
222 }
223
224 parser::ProofLine::Inference {
225 constraint_id,
226 premises,
227 consequent,
228 generated_by,
229 label,
230 } => {
231 let inference = Inference {
232 constraint_id,
233 premises: self.map_atomic_ids(premises)?,
234 consequent: consequent.map(|c| self.map_atomic_id(c)).transpose()?,
235 generated_by,
236 label: label.map(|label| label.into()),
237 };
238
239 return Ok(Some(Step::Inference(inference)));
240 }
241
242 parser::ProofLine::Deduction {
243 constraint_id,
244 premises,
245 sequence,
246 } => {
247 let deduction = Deduction {
248 constraint_id,
249 premises: self.map_atomic_ids(premises)?,
250 sequence,
251 };
252
253 return Ok(Some(Step::Deduction(deduction)));
254 }
255
256 parser::ProofLine::Conclusion(parser::ProofLineConclusion::Unsat) => {
257 return Ok(Some(Step::Conclusion(Conclusion::Unsat)));
258 }
259
260 parser::ProofLine::Conclusion(parser::ProofLineConclusion::DualBound(code)) => {
261 let bound = self.map_atomic_id(code)?;
262 return Ok(Some(Step::Conclusion(Conclusion::DualBound(bound))));
263 }
264 }
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::IntComparison::*;
273
274 #[test]
275 fn inference_with_consequent() {
276 let source = r#"
277 a 1 [x1 >= 0]
278 a 2 [x2 >= 0]
279 i 2 1 0 2 c:1 l:inf_name
280 "#;
281
282 let a1 = IntAtomic {
283 name: Rc::from("x1".to_owned()),
284 comparison: GreaterEqual,
285 value: 0,
286 };
287
288 let a2 = IntAtomic {
289 name: Rc::from("x2".to_owned()),
290 comparison: GreaterEqual,
291 value: 0,
292 };
293
294 test_single_proof_line(
295 source,
296 Step::Inference(Inference {
297 constraint_id: NonZero::new(2).unwrap(),
298 premises: vec![a1],
299 consequent: Some(a2),
300 generated_by: Some(NonZero::new(1).unwrap()),
301 label: Some("inf_name".into()),
302 }),
303 );
304 }
305
306 #[test]
307 fn inference_with_consequent_and_negative_atomic_values() {
308 let source = r#"
309 a 1 [x1 >= -1]
310 a 2 [x2 >= -10]
311 i 2 1 0 2 c:1 l:inf_name
312 "#;
313
314 let a1 = IntAtomic {
315 name: Rc::from("x1".to_owned()),
316 comparison: GreaterEqual,
317 value: -1,
318 };
319
320 let a2 = IntAtomic {
321 name: Rc::from("x2".to_owned()),
322 comparison: GreaterEqual,
323 value: -10,
324 };
325
326 test_single_proof_line(
327 source,
328 Step::Inference(Inference {
329 constraint_id: NonZero::new(2).unwrap(),
330 premises: vec![a1],
331 consequent: Some(a2),
332 generated_by: Some(NonZero::new(1).unwrap()),
333 label: Some("inf_name".into()),
334 }),
335 );
336 }
337
338 #[test]
339 fn inference_without_consequent() {
340 let source = r#"
341 a 1 [x1 >= 0]
342 a 2 [x2 >= 0]
343 i 2 1 -2 0 c:1 l:inf_name
344 "#;
345
346 let a1 = IntAtomic {
347 name: Rc::from("x1".to_owned()),
348 comparison: GreaterEqual,
349 value: 0,
350 };
351
352 let a2 = IntAtomic {
353 name: Rc::from("x2".to_owned()),
354 comparison: LessEqual,
355 value: -1,
356 };
357
358 test_single_proof_line(
359 source,
360 Step::Inference(Inference {
361 constraint_id: NonZero::new(2).unwrap(),
362 premises: vec![a1, a2],
363 consequent: None,
364 generated_by: Some(NonZero::new(1).unwrap()),
365 label: Some("inf_name".into()),
366 }),
367 );
368 }
369
370 #[test]
371 fn deduction_without_inferences() {
372 let source = r#"
373 a 1 [x1 >= 0]
374 a 2 [x2 >= 0]
375 n 2 1 -2 0
376 "#;
377
378 let a1 = IntAtomic {
379 name: Rc::from("x1".to_owned()),
380 comparison: GreaterEqual,
381 value: 0,
382 };
383
384 let a2 = IntAtomic {
385 name: Rc::from("x2".to_owned()),
386 comparison: LessEqual,
387 value: -1,
388 };
389
390 test_single_proof_line(
391 source,
392 Step::Deduction(Deduction {
393 constraint_id: NonZero::new(2).unwrap(),
394 premises: vec![a1, a2],
395 sequence: vec![],
396 }),
397 );
398 }
399
400 #[test]
401 fn deduction_with_inferences() {
402 let source = r#"
403 a 1 [x1 >= 0]
404 a 2 [x2 >= 0]
405 n 5 1 -2 0 1 3 2 4
406 "#;
407
408 let a1 = IntAtomic {
409 name: Rc::from("x1".to_owned()),
410 comparison: GreaterEqual,
411 value: 0,
412 };
413
414 let a2 = IntAtomic {
415 name: Rc::from("x2".to_owned()),
416 comparison: LessEqual,
417 value: -1,
418 };
419
420 test_single_proof_line(
421 source,
422 Step::Deduction(Deduction {
423 constraint_id: NonZero::new(5).unwrap(),
424 premises: vec![a1, a2],
425 sequence: vec![
426 NonZero::new(1).unwrap(),
427 NonZero::new(3).unwrap(),
428 NonZero::new(2).unwrap(),
429 NonZero::new(4).unwrap(),
430 ],
431 }),
432 );
433 }
434
435 #[test]
436 fn conclusion_unsat() {
437 let source = r#"
438 c UNSAT
439 "#;
440
441 test_single_proof_line(source, Step::Conclusion(Conclusion::Unsat));
442 }
443
444 #[test]
445 fn conclusion_dual_bound() {
446 let source = r#"
447 a 1 [x1 >= 4]
448 c 1
449 "#;
450
451 let a1 = IntAtomic {
452 name: Rc::from("x1".to_owned()),
453 comparison: GreaterEqual,
454 value: 4,
455 };
456
457 test_single_proof_line(source, Step::Conclusion(Conclusion::DualBound(a1)));
458 }
459
460 fn test_single_proof_line(source: &str, expected_step: ReadStep<i32>) {
461 let mut reader = ProofReader::<_, i32>::new(source.as_bytes());
462
463 let parsed_step = reader
464 .next_step()
465 .expect("no error reading")
466 .expect("there is one proof step");
467
468 assert_eq!(expected_step, parsed_step);
469 }
470}