| 1 | use std::{fmt, ops::Range}; |
| 2 | |
| 3 | |
| 4 | /// An error signaling that a different kind of token was expected. Returned by |
| 5 | /// the various `TryFrom` impls. |
| 6 | #[derive (Debug, Clone, Copy)] |
| 7 | pub struct InvalidToken { |
| 8 | pub(crate) expected: TokenKind, |
| 9 | pub(crate) actual: TokenKind, |
| 10 | pub(crate) span: Span, |
| 11 | } |
| 12 | |
| 13 | impl InvalidToken { |
| 14 | /// Returns a token stream representing `compile_error!("msg");` where |
| 15 | /// `"msg"` is the output of `self.to_string()`. **Panics if called outside |
| 16 | /// of a proc-macro context!** |
| 17 | pub fn to_compile_error(&self) -> proc_macro::TokenStream { |
| 18 | use proc_macro::{Delimiter, Ident, Group, Punct, Spacing, TokenTree}; |
| 19 | |
| 20 | let span = match self.span { |
| 21 | Span::One(s) => s, |
| 22 | #[cfg (feature = "proc-macro2" )] |
| 23 | Span::Two(s) => s.unwrap(), |
| 24 | }; |
| 25 | let msg = self.to_string(); |
| 26 | let tokens = vec![ |
| 27 | TokenTree::from(Ident::new("compile_error" , span)), |
| 28 | TokenTree::from(Punct::new('!' , Spacing::Alone)), |
| 29 | TokenTree::from(Group::new( |
| 30 | Delimiter::Parenthesis, |
| 31 | TokenTree::from(proc_macro::Literal::string(&msg)).into(), |
| 32 | )), |
| 33 | ]; |
| 34 | |
| 35 | |
| 36 | tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect() |
| 37 | } |
| 38 | |
| 39 | /// Like [`to_compile_error`][Self::to_compile_error], but returns a token |
| 40 | /// stream from `proc_macro2` and does not panic outside of a proc-macro |
| 41 | /// context. |
| 42 | #[cfg (feature = "proc-macro2" )] |
| 43 | pub fn to_compile_error2(&self) -> proc_macro2::TokenStream { |
| 44 | use proc_macro2::{Delimiter, Ident, Group, Punct, Spacing, TokenTree}; |
| 45 | |
| 46 | let span = match self.span { |
| 47 | Span::One(s) => proc_macro2::Span::from(s), |
| 48 | Span::Two(s) => s, |
| 49 | }; |
| 50 | let msg = self.to_string(); |
| 51 | let tokens = vec![ |
| 52 | TokenTree::from(Ident::new("compile_error" , span)), |
| 53 | TokenTree::from(Punct::new('!' , Spacing::Alone)), |
| 54 | TokenTree::from(Group::new( |
| 55 | Delimiter::Parenthesis, |
| 56 | TokenTree::from(proc_macro2::Literal::string(&msg)).into(), |
| 57 | )), |
| 58 | ]; |
| 59 | |
| 60 | |
| 61 | tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect() |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | impl std::error::Error for InvalidToken {} |
| 66 | |
| 67 | impl fmt::Display for InvalidToken { |
| 68 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 69 | fn kind_desc(kind: TokenKind) -> &'static str { |
| 70 | match kind { |
| 71 | TokenKind::Punct => "a punctuation character" , |
| 72 | TokenKind::Ident => "an identifier" , |
| 73 | TokenKind::Group => "a group" , |
| 74 | TokenKind::Literal => "a literal" , |
| 75 | TokenKind::BoolLit => "a bool literal (`true` or `false`)" , |
| 76 | TokenKind::ByteLit => "a byte literal (e.g. `b'r')" , |
| 77 | TokenKind::ByteStringLit => r#"a byte string literal (e.g. `b"fox"`)"# , |
| 78 | TokenKind::CharLit => "a character literal (e.g. `'P'`)" , |
| 79 | TokenKind::FloatLit => "a float literal (e.g. `3.14`)" , |
| 80 | TokenKind::IntegerLit => "an integer literal (e.g. `27`)" , |
| 81 | TokenKind::StringLit => r#"a string literal (e.g. "Ferris")"# , |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | write!(f, "expected {}, but found {}" , kind_desc(self.expected), kind_desc(self.actual)) |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | #[derive (Debug, Clone, Copy, PartialEq, Eq)] |
| 90 | pub(crate) enum TokenKind { |
| 91 | Punct, |
| 92 | Ident, |
| 93 | Group, |
| 94 | Literal, |
| 95 | BoolLit, |
| 96 | ByteLit, |
| 97 | ByteStringLit, |
| 98 | CharLit, |
| 99 | FloatLit, |
| 100 | IntegerLit, |
| 101 | StringLit, |
| 102 | } |
| 103 | |
| 104 | /// Unfortunately, we have to deal with both cases. |
| 105 | #[derive (Debug, Clone, Copy)] |
| 106 | pub(crate) enum Span { |
| 107 | One(proc_macro::Span), |
| 108 | #[cfg (feature = "proc-macro2" )] |
| 109 | Two(proc_macro2::Span), |
| 110 | } |
| 111 | |
| 112 | impl From<proc_macro::Span> for Span { |
| 113 | fn from(src: proc_macro::Span) -> Self { |
| 114 | Self::One(src) |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | #[cfg (feature = "proc-macro2" )] |
| 119 | impl From<proc_macro2::Span> for Span { |
| 120 | fn from(src: proc_macro2::Span) -> Self { |
| 121 | Self::Two(src) |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /// Errors during parsing. |
| 126 | /// |
| 127 | /// This type should be seen primarily for error reporting and not for catching |
| 128 | /// specific cases. The span and error kind are not guaranteed to be stable |
| 129 | /// over different versions of this library, meaning that a returned error can |
| 130 | /// change from one version to the next. There are simply too many fringe cases |
| 131 | /// that are not easy to classify as a specific error kind. It depends entirely |
| 132 | /// on the specific parser code how an invalid input is categorized. |
| 133 | /// |
| 134 | /// Consider these examples: |
| 135 | /// - `'\` can be seen as |
| 136 | /// - invalid escape in character literal, or |
| 137 | /// - unterminated character literal. |
| 138 | /// - `'''` can be seen as |
| 139 | /// - empty character literal, or |
| 140 | /// - unescaped quote character in character literal. |
| 141 | /// - `0b64` can be seen as |
| 142 | /// - binary integer literal with invalid digit 6, or |
| 143 | /// - binary integer literal with invalid digit 4, or |
| 144 | /// - decimal integer literal with invalid digit b, or |
| 145 | /// - decimal integer literal 0 with unknown type suffix `b64`. |
| 146 | /// |
| 147 | /// If you want to see more if these examples, feel free to check out the unit |
| 148 | /// tests of this library. |
| 149 | /// |
| 150 | /// While this library does its best to emit sensible and precise errors, and to |
| 151 | /// keep the returned errors as stable as possible, full stability cannot be |
| 152 | /// guaranteed. |
| 153 | #[derive (Debug, Clone)] |
| 154 | pub struct ParseError { |
| 155 | pub(crate) span: Option<Range<usize>>, |
| 156 | pub(crate) kind: ParseErrorKind, |
| 157 | } |
| 158 | |
| 159 | impl ParseError { |
| 160 | /// Returns a span of this error, if available. **Note**: the returned span |
| 161 | /// might change in future versions of this library. See [the documentation |
| 162 | /// of this type][ParseError] for more information. |
| 163 | pub fn span(&self) -> Option<Range<usize>> { |
| 164 | self.span.clone() |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /// This is a free standing function instead of an associated one to reduce |
| 169 | /// noise around parsing code. There are lots of places that create errors, we |
| 170 | /// I wanna keep them as short as possible. |
| 171 | pub(crate) fn perr(span: impl SpanLike, kind: ParseErrorKind) -> ParseError { |
| 172 | ParseError { |
| 173 | span: span.into_span(), |
| 174 | kind, |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | pub(crate) trait SpanLike { |
| 179 | fn into_span(self) -> Option<Range<usize>>; |
| 180 | } |
| 181 | |
| 182 | impl SpanLike for Option<Range<usize>> { |
| 183 | #[inline (always)] |
| 184 | fn into_span(self) -> Option<Range<usize>> { |
| 185 | self |
| 186 | } |
| 187 | } |
| 188 | impl SpanLike for Range<usize> { |
| 189 | #[inline (always)] |
| 190 | fn into_span(self) -> Option<Range<usize>> { |
| 191 | Some(self) |
| 192 | } |
| 193 | } |
| 194 | impl SpanLike for usize { |
| 195 | #[inline (always)] |
| 196 | fn into_span(self) -> Option<Range<usize>> { |
| 197 | Some(self..self + 1) |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | |
| 202 | /// Kinds of errors. |
| 203 | #[derive (Debug, Clone, Copy, PartialEq, Eq)] |
| 204 | #[non_exhaustive ] |
| 205 | pub(crate) enum ParseErrorKind { |
| 206 | /// The input was an empty string |
| 207 | Empty, |
| 208 | |
| 209 | /// An unexpected char was encountered. |
| 210 | UnexpectedChar, |
| 211 | |
| 212 | /// Literal was not recognized. |
| 213 | InvalidLiteral, |
| 214 | |
| 215 | /// Input does not start with decimal digit when trying to parse an integer. |
| 216 | DoesNotStartWithDigit, |
| 217 | |
| 218 | /// A digit invalid for the specified integer base was found. |
| 219 | InvalidDigit, |
| 220 | |
| 221 | /// Integer literal does not contain any valid digits. |
| 222 | NoDigits, |
| 223 | |
| 224 | /// Exponent of a float literal does not contain any digits. |
| 225 | NoExponentDigits, |
| 226 | |
| 227 | /// An unknown escape code, e.g. `\b`. |
| 228 | UnknownEscape, |
| 229 | |
| 230 | /// A started escape sequence where the input ended before the escape was |
| 231 | /// finished. |
| 232 | UnterminatedEscape, |
| 233 | |
| 234 | /// An `\x` escape where the two digits are not valid hex digits. |
| 235 | InvalidXEscape, |
| 236 | |
| 237 | /// A string or character literal using the `\xNN` escape where `NN > 0x7F`. |
| 238 | NonAsciiXEscape, |
| 239 | |
| 240 | /// A `\u{...}` escape in a byte or byte string literal. |
| 241 | UnicodeEscapeInByteLiteral, |
| 242 | |
| 243 | /// A Unicode escape that does not start with a hex digit. |
| 244 | InvalidStartOfUnicodeEscape, |
| 245 | |
| 246 | /// A `\u{...}` escape that lacks the opening brace. |
| 247 | UnicodeEscapeWithoutBrace, |
| 248 | |
| 249 | /// In a `\u{...}` escape, a non-hex digit and non-underscore character was |
| 250 | /// found. |
| 251 | NonHexDigitInUnicodeEscape, |
| 252 | |
| 253 | /// More than 6 digits found in unicode escape. |
| 254 | TooManyDigitInUnicodeEscape, |
| 255 | |
| 256 | /// The value from a unicode escape does not represent a valid character. |
| 257 | InvalidUnicodeEscapeChar, |
| 258 | |
| 259 | /// A `\u{..` escape that is not terminated (lacks the closing brace). |
| 260 | UnterminatedUnicodeEscape, |
| 261 | |
| 262 | /// A character literal that's not terminated. |
| 263 | UnterminatedCharLiteral, |
| 264 | |
| 265 | /// A character literal that contains more than one character. |
| 266 | OverlongCharLiteral, |
| 267 | |
| 268 | /// An empty character literal, i.e. `''`. |
| 269 | EmptyCharLiteral, |
| 270 | |
| 271 | UnterminatedByteLiteral, |
| 272 | OverlongByteLiteral, |
| 273 | EmptyByteLiteral, |
| 274 | NonAsciiInByteLiteral, |
| 275 | |
| 276 | /// A `'` character was not escaped in a character or byte literal, or a `"` |
| 277 | /// character was not escaped in a string or byte string literal. |
| 278 | UnescapedSingleQuote, |
| 279 | |
| 280 | /// A \n, \t or \r raw character in a char or byte literal. |
| 281 | UnescapedSpecialWhitespace, |
| 282 | |
| 283 | /// When parsing a character, byte, string or byte string literal directly |
| 284 | /// and the input does not start with the corresponding quote character |
| 285 | /// (plus optional raw string prefix). |
| 286 | DoesNotStartWithQuote, |
| 287 | |
| 288 | /// Unterminated raw string literal. |
| 289 | UnterminatedRawString, |
| 290 | |
| 291 | /// String literal without a `"` at the end. |
| 292 | UnterminatedString, |
| 293 | |
| 294 | /// Invalid start for a string literal. |
| 295 | InvalidStringLiteralStart, |
| 296 | |
| 297 | /// Invalid start for a byte literal. |
| 298 | InvalidByteLiteralStart, |
| 299 | |
| 300 | InvalidByteStringLiteralStart, |
| 301 | |
| 302 | /// An literal `\r` character not followed by a `\n` character in a |
| 303 | /// (raw) string or byte string literal. |
| 304 | IsolatedCr, |
| 305 | |
| 306 | /// Literal suffix is not a valid identifier. |
| 307 | InvalidSuffix, |
| 308 | |
| 309 | /// Returned by `Float::parse` if an integer literal (no fractional nor |
| 310 | /// exponent part) is passed. |
| 311 | UnexpectedIntegerLit, |
| 312 | |
| 313 | /// Integer suffixes cannot start with `e` or `E` as this conflicts with the |
| 314 | /// grammar for float literals. |
| 315 | IntegerSuffixStartingWithE, |
| 316 | } |
| 317 | |
| 318 | impl std::error::Error for ParseError {} |
| 319 | |
| 320 | impl fmt::Display for ParseError { |
| 321 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 322 | use ParseErrorKind::*; |
| 323 | |
| 324 | let description = match self.kind { |
| 325 | Empty => "input is empty" , |
| 326 | UnexpectedChar => "unexpected character" , |
| 327 | InvalidLiteral => "invalid literal" , |
| 328 | DoesNotStartWithDigit => "number literal does not start with decimal digit" , |
| 329 | InvalidDigit => "integer literal contains a digit invalid for its base" , |
| 330 | NoDigits => "integer literal does not contain any digits" , |
| 331 | NoExponentDigits => "exponent of floating point literal does not contain any digits" , |
| 332 | UnknownEscape => "unknown escape" , |
| 333 | UnterminatedEscape => "unterminated escape: input ended too soon" , |
| 334 | InvalidXEscape => r"invalid `\x` escape: not followed by two hex digits" , |
| 335 | NonAsciiXEscape => r"`\x` escape in char/string literal exceed ASCII range" , |
| 336 | UnicodeEscapeInByteLiteral => r"`\u{...}` escape in byte (string) literal not allowed" , |
| 337 | InvalidStartOfUnicodeEscape => r"invalid start of `\u{...}` escape" , |
| 338 | UnicodeEscapeWithoutBrace => r"`Unicode \u{...}` escape without opening brace" , |
| 339 | NonHexDigitInUnicodeEscape => r"non-hex digit found in `\u{...}` escape" , |
| 340 | TooManyDigitInUnicodeEscape => r"more than six digits in `\u{...}` escape" , |
| 341 | InvalidUnicodeEscapeChar => r"value specified in `\u{...}` escape is not a valid char" , |
| 342 | UnterminatedUnicodeEscape => r"unterminated `\u{...}` escape" , |
| 343 | UnterminatedCharLiteral => "character literal is not terminated" , |
| 344 | OverlongCharLiteral => "character literal contains more than one character" , |
| 345 | EmptyCharLiteral => "empty character literal" , |
| 346 | UnterminatedByteLiteral => "byte literal is not terminated" , |
| 347 | OverlongByteLiteral => "byte literal contains more than one byte" , |
| 348 | EmptyByteLiteral => "empty byte literal" , |
| 349 | NonAsciiInByteLiteral => "non ASCII character in byte (string) literal" , |
| 350 | UnescapedSingleQuote => "character literal contains unescaped ' character" , |
| 351 | UnescapedSpecialWhitespace => r"unescaped newline (\n), tab (\t) or cr (\r) character" , |
| 352 | DoesNotStartWithQuote => "invalid start for char/byte/string literal" , |
| 353 | UnterminatedRawString => "unterminated raw (byte) string literal" , |
| 354 | UnterminatedString => "unterminated (byte) string literal" , |
| 355 | InvalidStringLiteralStart => "invalid start for string literal" , |
| 356 | InvalidByteLiteralStart => "invalid start for byte literal" , |
| 357 | InvalidByteStringLiteralStart => "invalid start for byte string literal" , |
| 358 | IsolatedCr => r"`\r` not immediately followed by `\n` in string" , |
| 359 | InvalidSuffix => "literal suffix is not a valid identifier" , |
| 360 | UnexpectedIntegerLit => "expected float literal, but found integer" , |
| 361 | IntegerSuffixStartingWithE => "integer literal suffix must not start with 'e' or 'E'" , |
| 362 | }; |
| 363 | |
| 364 | description.fmt(f)?; |
| 365 | if let Some(span) = &self.span { |
| 366 | write!(f, " (at {}.. {})" , span.start, span.end)?; |
| 367 | } |
| 368 | |
| 369 | Ok(()) |
| 370 | } |
| 371 | } |
| 372 | |