]>
Commit | Line | Data |
---|---|---|
94b46f34 XL |
1 | use std::cmp; |
2 | use std::error; | |
3 | use std::fmt; | |
4 | use std::result; | |
5 | ||
6 | use ast; | |
7 | use hir; | |
8 | ||
9 | /// A type alias for dealing with errors returned by this crate. | |
10 | pub type Result<T> = result::Result<T, Error>; | |
11 | ||
12 | /// This error type encompasses any error that can be returned by this crate. | |
13 | #[derive(Clone, Debug, Eq, PartialEq)] | |
14 | pub enum Error { | |
15 | /// An error that occurred while translating concrete syntax into abstract | |
16 | /// syntax (AST). | |
17 | Parse(ast::Error), | |
18 | /// An error that occurred while translating abstract syntax into a high | |
19 | /// level intermediate representation (HIR). | |
20 | Translate(hir::Error), | |
21 | /// Hints that destructuring should not be exhaustive. | |
22 | /// | |
23 | /// This enum may grow additional variants, so this makes sure clients | |
24 | /// don't count on exhaustive matching. (Otherwise, adding a new variant | |
25 | /// could break existing code.) | |
26 | #[doc(hidden)] | |
27 | __Nonexhaustive, | |
28 | } | |
29 | ||
30 | impl From<ast::Error> for Error { | |
31 | fn from(err: ast::Error) -> Error { | |
32 | Error::Parse(err) | |
33 | } | |
34 | } | |
35 | ||
36 | impl From<hir::Error> for Error { | |
37 | fn from(err: hir::Error) -> Error { | |
38 | Error::Translate(err) | |
39 | } | |
40 | } | |
41 | ||
42 | impl error::Error for Error { | |
43 | fn description(&self) -> &str { | |
44 | match *self { | |
45 | Error::Parse(ref x) => x.description(), | |
46 | Error::Translate(ref x) => x.description(), | |
47 | _ => unreachable!(), | |
48 | } | |
49 | } | |
50 | } | |
51 | ||
52 | impl fmt::Display for Error { | |
53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
54 | match *self { | |
55 | Error::Parse(ref x) => x.fmt(f), | |
56 | Error::Translate(ref x) => x.fmt(f), | |
57 | _ => unreachable!(), | |
58 | } | |
59 | } | |
60 | } | |
61 | ||
62 | /// A helper type for formatting nice error messages. | |
63 | /// | |
64 | /// This type is responsible for reporting regex parse errors in a nice human | |
65 | /// readable format. Most of its complexity is from interspersing notational | |
66 | /// markers pointing out the position where an error occurred. | |
67 | #[derive(Debug)] | |
68 | pub struct Formatter<'e, E: 'e> { | |
69 | /// The original regex pattern in which the error occurred. | |
70 | pattern: &'e str, | |
71 | /// The error kind. It must impl fmt::Display. | |
72 | err: &'e E, | |
73 | /// The primary span of the error. | |
74 | span: &'e ast::Span, | |
75 | /// An auxiliary and optional span, in case the error needs to point to | |
76 | /// two locations (e.g., when reporting a duplicate capture group name). | |
77 | aux_span: Option<&'e ast::Span>, | |
78 | } | |
79 | ||
80 | impl<'e> From<&'e ast::Error> for Formatter<'e, ast::ErrorKind> { | |
81 | fn from(err: &'e ast::Error) -> Self { | |
82 | Formatter { | |
83 | pattern: err.pattern(), | |
84 | err: err.kind(), | |
85 | span: err.span(), | |
86 | aux_span: err.auxiliary_span(), | |
87 | } | |
88 | } | |
89 | } | |
90 | ||
91 | impl<'e> From<&'e hir::Error> for Formatter<'e, hir::ErrorKind> { | |
92 | fn from(err: &'e hir::Error) -> Self { | |
93 | Formatter { | |
94 | pattern: err.pattern(), | |
95 | err: err.kind(), | |
96 | span: err.span(), | |
97 | aux_span: None, | |
98 | } | |
99 | } | |
100 | } | |
101 | ||
102 | impl<'e, E: fmt::Display> fmt::Display for Formatter<'e, E> { | |
103 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
104 | let spans = Spans::from_formatter(self); | |
105 | if self.pattern.contains('\n') { | |
106 | let divider = repeat_char('~', 79); | |
107 | ||
108 | try!(writeln!(f, "regex parse error:")); | |
109 | try!(writeln!(f, "{}", divider)); | |
110 | let notated = spans.notate(); | |
111 | try!(write!(f, "{}", notated)); | |
112 | try!(writeln!(f, "{}", divider)); | |
113 | // If we have error spans that cover multiple lines, then we just | |
114 | // note the line numbers. | |
115 | if !spans.multi_line.is_empty() { | |
116 | let mut notes = vec![]; | |
117 | for span in &spans.multi_line { | |
118 | notes.push(format!( | |
119 | "on line {} (column {}) through line {} (column {})", | |
120 | span.start.line, span.start.column, | |
121 | span.end.line, span.end.column - 1)); | |
122 | } | |
123 | try!(writeln!(f, "{}", notes.join("\n"))); | |
124 | } | |
125 | try!(write!(f, "error: {}", self.err)); | |
126 | } else { | |
127 | try!(writeln!(f, "regex parse error:")); | |
128 | let notated = Spans::from_formatter(self).notate(); | |
129 | try!(write!(f, "{}", notated)); | |
130 | try!(write!(f, "error: {}", self.err)); | |
131 | } | |
132 | Ok(()) | |
133 | } | |
134 | } | |
135 | ||
136 | /// This type represents an arbitrary number of error spans in a way that makes | |
137 | /// it convenient to notate the regex pattern. ("Notate" means "point out | |
138 | /// exactly where the error occurred in the regex pattern.") | |
139 | /// | |
140 | /// Technically, we can only ever have two spans given our current error | |
141 | /// structure. However, after toiling with a specific algorithm for handling | |
142 | /// two spans, it became obvious that an algorithm to handle an arbitrary | |
143 | /// number of spans was actually much simpler. | |
144 | struct Spans<'p> { | |
145 | /// The original regex pattern string. | |
146 | pattern: &'p str, | |
147 | /// The total width that should be used for line numbers. The width is | |
148 | /// used for left padding the line numbers for alignment. | |
149 | /// | |
150 | /// A value of `0` means line numbers should not be displayed. That is, | |
151 | /// the pattern is itself only one line. | |
152 | line_number_width: usize, | |
153 | /// All error spans that occur on a single line. This sequence always has | |
154 | /// length equivalent to the number of lines in `pattern`, where the index | |
155 | /// of the sequence represents a line number, starting at `0`. The spans | |
156 | /// in each line are sorted in ascending order. | |
157 | by_line: Vec<Vec<ast::Span>>, | |
158 | /// All error spans that occur over one or more lines. That is, the start | |
159 | /// and end position of the span have different line numbers. The spans are | |
160 | /// sorted in ascending order. | |
161 | multi_line: Vec<ast::Span>, | |
162 | } | |
163 | ||
164 | impl<'p> Spans<'p> { | |
165 | /// Build a sequence of spans from a formatter. | |
166 | fn from_formatter<'e, E: fmt::Display>( | |
167 | fmter: &'p Formatter<'e, E>, | |
168 | ) -> Spans<'p> { | |
169 | let mut line_count = fmter.pattern.lines().count(); | |
170 | // If the pattern ends with a `\n` literal, then our line count is | |
171 | // off by one, since a span can occur immediately after the last `\n`, | |
172 | // which is consider to be an additional line. | |
173 | if fmter.pattern.ends_with('\n') { | |
174 | line_count += 1; | |
175 | } | |
176 | let line_number_width = | |
177 | if line_count <= 1 { | |
178 | 0 | |
179 | } else { | |
180 | line_count.to_string().len() | |
181 | }; | |
182 | let mut spans = Spans { | |
183 | pattern: &fmter.pattern, | |
184 | line_number_width: line_number_width, | |
185 | by_line: vec![vec![]; line_count], | |
186 | multi_line: vec![], | |
187 | }; | |
188 | spans.add(fmter.span.clone()); | |
189 | if let Some(span) = fmter.aux_span { | |
190 | spans.add(span.clone()); | |
191 | } | |
192 | spans | |
193 | } | |
194 | ||
195 | /// Add the given span to this sequence, putting it in the right place. | |
196 | fn add(&mut self, span: ast::Span) { | |
197 | // This is grossly inefficient since we sort after each add, but right | |
198 | // now, we only ever add two spans at most. | |
199 | if span.is_one_line() { | |
200 | let i = span.start.line - 1; // because lines are 1-indexed | |
201 | self.by_line[i].push(span); | |
202 | self.by_line[i].sort(); | |
203 | } else { | |
204 | self.multi_line.push(span); | |
205 | self.multi_line.sort(); | |
206 | } | |
207 | } | |
208 | ||
209 | /// Notate the pattern string with carents (`^`) pointing at each span | |
210 | /// location. This only applies to spans that occur within a single line. | |
211 | fn notate(&self) -> String { | |
212 | let mut notated = String::new(); | |
213 | for (i, line) in self.pattern.lines().enumerate() { | |
214 | if self.line_number_width > 0 { | |
215 | notated.push_str(&self.left_pad_line_number(i + 1)); | |
216 | notated.push_str(": "); | |
217 | } else { | |
218 | notated.push_str(" "); | |
219 | } | |
220 | notated.push_str(line); | |
221 | notated.push('\n'); | |
222 | if let Some(notes) = self.notate_line(i) { | |
223 | notated.push_str(¬es); | |
224 | notated.push('\n'); | |
225 | } | |
226 | } | |
227 | notated | |
228 | } | |
229 | ||
230 | /// Return notes for the line indexed at `i` (zero-based). If there are no | |
231 | /// spans for the given line, then `None` is returned. Otherwise, an | |
232 | /// appropriately space padded string with correctly positioned `^` is | |
233 | /// returned, accounting for line numbers. | |
234 | fn notate_line(&self, i: usize) -> Option<String> { | |
235 | let spans = &self.by_line[i]; | |
236 | if spans.is_empty() { | |
237 | return None; | |
238 | } | |
239 | let mut notes = String::new(); | |
240 | for _ in 0..self.line_number_padding() { | |
241 | notes.push(' '); | |
242 | } | |
243 | let mut pos = 0; | |
244 | for span in spans { | |
245 | for _ in pos..(span.start.column - 1) { | |
246 | notes.push(' '); | |
247 | pos += 1; | |
248 | } | |
249 | let note_len = span.end.column.saturating_sub(span.start.column); | |
250 | for _ in 0..cmp::max(1, note_len) { | |
251 | notes.push('^'); | |
252 | pos += 1; | |
253 | } | |
254 | } | |
255 | Some(notes) | |
256 | } | |
257 | ||
258 | /// Left pad the given line number with spaces such that it is aligned with | |
259 | /// other line numbers. | |
260 | fn left_pad_line_number(&self, n: usize) -> String { | |
261 | let n = n.to_string(); | |
262 | let pad = self.line_number_width.checked_sub(n.len()).unwrap(); | |
263 | let mut result = repeat_char(' ', pad); | |
264 | result.push_str(&n); | |
265 | result | |
266 | } | |
267 | ||
268 | /// Return the line number padding beginning at the start of each line of | |
269 | /// the pattern. | |
270 | /// | |
271 | /// If the pattern is only one line, then this returns a fixed padding | |
272 | /// for visual indentation. | |
273 | fn line_number_padding(&self) -> usize { | |
274 | if self.line_number_width == 0 { | |
275 | 4 | |
276 | } else { | |
277 | 2 + self.line_number_width | |
278 | } | |
279 | } | |
280 | } | |
281 | ||
282 | fn repeat_char(c: char, count: usize) -> String { | |
283 | ::std::iter::repeat(c).take(count).collect() | |
284 | } | |
285 | ||
286 | #[cfg(test)] | |
287 | mod tests { | |
288 | use ast::parse::Parser; | |
289 | ||
290 | // See: https://github.com/rust-lang/regex/issues/464 | |
291 | #[test] | |
292 | fn regression_464() { | |
293 | let err = Parser::new().parse("a{\n").unwrap_err(); | |
294 | // This test checks that the error formatter doesn't panic. | |
295 | assert!(!err.to_string().is_empty()); | |
296 | } | |
297 | } |