extern crate alloc;
use {
crate::{utils::PathLike, FullLocation, Input, Location},
alloc::borrow::Cow,
core::{
convert::Infallible,
error::Error,
fmt::{Debug, Display, Formatter},
ops::Not,
},
std::{fs::read_to_string, io},
};
#[expect(unused)] use crate::Parser;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ParsingError<In, Reason = Infallible> {
pub rest: In,
pub reason: Option<Reason>,
}
impl<In: Input, Reason: Display> Display for ParsingError<In, Reason> {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
if let Some(reason) = &self.reason {
writeln!(f, "{reason}")?;
}
write!(
f,
"error source: {}{}",
self.rest[..self.rest.len().min(16)].escape_debug(),
if self.rest.len() > 16 { "..." } else { "" }
)?;
Ok(())
}
}
impl<In: Input, Reason: Error> Error for ParsingError<In, Reason> {}
impl<In, Reason> ParsingError<In, Reason> {
pub const fn new(rest: In, reason: Reason) -> Self {
Self {
rest,
reason: Some(reason),
}
}
pub const fn new_recoverable(rest: In) -> Self {
Self { rest, reason: None }
}
pub const fn is_recoverable(&self) -> bool {
self.reason.is_none()
}
pub fn reason<NewReason>(self, reason: NewReason) -> ParsingError<In, NewReason> {
ParsingError {
reason: Some(reason),
rest: self.rest,
}
}
#[must_use]
pub fn or_reason(self, reason: Reason) -> Self {
Self {
reason: self.reason.or(Some(reason)),
rest: self.rest,
}
}
#[must_use]
pub fn or_reason_if_nonempty(self, reason: Reason) -> Self
where
In: Input,
{
Self {
reason: self
.reason
.or_else(|| self.rest.is_empty().not().then_some(reason)),
rest: self.rest,
}
}
pub fn map_reason<NewReason>(
self,
f: impl FnOnce(Reason) -> NewReason,
) -> ParsingError<In, NewReason> {
ParsingError {
reason: self.reason.map(f),
rest: self.rest,
}
}
#[allow(unreachable_code)]
pub fn adapt_reason<NewReason>(self) -> ParsingError<In, NewReason>
where
Infallible: From<Reason>,
{
ParsingError {
reason: self.reason.map(|x| match Infallible::from(x) {}),
rest: self.rest,
}
}
pub fn with_src_loc<'a>(
self,
path: impl PathLike<'a>,
src: &'a str,
) -> FullParsingError<'a, Reason>
where
In: Input,
{
FullParsingError {
loc: Location::find_saturating(self.rest.as_ptr(), src).with_path(path),
reason: self.reason,
src: src.into(),
}
}
#[cfg(feature = "std")]
pub fn with_file_loc<'a>(
self,
path: impl PathLike<'a>,
) -> io::Result<FullParsingError<'a, Reason>>
where
In: Input,
{
let path = path.into_path();
let src = read_to_string(&path)?;
Ok(FullParsingError {
loc: Location::find_saturating(self.rest.as_ptr(), &src).with_path(path),
reason: self.reason,
src: src.into(),
})
}
}
#[derive(Debug, Clone)]
pub struct FullParsingError<'a, Reason> {
pub loc: FullLocation<'a>,
pub reason: Option<Reason>,
pub src: Cow<'a, str>,
}
impl<Reason: Display> Display for FullParsingError<'_, Reason> {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
if let Some(reason) = &self.reason {
writeln!(f, "{reason}")?;
}
writeln!(f, "--> {}", self.loc)?;
let line = self
.src
.lines()
.nth(self.loc.loc.line.get() as usize - 1)
.ok_or(core::fmt::Error)?;
let line_col_off = self.loc.loc.line.ilog10() as usize + 1;
writeln!(f, "{:line_col_off$} |", "")?;
writeln!(f, "{:line_col_off$} | {line}", self.loc.loc.line)?;
write!(
f,
"{:line_col_off$} | {:>2$}",
"",
'^',
self.loc.loc.col as usize + 1
)?;
Ok(())
}
}
impl<Reason: Error> Error for FullParsingError<'_, Reason> {}
impl<Reason> FullParsingError<'_, Reason> {
pub fn own(self) -> FullParsingError<'static, Reason> {
FullParsingError {
loc: self.loc.own(),
src: self.src.into_owned().into(),
..self
}
}
}
pub type ParsingResult<In, T, Reason = Infallible> =
core::result::Result<(In, T), ParsingError<In, Reason>>;