| use std::{fmt, ops::Range}; |
| |
| |
| /// An error signaling that a different kind of token was expected. Returned by |
| /// the various `TryFrom` impls. |
| #[derive(Debug, Clone, Copy)] |
| pub struct InvalidToken { |
| pub(crate) expected: TokenKind, |
| pub(crate) actual: TokenKind, |
| pub(crate) span: Span, |
| } |
| |
| impl InvalidToken { |
| /// Returns a token stream representing `compile_error!("msg");` where |
| /// `"msg"` is the output of `self.to_string()`. **Panics if called outside |
| /// of a proc-macro context!** |
| pub fn to_compile_error(&self) -> proc_macro::TokenStream { |
| use proc_macro::{Delimiter, Ident, Group, Punct, Spacing, TokenTree}; |
| |
| let span = match self.span { |
| Span::One(s) => s, |
| #[cfg(feature = "proc-macro2")] |
| Span::Two(s) => s.unwrap(), |
| }; |
| let msg = self.to_string(); |
| let tokens = vec![ |
| TokenTree::from(Ident::new("compile_error", span)), |
| TokenTree::from(Punct::new('!', Spacing::Alone)), |
| TokenTree::from(Group::new( |
| Delimiter::Parenthesis, |
| TokenTree::from(proc_macro::Literal::string(&msg)).into(), |
| )), |
| ]; |
| |
| |
| tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect() |
| } |
| |
| /// Like [`to_compile_error`][Self::to_compile_error], but returns a token |
| /// stream from `proc_macro2` and does not panic outside of a proc-macro |
| /// context. |
| #[cfg(feature = "proc-macro2")] |
| pub fn to_compile_error2(&self) -> proc_macro2::TokenStream { |
| use proc_macro2::{Delimiter, Ident, Group, Punct, Spacing, TokenTree}; |
| |
| let span = match self.span { |
| Span::One(s) => proc_macro2::Span::from(s), |
| Span::Two(s) => s, |
| }; |
| let msg = self.to_string(); |
| let tokens = vec![ |
| TokenTree::from(Ident::new("compile_error", span)), |
| TokenTree::from(Punct::new('!', Spacing::Alone)), |
| TokenTree::from(Group::new( |
| Delimiter::Parenthesis, |
| TokenTree::from(proc_macro2::Literal::string(&msg)).into(), |
| )), |
| ]; |
| |
| |
| tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect() |
| } |
| } |
| |
| impl std::error::Error for InvalidToken {} |
| |
| impl fmt::Display for InvalidToken { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| fn kind_desc(kind: TokenKind) -> &'static str { |
| match kind { |
| TokenKind::Punct => "a punctuation character", |
| TokenKind::Ident => "an identifier", |
| TokenKind::Group => "a group", |
| TokenKind::Literal => "a literal", |
| TokenKind::BoolLit => "a bool literal (`true` or `false`)", |
| TokenKind::ByteLit => "a byte literal (e.g. `b'r')", |
| TokenKind::ByteStringLit => r#"a byte string literal (e.g. `b"fox"`)"#, |
| TokenKind::CharLit => "a character literal (e.g. `'P'`)", |
| TokenKind::FloatLit => "a float literal (e.g. `3.14`)", |
| TokenKind::IntegerLit => "an integer literal (e.g. `27`)", |
| TokenKind::StringLit => r#"a string literal (e.g. "Ferris")"#, |
| } |
| } |
| |
| write!(f, "expected {}, but found {}", kind_desc(self.expected), kind_desc(self.actual)) |
| } |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| pub(crate) enum TokenKind { |
| Punct, |
| Ident, |
| Group, |
| Literal, |
| BoolLit, |
| ByteLit, |
| ByteStringLit, |
| CharLit, |
| FloatLit, |
| IntegerLit, |
| StringLit, |
| } |
| |
| /// Unfortunately, we have to deal with both cases. |
| #[derive(Debug, Clone, Copy)] |
| pub(crate) enum Span { |
| One(proc_macro::Span), |
| #[cfg(feature = "proc-macro2")] |
| Two(proc_macro2::Span), |
| } |
| |
| impl From<proc_macro::Span> for Span { |
| fn from(src: proc_macro::Span) -> Self { |
| Self::One(src) |
| } |
| } |
| |
| #[cfg(feature = "proc-macro2")] |
| impl From<proc_macro2::Span> for Span { |
| fn from(src: proc_macro2::Span) -> Self { |
| Self::Two(src) |
| } |
| } |
| |
| /// Errors during parsing. |
| /// |
| /// This type should be seen primarily for error reporting and not for catching |
| /// specific cases. The span and error kind are not guaranteed to be stable |
| /// over different versions of this library, meaning that a returned error can |
| /// change from one version to the next. There are simply too many fringe cases |
| /// that are not easy to classify as a specific error kind. It depends entirely |
| /// on the specific parser code how an invalid input is categorized. |
| /// |
| /// Consider these examples: |
| /// - `'\` can be seen as |
| /// - invalid escape in character literal, or |
| /// - unterminated character literal. |
| /// - `'''` can be seen as |
| /// - empty character literal, or |
| /// - unescaped quote character in character literal. |
| /// - `0b64` can be seen as |
| /// - binary integer literal with invalid digit 6, or |
| /// - binary integer literal with invalid digit 4, or |
| /// - decimal integer literal with invalid digit b, or |
| /// - decimal integer literal 0 with unknown type suffix `b64`. |
| /// |
| /// If you want to see more if these examples, feel free to check out the unit |
| /// tests of this library. |
| /// |
| /// While this library does its best to emit sensible and precise errors, and to |
| /// keep the returned errors as stable as possible, full stability cannot be |
| /// guaranteed. |
| #[derive(Debug, Clone)] |
| pub struct ParseError { |
| pub(crate) span: Option<Range<usize>>, |
| pub(crate) kind: ParseErrorKind, |
| } |
| |
| impl ParseError { |
| /// Returns a span of this error, if available. **Note**: the returned span |
| /// might change in future versions of this library. See [the documentation |
| /// of this type][ParseError] for more information. |
| pub fn span(&self) -> Option<Range<usize>> { |
| self.span.clone() |
| } |
| } |
| |
| /// This is a free standing function instead of an associated one to reduce |
| /// noise around parsing code. There are lots of places that create errors, we |
| /// I wanna keep them as short as possible. |
| pub(crate) fn perr(span: impl SpanLike, kind: ParseErrorKind) -> ParseError { |
| ParseError { |
| span: span.into_span(), |
| kind, |
| } |
| } |
| |
| pub(crate) trait SpanLike { |
| fn into_span(self) -> Option<Range<usize>>; |
| } |
| |
| impl SpanLike for Option<Range<usize>> { |
| #[inline(always)] |
| fn into_span(self) -> Option<Range<usize>> { |
| self |
| } |
| } |
| impl SpanLike for Range<usize> { |
| #[inline(always)] |
| fn into_span(self) -> Option<Range<usize>> { |
| Some(self) |
| } |
| } |
| impl SpanLike for usize { |
| #[inline(always)] |
| fn into_span(self) -> Option<Range<usize>> { |
| Some(self..self + 1) |
| } |
| } |
| |
| |
| /// Kinds of errors. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| #[non_exhaustive] |
| pub(crate) enum ParseErrorKind { |
| /// The input was an empty string |
| Empty, |
| |
| /// An unexpected char was encountered. |
| UnexpectedChar, |
| |
| /// Literal was not recognized. |
| InvalidLiteral, |
| |
| /// Input does not start with decimal digit when trying to parse an integer. |
| DoesNotStartWithDigit, |
| |
| /// A digit invalid for the specified integer base was found. |
| InvalidDigit, |
| |
| /// Integer literal does not contain any valid digits. |
| NoDigits, |
| |
| /// Found a integer type suffix that is invalid. |
| InvalidIntegerTypeSuffix, |
| |
| /// Found a float type suffix that is invalid. Only `f32` and `f64` are |
| /// valid. |
| InvalidFloatTypeSuffix, |
| |
| /// Exponent of a float literal does not contain any digits. |
| NoExponentDigits, |
| |
| /// An unknown escape code, e.g. `\b`. |
| UnknownEscape, |
| |
| /// A started escape sequence where the input ended before the escape was |
| /// finished. |
| UnterminatedEscape, |
| |
| /// An `\x` escape where the two digits are not valid hex digits. |
| InvalidXEscape, |
| |
| /// A string or character literal using the `\xNN` escape where `NN > 0x7F`. |
| NonAsciiXEscape, |
| |
| /// A `\u{...}` escape in a byte or byte string literal. |
| UnicodeEscapeInByteLiteral, |
| |
| /// A Unicode escape that does not start with a hex digit. |
| InvalidStartOfUnicodeEscape, |
| |
| /// A `\u{...}` escape that lacks the opening brace. |
| UnicodeEscapeWithoutBrace, |
| |
| /// In a `\u{...}` escape, a non-hex digit and non-underscore character was |
| /// found. |
| NonHexDigitInUnicodeEscape, |
| |
| /// More than 6 digits found in unicode escape. |
| TooManyDigitInUnicodeEscape, |
| |
| /// The value from a unicode escape does not represent a valid character. |
| InvalidUnicodeEscapeChar, |
| |
| /// A `\u{..` escape that is not terminated (lacks the closing brace). |
| UnterminatedUnicodeEscape, |
| |
| /// A character literal that's not terminated. |
| UnterminatedCharLiteral, |
| |
| /// A character literal that contains more than one character. |
| OverlongCharLiteral, |
| |
| /// An empty character literal, i.e. `''`. |
| EmptyCharLiteral, |
| |
| UnterminatedByteLiteral, |
| OverlongByteLiteral, |
| EmptyByteLiteral, |
| NonAsciiInByteLiteral, |
| |
| /// A `'` character was not escaped in a character or byte literal, or a `"` |
| /// character was not escaped in a string or byte string literal. |
| UnescapedSingleQuote, |
| |
| /// A \n, \t or \r raw character in a char or byte literal. |
| UnescapedSpecialWhitespace, |
| |
| /// When parsing a character, byte, string or byte string literal directly |
| /// and the input does not start with the corresponding quote character |
| /// (plus optional raw string prefix). |
| DoesNotStartWithQuote, |
| |
| /// Unterminated raw string literal. |
| UnterminatedRawString, |
| |
| /// String literal without a `"` at the end. |
| UnterminatedString, |
| |
| /// Invalid start for a string literal. |
| InvalidStringLiteralStart, |
| |
| /// Invalid start for a byte literal. |
| InvalidByteLiteralStart, |
| |
| InvalidByteStringLiteralStart, |
| |
| /// An literal `\r` character not followed by a `\n` character in a |
| /// (raw) string or byte string literal. |
| IsolatedCr, |
| } |
| |
| impl std::error::Error for ParseError {} |
| |
| impl fmt::Display for ParseError { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| use ParseErrorKind::*; |
| |
| let description = match self.kind { |
| Empty => "input is empty", |
| UnexpectedChar => "unexpected character", |
| InvalidLiteral => "invalid literal", |
| DoesNotStartWithDigit => "number literal does not start with decimal digit", |
| InvalidDigit => "integer literal contains a digit invalid for its base", |
| NoDigits => "integer literal does not contain any digits", |
| InvalidIntegerTypeSuffix => "invalid integer type suffix", |
| InvalidFloatTypeSuffix => "invalid floating point type suffix", |
| NoExponentDigits => "exponent of floating point literal does not contain any digits", |
| UnknownEscape => "unknown escape", |
| UnterminatedEscape => "unterminated escape: input ended too soon", |
| InvalidXEscape => r"invalid `\x` escape: not followed by two hex digits", |
| NonAsciiXEscape => r"`\x` escape in char/string literal exceed ASCII range", |
| UnicodeEscapeInByteLiteral => r"`\u{...}` escape in byte (string) literal not allowed", |
| InvalidStartOfUnicodeEscape => r"invalid start of `\u{...}` escape", |
| UnicodeEscapeWithoutBrace => r"`Unicode \u{...}` escape without opening brace", |
| NonHexDigitInUnicodeEscape => r"non-hex digit found in `\u{...}` escape", |
| TooManyDigitInUnicodeEscape => r"more than six digits in `\u{...}` escape", |
| InvalidUnicodeEscapeChar => r"value specified in `\u{...}` escape is not a valid char", |
| UnterminatedUnicodeEscape => r"unterminated `\u{...}` escape", |
| UnterminatedCharLiteral => "character literal is not terminated", |
| OverlongCharLiteral => "character literal contains more than one character", |
| EmptyCharLiteral => "empty character literal", |
| UnterminatedByteLiteral => "byte literal is not terminated", |
| OverlongByteLiteral => "byte literal contains more than one byte", |
| EmptyByteLiteral => "empty byte literal", |
| NonAsciiInByteLiteral => "non ASCII character in byte (string) literal", |
| UnescapedSingleQuote => "character literal contains unescaped ' character", |
| UnescapedSpecialWhitespace => r"unescaped newline (\n), tab (\t) or cr (\r) character", |
| DoesNotStartWithQuote => "invalid start for char/byte/string literal", |
| UnterminatedRawString => "unterminated raw (byte) string literal", |
| UnterminatedString => "unterminated (byte) string literal", |
| InvalidStringLiteralStart => "invalid start for string literal", |
| InvalidByteLiteralStart => "invalid start for byte literal", |
| InvalidByteStringLiteralStart => "invalid start for byte string literal", |
| IsolatedCr => r"`\r` not immediately followed by `\n` in string", |
| }; |
| |
| description.fmt(f)?; |
| if let Some(span) = &self.span { |
| write!(f, " (at {}..{})", span.start, span.end)?; |
| } |
| |
| Ok(()) |
| } |
| } |