1extern crate alloc;
2
3use {
4 crate::{utils::PathLike, FullLocation, Input, Location},
5 core::{
6 convert::Infallible,
7 error::Error,
8 fmt::{Debug, Display, Formatter},
9 ops::Not,
10 },
11 std::{io, fs::read_to_string},
12 alloc::borrow::Cow,
13};
14
15#[derive(Debug, PartialEq, Eq, Clone, Copy)]
25pub struct ParsingError<In, Reason = Infallible> {
26 pub rest: In,
28 pub reason: Option<Reason>,
31}
32
33impl<In: Input, Reason: Display> Display for ParsingError<In, Reason> {
34 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
35 if let Some(reason) = &self.reason {
36 writeln!(f, "{reason}")?;
37 }
38 write!(
39 f,
40 "error source: {}{}",
41 self.rest[..self.rest.len().min(16)].escape_debug(),
42 if self.rest.len() > 16 { "..." } else { "" }
43 )?;
44 Ok(())
45 }
46}
47
48impl<In: Input, Reason: Error> Error for ParsingError<In, Reason> {}
49
50impl<In, Reason> ParsingError<In, Reason> {
51 pub const fn new(rest: In, reason: Reason) -> Self {
53 Self {
54 rest,
55 reason: Some(reason),
56 }
57 }
58
59 pub const fn new_recoverable(rest: In) -> Self {
61 Self { rest, reason: None }
62 }
63
64 pub const fn is_recoverable(&self) -> bool {
66 self.reason.is_none()
67 }
68
69 pub fn reason<NewReason>(self, reason: NewReason) -> ParsingError<In, NewReason> {
71 ParsingError {
72 reason: Some(reason),
73 rest: self.rest,
74 }
75 }
76
77 #[must_use]
79 pub fn or_reason(self, reason: Reason) -> Self {
80 Self {
81 reason: self.reason.or(Some(reason)),
82 rest: self.rest,
83 }
84 }
85
86 #[must_use]
88 pub fn or_reason_if_nonempty(self, reason: Reason) -> Self
89 where
90 In: Input,
91 {
92 Self {
93 reason: self
94 .reason
95 .or_else(|| self.rest.is_empty().not().then_some(reason)),
96 rest: self.rest,
97 }
98 }
99
100 pub fn map_reason<NewReason>(
103 self,
104 f: impl FnOnce(Reason) -> NewReason,
105 ) -> ParsingError<In, NewReason> {
106 ParsingError {
107 reason: self.reason.map(f),
108 rest: self.rest,
109 }
110 }
111
112 #[allow(unreachable_code)]
115 pub fn adapt_reason<NewReason>(self) -> ParsingError<In, NewReason>
116 where
117 Infallible: From<Reason>,
118 {
119 ParsingError {
120 reason: self.reason.map(|x| match Infallible::from(x) {}),
121 rest: self.rest,
122 }
123 }
124
125 pub fn with_src_loc<'a>(
131 self,
132 path: impl PathLike<'a>,
133 src: &'a str,
134 ) -> FullParsingError<'a, Reason>
135 where
136 In: Input,
137 {
138 FullParsingError {
139 loc: Location::find_saturating(self.rest.as_ptr(), src).with_path(path),
140 reason: self.reason,
141 src: src.into(),
142 }
143 }
144
145 #[cfg(feature = "std")]
150 pub fn with_file_loc<'a>(self, path: impl PathLike<'a>) -> io::Result<FullParsingError<'a, Reason>>
151 where
152 In: Input,
153 {
154 let path = path.into_path();
155 let src = read_to_string(&path)?;
156 Ok(FullParsingError {
157 loc: Location::find_saturating(self.rest.as_ptr(), &src).with_path(path),
158 reason: self.reason,
159 src: src.into(),
160 })
161 }
162}
163
164#[derive(Debug, Clone)]
174pub struct FullParsingError<'a, Reason> {
175 pub loc: FullLocation<'a>,
177 pub reason: Option<Reason>,
181 pub src: Cow<'a, str>,
183}
184
185impl<Reason: Display> Display for FullParsingError<'_, Reason> {
186 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
187 if let Some(reason) = &self.reason {
188 writeln!(f, "{reason}")?;
189 }
190 writeln!(f, "--> {}", self.loc)?;
191 let line = self.src
192 .lines()
193 .nth(self.loc.loc.line.get() as usize - 1)
194 .ok_or(core::fmt::Error)?;
195 let line_col_off = self.loc.loc.line.ilog10() as usize + 1;
196 writeln!(f, "{:line_col_off$} |", "")?;
197 writeln!(f, "{:line_col_off$} | {line}", self.loc.loc.line)?;
198 write!(
199 f,
200 "{:line_col_off$} | {:>2$}",
201 "",
202 '^',
203 self.loc.loc.col as usize + 1
204 )?;
205 Ok(())
206 }
207}
208
209impl<Reason: Error> Error for FullParsingError<'_, Reason> {}
210
211impl<Reason> FullParsingError<'_, Reason> {
212 pub fn own(self) -> FullParsingError<'static, Reason> {
214 FullParsingError {
215 loc: self.loc.own(),
216 src: self.src.into_owned().into(),
217 ..self
218 }
219 }
220}
221
222pub type ParsingResult<In, T, Reason = Infallible> =
224 core::result::Result<(In, T), ParsingError<In, Reason>>;