use nom::error::{ErrorKind, ParseError};
use nom::{Err, IResult};
use nom::{Offset, Parser};
use std::borrow::Cow;
use std::fmt::{self, Debug, Display, Write};
use std::iter;
pub use crate::parser::scan::ScanError;
#[derive(Debug)]
pub struct OutOfRangeError<const LOW: u8, const HIGH: u8>(pub(crate) u8);
impl<const LOW: u8, const HIGH: u8> Display for OutOfRangeError<LOW, HIGH> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "conversion failed, value {} is out of range {}..={}", self.0, LOW, HIGH)
}
}
impl<const LOW: u8, const HIGH: u8> std::error::Error for OutOfRangeError<LOW, HIGH> {}
#[derive(Clone, Debug, PartialEq)]
pub struct VerboseError<I> {
pub errors: Vec<(I, VerboseErrorKind)>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum VerboseErrorKind {
Context(Cow<'static, str>),
Nom(ErrorKind),
}
impl<I> ParseError<I> for VerboseError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
VerboseError {
errors: vec![(input, VerboseErrorKind::Nom(kind))],
}
}
fn append(input: I, kind: ErrorKind, mut other: Self) -> Self {
other.errors.push((input, VerboseErrorKind::Nom(kind)));
other
}
fn from_char(_input: I, _c: char) -> Self {
unimplemented!("chars don't really make sense for a binary parser")
}
}
pub trait ContextError<I>: Sized {
fn add_context(_input: I, _ctx: Cow<'static, str>, other: Self) -> Self;
fn new(_input: I, _ctx: Cow<'static, str>) -> Self;
}
impl<I> ContextError<I> for VerboseError<I> {
fn add_context(input: I, ctx: Cow<'static, str>, mut other: Self) -> Self {
other.errors.push((input, VerboseErrorKind::Context(ctx)));
other
}
fn new(input: I, ctx: Cow<'static, str>) -> Self {
VerboseError {
errors: vec![(input, VerboseErrorKind::Context(ctx))],
}
}
}
pub(crate) fn context<'i, I, E, F, O>(
context: impl Fn() -> Cow<'static, str>,
mut f: F,
) -> impl FnMut(I) -> IResult<I, O, E>
where
F: Parser<I, O, E>,
I: Clone,
E: ContextError<I> + 'i,
{
move |i: I| match f.parse(i.clone()) {
Ok(o) => Ok(o),
Err(Err::Incomplete(i)) => Err(Err::Incomplete(i)),
Err(Err::Error(e)) => Err(Err::Error(E::add_context(i, context(), e))),
Err(Err::Failure(e)) => Err(Err::Failure(E::add_context(i, context(), e))),
}
}
macro_rules! context {
( $parser: expr, $msg: literal $(,)? ) => {
$crate::error::context(move || ::std::borrow::Cow::Borrowed($msg), $parser)
};
( $parser: expr, $fmt: literal $(, $args: expr )+ $(,)? ) => {
$crate::error::context(move || ::std::borrow::Cow::Owned(::std::format!($fmt, $($args),+)), $parser)
};
( $parser: expr, $payload: expr $(,)? ) => {
$crate::error::context(move || ::std::borrow::Cow::Owned($payload.to_string()), $parser)
};
}
macro_rules! error {
( $input: expr, $msg: literal $(,)? ) => {
E::new($input, ::std::borrow::Cow::Borrowed($msg))
};
( $input: expr, $fmt: literal $(, $args: expr )+ $(,)? ) => {
E::new($input, ::std::borrow::Cow::Owned(::std::format!($fmt, $($args),+)))
};
( $input: expr, $payload: expr $(,)? ) => {
E::new($input, ::std::borrow::Cow::Owned($payload.to_string()))
};
}
macro_rules! bail {
($($tt:tt)*) => {
return ::std::result::Result::Err(::nom::Err::Error(error!($($tt)*)))
};
}
pub fn convert_error(
input: &[u8],
err: VerboseError<&[u8]>,
) -> String {
let mut result = String::new();
for (i, (substring, kind)) in err.errors.iter().enumerate() {
let offset = input.offset(substring);
if input.is_empty() {
use VerboseErrorKind::*;
match kind {
Context(s) => write!(&mut result, "{}: in {}, got empty input\n\n", i, s).unwrap(),
Nom(e) => write!(&mut result, "{}: in {:?}, got empty input\n\n", i, e).unwrap(),
}
} else {
let line_begin = offset - (offset % 16);
let line = {
let mut buf = String::new();
write!(&mut buf, "{:08x}:", line_begin).unwrap();
let line = input[line_begin..]
.iter()
.map(|&byte| Some(byte))
.chain(iter::repeat(None))
.take(16)
.enumerate();
for (i, byte) in line.into_iter() {
if i % 2 == 0 {
buf.push(' ');
}
if let Some(byte) = byte {
write!(&mut buf, "{:02x}", byte).unwrap();
} else {
buf.push_str(" ");
}
}
buf.push_str(" ");
for &byte in input[line_begin..].iter().take(16) {
if byte.is_ascii_graphic() || byte == b' ' {
buf.push(byte as char);
} else {
buf.push('.');
}
}
buf
};
const CARET: &str = "^---";
let caret_position = {
let line_offset = offset % 16;
10 + CARET.len() + (line_offset / 2) * 5 + (line_offset % 2) * 2
};
match kind {
VerboseErrorKind::Context(s) => write!(
&mut result,
"{i}: at offset {offset:#x}, {context}:\n\
{line}\n\
{caret:>column$}\n\n",
i = i,
offset = offset,
context = s,
line = line,
caret = CARET,
column = caret_position,
).unwrap(),
VerboseErrorKind::Nom(_) => {},
}
}
}
result
}