1use alloc::{
2 format,
3 string::{String, ToString},
4 vec,
5 vec::Vec,
6};
7
8use crate::{ast, hir};
9
10#[non_exhaustive]
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum Error {
17 Parse(ast::Error),
20 Translate(hir::Error),
23}
24
25impl From<ast::Error> for Error {
26 fn from(err: ast::Error) -> Error {
27 Error::Parse(err)
28 }
29}
30
31impl From<hir::Error> for Error {
32 fn from(err: hir::Error) -> Error {
33 Error::Translate(err)
34 }
35}
36
37#[cfg(feature = "std")]
38impl std::error::Error for Error {}
39
40impl core::fmt::Display for Error {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 match *self {
43 Error::Parse(ref x) => x.fmt(f),
44 Error::Translate(ref x) => x.fmt(f),
45 }
46 }
47}
48
49#[derive(Debug)]
55pub struct Formatter<'e, E> {
56 pattern: &'e str,
58 err: &'e E,
60 span: &'e ast::Span,
62 aux_span: Option<&'e ast::Span>,
65}
66
67impl<'e> From<&'e ast::Error> for Formatter<'e, ast::ErrorKind> {
68 fn from(err: &'e ast::Error) -> Self {
69 Formatter {
70 pattern: err.pattern(),
71 err: err.kind(),
72 span: err.span(),
73 aux_span: err.auxiliary_span(),
74 }
75 }
76}
77
78impl<'e> From<&'e hir::Error> for Formatter<'e, hir::ErrorKind> {
79 fn from(err: &'e hir::Error) -> Self {
80 Formatter {
81 pattern: err.pattern(),
82 err: err.kind(),
83 span: err.span(),
84 aux_span: None,
85 }
86 }
87}
88
89impl<'e, E: core::fmt::Display> core::fmt::Display for Formatter<'e, E> {
90 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
91 let spans = Spans::from_formatter(self);
92 if self.pattern.contains('\n') {
93 let divider = repeat_char('~', 79);
94
95 writeln!(f, "regex parse error:")?;
96 writeln!(f, "{}", divider)?;
97 let notated = spans.notate();
98 write!(f, "{}", notated)?;
99 writeln!(f, "{}", divider)?;
100 if !spans.multi_line.is_empty() {
103 let mut notes = vec![];
104 for span in &spans.multi_line {
105 notes.push(format!(
106 "on line {} (column {}) through line {} (column {})",
107 span.start.line,
108 span.start.column,
109 span.end.line,
110 span.end.column - 1
111 ));
112 }
113 writeln!(f, "{}", notes.join("\n"))?;
114 }
115 write!(f, "error: {}", self.err)?;
116 } else {
117 writeln!(f, "regex parse error:")?;
118 let notated = Spans::from_formatter(self).notate();
119 write!(f, "{}", notated)?;
120 write!(f, "error: {}", self.err)?;
121 }
122 Ok(())
123 }
124}
125
126struct Spans<'p> {
135 pattern: &'p str,
137 line_number_width: usize,
143 by_line: Vec<Vec<ast::Span>>,
148 multi_line: Vec<ast::Span>,
152}
153
154impl<'p> Spans<'p> {
155 fn from_formatter<'e, E: core::fmt::Display>(
157 fmter: &'p Formatter<'e, E>,
158 ) -> Spans<'p> {
159 let mut line_count = fmter.pattern.lines().count();
160 if fmter.pattern.ends_with('\n') {
164 line_count += 1;
165 }
166 let line_number_width =
167 if line_count <= 1 { 0 } else { line_count.to_string().len() };
168 let mut spans = Spans {
169 pattern: &fmter.pattern,
170 line_number_width,
171 by_line: vec![vec![]; line_count],
172 multi_line: vec![],
173 };
174 spans.add(fmter.span.clone());
175 if let Some(span) = fmter.aux_span {
176 spans.add(span.clone());
177 }
178 spans
179 }
180
181 fn add(&mut self, span: ast::Span) {
183 if span.is_one_line() {
186 let i = span.start.line - 1; self.by_line[i].push(span);
188 self.by_line[i].sort();
189 } else {
190 self.multi_line.push(span);
191 self.multi_line.sort();
192 }
193 }
194
195 fn notate(&self) -> String {
198 let mut notated = String::new();
199 for (i, line) in self.pattern.lines().enumerate() {
200 if self.line_number_width > 0 {
201 notated.push_str(&self.left_pad_line_number(i + 1));
202 notated.push_str(": ");
203 } else {
204 notated.push_str(" ");
205 }
206 notated.push_str(line);
207 notated.push('\n');
208 if let Some(notes) = self.notate_line(i) {
209 notated.push_str(¬es);
210 notated.push('\n');
211 }
212 }
213 notated
214 }
215
216 fn notate_line(&self, i: usize) -> Option<String> {
221 let spans = &self.by_line[i];
222 if spans.is_empty() {
223 return None;
224 }
225 let mut notes = String::new();
226 for _ in 0..self.line_number_padding() {
227 notes.push(' ');
228 }
229 let mut pos = 0;
230 for span in spans {
231 for _ in pos..(span.start.column - 1) {
232 notes.push(' ');
233 pos += 1;
234 }
235 let note_len = span.end.column.saturating_sub(span.start.column);
236 for _ in 0..core::cmp::max(1, note_len) {
237 notes.push('^');
238 pos += 1;
239 }
240 }
241 Some(notes)
242 }
243
244 fn left_pad_line_number(&self, n: usize) -> String {
247 let n = n.to_string();
248 let pad = self.line_number_width.checked_sub(n.len()).unwrap();
249 let mut result = repeat_char(' ', pad);
250 result.push_str(&n);
251 result
252 }
253
254 fn line_number_padding(&self) -> usize {
260 if self.line_number_width == 0 {
261 4
262 } else {
263 2 + self.line_number_width
264 }
265 }
266}
267
268fn repeat_char(c: char, count: usize) -> String {
269 core::iter::repeat(c).take(count).collect()
270}
271
272#[cfg(test)]
273mod tests {
274 use alloc::string::ToString;
275
276 use crate::ast::parse::Parser;
277
278 fn assert_panic_message(pattern: &str, expected_msg: &str) {
279 let result = Parser::new().parse(pattern);
280 match result {
281 Ok(_) => {
282 panic!("regex should not have parsed");
283 }
284 Err(err) => {
285 assert_eq!(err.to_string(), expected_msg.trim());
286 }
287 }
288 }
289
290 #[test]
292 fn regression_464() {
293 let err = Parser::new().parse("a{\n").unwrap_err();
294 assert!(!err.to_string().is_empty());
296 }
297
298 #[test]
300 fn repetition_quantifier_expects_a_valid_decimal() {
301 assert_panic_message(
302 r"\\u{[^}]*}",
303 r#"
304regex parse error:
305 \\u{[^}]*}
306 ^
307error: repetition quantifier expects a valid decimal
308"#,
309 );
310 }
311}