| 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 |  | 
|---|