drcp_format/reader/
mod.rs

1//! Proofs can be read using a [`ProofReader`]. It reads the proof line-by-line, and can be thought
2//! of as an Iterator over [`Step`].
3//!
4//! The reader does not do any form of validation of the proof. It is up to the consumer of this
5//! crate to ensure that constraint IDs are valid, that inference labels make sense, etc.
6//!
7//! ```
8//! use std::num::NonZero;
9//! use std::rc::Rc;
10//!
11//! use drcp_format::reader::ProofReader;
12//! use drcp_format::Conclusion;
13//! use drcp_format::Deduction;
14//! use drcp_format::Inference;
15//! use drcp_format::IntAtomic;
16//! use drcp_format::IntComparison::*;
17//! use drcp_format::Step;
18//!
19//! let source = r#"
20//!     a 1 [x1 >= 0]
21//!     a 2 [x2 >= 0]
22//!     i 2 1 0 2 c:1 l:inf_name
23//!     n 3 1 -2 0 2
24//!     c UNSAT
25//! "#;
26//!
27//! let a1 = IntAtomic {
28//!     name: Rc::from("x1".to_owned()),
29//!     comparison: GreaterEqual,
30//!     value: 0,
31//! };
32//!
33//! let a2 = IntAtomic {
34//!     name: Rc::from("x2".to_owned()),
35//!     comparison: GreaterEqual,
36//!     value: 0,
37//! };
38//!
39//! let mut reader = ProofReader::<_, i32>::new(source.as_bytes());
40//!
41//! let inference = reader
42//!     .next_step()
43//!     .expect("no error reading")
44//!     .expect("proof step exists");
45//!
46//! assert_eq!(
47//!     inference,
48//!     Step::Inference(Inference {
49//!         constraint_id: NonZero::new(2).unwrap(),
50//!         premises: vec![a1.clone()],
51//!         consequent: Some(a2.clone()),
52//!         generated_by: Some(NonZero::new(1).unwrap()),
53//!         label: Some("inf_name".into()),
54//!     })
55//! );
56//!
57//! let deduction = reader
58//!     .next_step()
59//!     .expect("no error reading")
60//!     .expect("proof step exists");
61//!
62//! assert_eq!(
63//!     deduction,
64//!     Step::Deduction(Deduction {
65//!         constraint_id: NonZero::new(3).unwrap(),
66//!         premises: vec![a1.clone(), !a2],
67//!         sequence: vec![NonZero::new(2).unwrap()],
68//!     })
69//! );
70//!
71//! let conclusion = reader
72//!     .next_step()
73//!     .expect("no error reading")
74//!     .expect("proof step exists");
75//!
76//! assert_eq!(conclusion, Step::Conclusion(Conclusion::Unsat));
77//!
78//! let eof = reader.next_step().expect("no error reading");
79//! assert_eq!(eof, None);
80//! ```
81
82mod 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/// A parser of DRCP proofs. See module documentation on [`crate::reader`] for examples on how to
101/// use it.
102#[derive(Debug)]
103pub struct ProofReader<Source, Int> {
104    /// The source of the proof.
105    source: Source,
106
107    /// The defined atomics that are used in the proof.
108    atomics: BTreeMap<NonZero<u32>, IntAtomic<Rc<str>, Int>>,
109
110    /// The buffer that holds the current line of `source`.
111    line_buffer: String,
112
113    /// The line number currently being processed in the proof.
114    ///
115    /// Used for error reporting.
116    line_nr: usize,
117}
118
119impl<Source, Int> ProofReader<Source, Int> {
120    /// Create a new [`ProofReader`].
121    ///
122    /// Note that [`ProofReader::next_step`] is only implemented for sources that implement
123    /// [`io::BufRead`]. It is up to the user to set up the buffered reader.
124    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
157/// The [`IntAtomic`] type that is produced by the [`ProofReader`].
158pub type ReadAtomic<Int> = IntAtomic<Rc<str>, Int>;
159/// The [`Step`] type that is produced by the [`ProofReader`].
160pub 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    /// Parse the next [`Step`] from the proof file.
168    ///
169    /// The end-of-file is signified by the value `Ok(None)` and is _not_ an error.
170    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            // Reading 0 bytes indicates we have reached the end of the file.
176            if read_bytes == 0 {
177                return Ok(None);
178            }
179
180            self.line_nr += 1;
181
182            // We should get rid of leading or trailing whitespace.
183            let trimmed_line = self.line_buffer.trim();
184
185            // If the line is empty, go on to the next line.
186            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}