use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::fmt::Display;
#[non_exhaustive]
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
pub enum Reason {
PredicateFailed,
NotFailed,
NotEndOfInput,
EndOfInput,
Mismatch(String),
Error(String),
Label(&'static str),
}
impl Reason {
fn weight(&self) -> usize {
match self {
Reason::PredicateFailed => 0,
Reason::NotFailed => 10,
Reason::NotEndOfInput => 20,
Reason::EndOfInput => 30,
Reason::Mismatch(_) => 40,
Reason::Error(_) => 50,
Reason::Label(_) => 60,
}
}
}
impl Display for Reason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Reason::PredicateFailed => {
write!(f, "predicate evaluated to false")
}
Reason::NotFailed => {
write!(f, "unexpected parser success")
}
Reason::NotEndOfInput => {
write!(f, "expected end of input")
}
Reason::EndOfInput => {
write!(f, "unexpected end of input")
}
Reason::Mismatch(s) => {
write!(f, "expected \"{}\"", s)
}
Reason::Error(e) => {
write!(f, "unexpected error: {}", e)
}
Reason::Label(l) => {
write!(f, "expected {}", l)
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParserFailure {
pub position: usize,
pub reason: Reason,
}
impl ParserFailure {
fn new(reason: Reason, position: usize) -> Self {
Self { position, reason }
}
}
impl Ord for ParserFailure {
fn cmp(&self, other: &Self) -> Ordering {
let res = self.position.cmp(&other.position);
if res == Ordering::Equal {
self.reason.weight().cmp(&other.reason.weight())
} else {
res
}
}
}
impl PartialOrd for ParserFailure {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Display for ParserFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} at position {}", self.reason, self.position)
}
}
#[derive(Debug)]
pub struct FailureLog {
heap: BinaryHeap<ParserFailure>,
}
impl Default for FailureLog {
fn default() -> Self {
Self::new()
}
}
impl FailureLog {
pub fn new() -> Self {
FailureLog {
heap: BinaryHeap::new(),
}
}
pub fn add(&mut self, reason: Reason, position: usize) {
self.heap.push(ParserFailure::new(reason, position));
}
pub fn pop(&mut self) -> Option<ParserFailure> {
self.heap.pop()
}
pub fn peek(&self) -> Option<&ParserFailure> {
self.heap.peek()
}
pub fn to_vec(self) -> Vec<ParserFailure> {
self.heap.into_sorted_vec()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_comparing_parser_failure() {
let a = ParserFailure::new(Reason::EndOfInput, 12);
let b = ParserFailure::new(Reason::EndOfInput, 1);
let c = ParserFailure::new(Reason::EndOfInput, 3);
let d = ParserFailure::new(Reason::EndOfInput, 12);
assert!(a > b);
assert!(c > b);
assert_eq!(a, d);
}
#[test]
fn test_parser_failure_reason_ordering() {
let mut log = FailureLog::new();
log.add(Reason::PredicateFailed, 1);
log.add(Reason::Label("digit"), 1);
log.add(Reason::EndOfInput, 1);
log.add(Reason::Mismatch("9".into()), 1);
log.add(Reason::NotFailed, 1);
log.add(Reason::Error("err".into()), 1);
log.add(Reason::NotEndOfInput, 1);
let label = log.pop().unwrap();
let err = log.pop().unwrap();
let mismatch = log.pop().unwrap();
let eoi = log.pop().unwrap();
let neoi = log.pop().unwrap();
let notfailed = log.pop().unwrap();
let predicate = log.pop().unwrap();
assert!(matches!(
label,
ParserFailure {
reason: Reason::Label(_),
..
}
));
assert!(matches!(
err,
ParserFailure {
reason: Reason::Error(_),
..
}
));
assert!(matches!(
mismatch,
ParserFailure {
reason: Reason::Mismatch(_),
..
}
));
assert!(matches!(
eoi,
ParserFailure {
reason: Reason::EndOfInput,
..
}
));
assert!(matches!(
neoi,
ParserFailure {
reason: Reason::NotEndOfInput,
..
}
));
assert!(matches!(
notfailed,
ParserFailure {
reason: Reason::NotFailed,
..
}
));
assert!(matches!(
predicate,
ParserFailure {
reason: Reason::PredicateFailed,
..
}
));
}
#[test]
fn test_parser_failure_ordering() {
let mut log = FailureLog::new();
log.add(Reason::NotFailed, 1);
log.add(Reason::NotFailed, 100);
log.add(Reason::NotFailed, 34);
let first = log.pop().unwrap();
let second = log.pop().unwrap();
let third = log.pop().unwrap();
assert!(matches!(first, ParserFailure { position: 100, .. }));
assert!(matches!(second, ParserFailure { position: 34, .. }));
assert!(matches!(third, ParserFailure { position: 1, .. }));
}
}