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 for Span { fn from(src: proc_macro::Span) -> Self { Self::One(src) } } #[cfg(feature = "proc-macro2")] impl From 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>, 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> { 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>; } impl SpanLike for Option> { #[inline(always)] fn into_span(self) -> Option> { self } } impl SpanLike for Range { #[inline(always)] fn into_span(self) -> Option> { Some(self) } } impl SpanLike for usize { #[inline(always)] fn into_span(self) -> Option> { 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, /// 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, /// Literal suffix is not a valid identifier. InvalidSuffix, /// Returned by `Float::parse` if an integer literal (no fractional nor /// exponent part) is passed. UnexpectedIntegerLit, /// Integer suffixes cannot start with `e` or `E` as this conflicts with the /// grammar for float literals. IntegerSuffixStartingWithE, } 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", 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", InvalidSuffix => "literal suffix is not a valid identifier", UnexpectedIntegerLit => "expected float literal, but found integer", IntegerSuffixStartingWithE => "integer literal suffix must not start with 'e' or 'E'", }; description.fmt(f)?; if let Some(span) = &self.span { write!(f, " (at {}..{})", span.start, span.end)?; } Ok(()) } }