Next Generation WASM Microkernel Operating System
at trap_handler 193 lines 5.8 kB view raw
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}