/src/wasm-tools/crates/wast/src/error.rs
Line | Count | Source |
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 | 7.04k | pub(crate) fn lex(span: Span, content: &str, kind: LexError) -> Error { |
44 | 7.04k | let mut ret = Error { |
45 | 7.04k | inner: Box::new(ErrorInner { |
46 | 7.04k | text: None, |
47 | 7.04k | file: None, |
48 | 7.04k | span, |
49 | 7.04k | kind: ErrorKind::Lex(kind), |
50 | 7.04k | }), |
51 | 7.04k | }; |
52 | 7.04k | ret.set_text(content); |
53 | 7.04k | ret |
54 | 7.04k | } |
55 | | |
56 | 2.23k | pub(crate) fn parse(span: Span, content: &str, message: String) -> Error { |
57 | 2.23k | let mut ret = Error { |
58 | 2.23k | inner: Box::new(ErrorInner { |
59 | 2.23k | text: None, |
60 | 2.23k | file: None, |
61 | 2.23k | span, |
62 | 2.23k | kind: ErrorKind::Custom(message), |
63 | 2.23k | }), |
64 | 2.23k | }; |
65 | 2.23k | ret.set_text(content); |
66 | 2.23k | ret |
67 | 2.23k | } |
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 | 1.02k | pub fn new(span: Span, message: String) -> Error { |
75 | 1.02k | Error { |
76 | 1.02k | inner: Box::new(ErrorInner { |
77 | 1.02k | text: None, |
78 | 1.02k | file: None, |
79 | 1.02k | span, |
80 | 1.02k | kind: ErrorKind::Custom(message), |
81 | 1.02k | }), |
82 | 1.02k | } |
83 | 1.02k | } |
84 | | |
85 | | /// Return the `Span` for this error. |
86 | 0 | pub fn span(&self) -> Span { |
87 | 0 | self.inner.span |
88 | 0 | } |
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 | 11.3k | pub fn set_text(&mut self, contents: &str) { |
97 | 11.3k | if self.inner.text.is_some() { |
98 | 1.99k | return; |
99 | 9.37k | } |
100 | 9.37k | self.inner.text = Some(Text::new(contents, self.inner.span)); |
101 | 11.3k | } |
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 | 0 | pub fn set_path(&mut self, path: &Path) { |
109 | 0 | if self.inner.file.is_some() { |
110 | 0 | return; |
111 | 0 | } |
112 | 0 | self.inner.file = Some(path.to_path_buf()); |
113 | 0 | } |
114 | | |
115 | | /// Returns the underlying `LexError`, if any, that describes this error. |
116 | 0 | pub fn lex_error(&self) -> Option<&LexError> { |
117 | 0 | match &self.inner.kind { |
118 | 0 | ErrorKind::Lex(e) => Some(e), |
119 | 0 | _ => None, |
120 | | } |
121 | 0 | } |
122 | | |
123 | | /// Returns the underlying message, if any, that describes this error. |
124 | 0 | pub fn message(&self) -> String { |
125 | 0 | match &self.inner.kind { |
126 | 0 | ErrorKind::Lex(e) => e.to_string(), |
127 | 0 | ErrorKind::Custom(e) => e.clone(), |
128 | | } |
129 | 0 | } |
130 | | } |
131 | | |
132 | | impl fmt::Display for Error { |
133 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
134 | 0 | let err = match &self.inner.kind { |
135 | 0 | ErrorKind::Lex(e) => e as &dyn fmt::Display, |
136 | 0 | ErrorKind::Custom(e) => e as &dyn fmt::Display, |
137 | | }; |
138 | 0 | let text = match &self.inner.text { |
139 | 0 | Some(text) => text, |
140 | | None => { |
141 | 0 | return write!(f, "{err} at byte offset {}", self.inner.span.offset); |
142 | | } |
143 | | }; |
144 | 0 | let file = self |
145 | 0 | .inner |
146 | 0 | .file |
147 | 0 | .as_ref() |
148 | 0 | .and_then(|p| p.to_str()) |
149 | 0 | .unwrap_or("<anon>"); |
150 | 0 | let col = text.col + 1; |
151 | 0 | let line = text.line + 1; |
152 | | |
153 | | // If the column is too big skip the fancy rendering below and just |
154 | | // print the raw error. |
155 | 0 | if col > 500 { |
156 | 0 | return write!(f, "{err} at {file}:{line}:{col}"); |
157 | 0 | } |
158 | 0 | write!( |
159 | 0 | f, |
160 | | "\ |
161 | | {err} |
162 | | --> {file}:{line}:{col} |
163 | | | |
164 | | {line:4} | {text} |
165 | | | {marker:>0$}", |
166 | | col, |
167 | | text = text.snippet, |
168 | | marker = "^", |
169 | | ) |
170 | 0 | } |
171 | | } |
172 | | |
173 | | impl std::error::Error for Error {} |
174 | | |
175 | | impl Text { |
176 | 9.37k | fn new(content: &str, span: Span) -> Text { |
177 | 9.37k | let (line, col) = span.linecol_in(content); |
178 | 9.37k | let contents = content.lines().nth(line).unwrap_or(""); |
179 | 9.37k | let mut snippet = String::new(); |
180 | 47.0M | for ch in contents.chars() { |
181 | 47.0M | match ch { |
182 | | // Replace tabs with spaces to render consistently |
183 | 2.62M | '\t' => { |
184 | 2.62M | snippet.push_str(" "); |
185 | 2.62M | } |
186 | | // these codepoints change how text is rendered so for clarity |
187 | | // in error messages they're dropped. |
188 | | '\u{202a}' | '\u{202b}' | '\u{202d}' | '\u{202e}' | '\u{2066}' | '\u{2067}' |
189 | 9.19k | | '\u{2068}' | '\u{206c}' | '\u{2069}' => {} |
190 | | |
191 | 44.4M | c => snippet.push(c), |
192 | | } |
193 | | } |
194 | | // Use the `unicode-width` crate to figure out how wide the snippet, up |
195 | | // to our "column", actually is. That'll tell us how many spaces to |
196 | | // place before the `^` character that points at the problem |
197 | 9.37k | let col = snippet.get(..col).map(|s| s.width()).unwrap_or(col); |
198 | 9.37k | Text { line, col, snippet } |
199 | 9.37k | } |
200 | | } |