use crate::input::Position;
use std::fmt::{Debug, Display};
pub trait MismatchElement: Display + Debug {}
impl<T> MismatchElement for T where T: Display + Debug {}
impl PartialEq for dyn MismatchElement {
fn eq(&self, other: &Self) -> bool {
self.to_string() == other.to_string()
}
}
#[derive(PartialEq, Debug)]
pub struct Mismatch {
expected: Option<Box<dyn MismatchElement>>,
found: Option<Box<dyn MismatchElement>>,
}
impl Mismatch {
pub fn new<E1, E2>(expected: E1, found: E2) -> Mismatch
where
E1: MismatchElement + 'static,
E2: MismatchElement + 'static,
{
let expected = Box::new(expected);
let found = Box::new(found);
Mismatch {
expected: Some(expected),
found: Some(found),
}
}
pub fn without_found<E>(expected: E) -> Mismatch
where
E: MismatchElement + 'static,
{
let expected = Box::new(expected);
Mismatch {
expected: Some(expected),
found: None,
}
}
pub fn without_expectation<E>(found: E) -> Mismatch
where
E: MismatchElement + 'static,
{
let found = Box::new(found);
Mismatch {
expected: None,
found: Some(found),
}
}
pub fn replace_expectation<E>(&mut self, expected: E)
where
E: MismatchElement + 'static,
{
self.expected.replace(Box::new(expected));
}
}
impl Display for Mismatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.expected {
Some(ref expected) => match self.found {
Some(ref found) => write!(f, "Expected: {expected}, found: {found}"),
None => write!(f, "Expected: {expected}"),
},
None => match self.found {
Some(ref found) => write!(f, "Found: {found}"),
None => panic!("Invalid error mismatch."),
},
}
}
}
#[derive(Debug, PartialEq)]
pub enum Error {
UnexpectedToken(Option<String>, Position, Option<Mismatch>),
EndOfInput(Option<Box<dyn MismatchElement>>),
NonConsumingLoop(Option<String>, Position),
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::UnexpectedToken(Some(source_name), pos, None) => {
write!(f, "Unexpected token at {}:{}.", source_name, pos)
}
Error::UnexpectedToken(Some(source_name), pos, Some(mismatch)) => {
write!(f, "Unexpected token at {source_name}:{pos}. {mismatch}")
}
Error::UnexpectedToken(None, pos, None) => write!(f, "Unexpected token at {pos}."),
Error::UnexpectedToken(None, pos, Some(mismatch)) => {
write!(f, "Unexpected token at {pos}. {mismatch}")
}
Error::EndOfInput(Some(expected)) => {
write!(f, "End of input reached when expected {}.", expected)
}
Error::EndOfInput(None) => write!(f, "End of input reached."),
Error::NonConsumingLoop(Some(source_name), pos) => {
write!(f, "Non-consuming parser loop at {source_name}:{pos}.")
}
Error::NonConsumingLoop(None, pos) => write!(f, "Non-consuming parser loop at {pos}."),
}
}
}
#[cfg(test)]
mod tests {
mod mismatch {
use crate::Mismatch;
#[test]
fn same_with_expectation_equal() {
let m1 = Mismatch::new("h", "p");
let m2 = Mismatch::new("h", "p");
assert_eq!(m1, m2);
}
#[test]
fn same_without_expectation_equal() {
let m1 = Mismatch::without_expectation("hello");
let m2 = Mismatch::without_expectation("hello");
assert_eq!(m1, m2);
}
#[test]
fn different_expectation_presence() {
let m1 = Mismatch::new("hello", "p");
let m2 = Mismatch::without_expectation("hello");
assert_ne!(m1, m2);
}
#[test]
fn different_expectation() {
let m1 = Mismatch::new("hello", "p");
let m2 = Mismatch::new("hallo", "p");
assert_ne!(m1, m2);
}
#[test]
fn different_found() {
let m1 = Mismatch::new("hello", "p");
let m2 = Mismatch::new("hello", "x");
assert_ne!(m1, m2);
}
}
}