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