1 | use crate::lexer::LexError; |
2 | use crate::token::Span; |
3 | use std::fmt; |
4 | use std::path::{Path, PathBuf}; |
5 | use unicode_width::UnicodeWidthStr; |
6 | |
7 | /// A convenience error type to tie together all the detailed errors produced by |
8 | /// this crate. |
9 | /// |
10 | /// This type can be created from a [`LexError`]. This also contains |
11 | /// storage for file/text information so a nice error can be rendered along the |
12 | /// same lines of rustc's own error messages (minus the color). |
13 | /// |
14 | /// This type is typically suitable for use in public APIs for consumers of this |
15 | /// crate. |
16 | #[derive (Debug)] |
17 | pub struct Error { |
18 | inner: Box<ErrorInner>, |
19 | } |
20 | |
21 | #[derive (Debug)] |
22 | struct ErrorInner { |
23 | text: Option<Text>, |
24 | file: Option<PathBuf>, |
25 | span: Span, |
26 | kind: ErrorKind, |
27 | } |
28 | |
29 | #[derive (Debug)] |
30 | struct Text { |
31 | line: usize, |
32 | col: usize, |
33 | snippet: String, |
34 | } |
35 | |
36 | #[derive (Debug)] |
37 | enum ErrorKind { |
38 | Lex(LexError), |
39 | Custom(String), |
40 | } |
41 | |
42 | impl Error { |
43 | pub(crate) fn lex(span: Span, content: &str, kind: LexError) -> Error { |
44 | let mut ret = Error { |
45 | inner: Box::new(ErrorInner { |
46 | text: None, |
47 | file: None, |
48 | span, |
49 | kind: ErrorKind::Lex(kind), |
50 | }), |
51 | }; |
52 | ret.set_text(content); |
53 | ret |
54 | } |
55 | |
56 | pub(crate) fn parse(span: Span, content: &str, message: String) -> Error { |
57 | let mut ret = Error { |
58 | inner: Box::new(ErrorInner { |
59 | text: None, |
60 | file: None, |
61 | span, |
62 | kind: ErrorKind::Custom(message), |
63 | }), |
64 | }; |
65 | ret.set_text(content); |
66 | ret |
67 | } |
68 | |
69 | /// Creates a new error with the given `message` which is targeted at the |
70 | /// given `span` |
71 | /// |
72 | /// Note that you'll want to ensure that `set_text` or `set_path` is called |
73 | /// on the resulting error to improve the rendering of the error message. |
74 | pub fn new(span: Span, message: String) -> Error { |
75 | Error { |
76 | inner: Box::new(ErrorInner { |
77 | text: None, |
78 | file: None, |
79 | span, |
80 | kind: ErrorKind::Custom(message), |
81 | }), |
82 | } |
83 | } |
84 | |
85 | /// Return the `Span` for this error. |
86 | pub fn span(&self) -> Span { |
87 | self.inner.span |
88 | } |
89 | |
90 | /// To provide a more useful error this function can be used to extract |
91 | /// relevant textual information about this error into the error itself. |
92 | /// |
93 | /// The `contents` here should be the full text of the original file being |
94 | /// parsed, and this will extract a sub-slice as necessary to render in the |
95 | /// `Display` implementation later on. |
96 | pub fn set_text(&mut self, contents: &str) { |
97 | if self.inner.text.is_some() { |
98 | return; |
99 | } |
100 | self.inner.text = Some(Text::new(contents, self.inner.span)); |
101 | } |
102 | |
103 | /// To provide a more useful error this function can be used to set |
104 | /// the file name that this error is associated with. |
105 | /// |
106 | /// The `path` here will be stored in this error and later rendered in the |
107 | /// `Display` implementation. |
108 | pub fn set_path(&mut self, path: &Path) { |
109 | if self.inner.file.is_some() { |
110 | return; |
111 | } |
112 | self.inner.file = Some(path.to_path_buf()); |
113 | } |
114 | |
115 | /// Returns the underlying `LexError`, if any, that describes this error. |
116 | pub fn lex_error(&self) -> Option<&LexError> { |
117 | match &self.inner.kind { |
118 | ErrorKind::Lex(e) => Some(e), |
119 | _ => None, |
120 | } |
121 | } |
122 | |
123 | /// Returns the underlying message, if any, that describes this error. |
124 | pub fn message(&self) -> String { |
125 | match &self.inner.kind { |
126 | ErrorKind::Lex(e) => e.to_string(), |
127 | ErrorKind::Custom(e) => e.clone(), |
128 | } |
129 | } |
130 | } |
131 | |
132 | impl fmt::Display for Error { |
133 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
134 | let err = match &self.inner.kind { |
135 | ErrorKind::Lex(e) => e as &dyn fmt::Display, |
136 | ErrorKind::Custom(e) => e as &dyn fmt::Display, |
137 | }; |
138 | let text = match &self.inner.text { |
139 | Some(text) => text, |
140 | None => { |
141 | return write!(f, " {} at byte offset {}" , err, self.inner.span.offset); |
142 | } |
143 | }; |
144 | let file = self |
145 | .inner |
146 | .file |
147 | .as_ref() |
148 | .and_then(|p| p.to_str()) |
149 | .unwrap_or("<anon>" ); |
150 | write!( |
151 | f, |
152 | "\ |
153 | {err} |
154 | --> {file}: {line}: {col} |
155 | | |
156 | {line:4} | {text} |
157 | | {marker:>0$}" , |
158 | text.col + 1, |
159 | file = file, |
160 | line = text.line + 1, |
161 | col = text.col + 1, |
162 | err = err, |
163 | text = text.snippet, |
164 | marker = "^" , |
165 | ) |
166 | } |
167 | } |
168 | |
169 | impl std::error::Error for Error {} |
170 | |
171 | impl Text { |
172 | fn new(content: &str, span: Span) -> Text { |
173 | let (line, col) = span.linecol_in(content); |
174 | let contents = content.lines().nth(line).unwrap_or("" ); |
175 | let mut snippet = String::new(); |
176 | for ch in contents.chars() { |
177 | match ch { |
178 | // Replace tabs with spaces to render consistently |
179 | ' \t' => { |
180 | snippet.push_str(" " ); |
181 | } |
182 | // these codepoints change how text is rendered so for clarity |
183 | // in error messages they're dropped. |
184 | ' \u{202a}' | ' \u{202b}' | ' \u{202d}' | ' \u{202e}' | ' \u{2066}' | ' \u{2067}' |
185 | | ' \u{2068}' | ' \u{206c}' | ' \u{2069}' => {} |
186 | |
187 | c => snippet.push(c), |
188 | } |
189 | } |
190 | // Use the `unicode-width` crate to figure out how wide the snippet, up |
191 | // to our "column", actually is. That'll tell us how many spaces to |
192 | // place before the `^` character that points at the problem |
193 | let col = snippet.get(..col).map(|s| s.width()).unwrap_or(col); |
194 | Text { line, col, snippet } |
195 | } |
196 | } |
197 | |