use std::{fmt, ops::Range};
#[derive(Debug, Clone, Copy)]
pub struct InvalidToken {
pub(crate) expected: TokenKind,
pub(crate) actual: TokenKind,
pub(crate) span: Span,
}
impl InvalidToken {
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()
}
#[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,
}
#[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)
}
}
#[derive(Debug, Clone)]
pub struct ParseError {
pub(crate) span: Option<Range<usize>>,
pub(crate) kind: ParseErrorKind,
}
impl ParseError {
pub fn span(&self) -> Option<Range<usize>> {
self.span.clone()
}
}
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>> {
fn into_span(self) -> Option<Range<usize>> {
self
}
}
impl SpanLike for Range<usize> {
fn into_span(self) -> Option<Range<usize>> {
Some(self)
}
}
impl SpanLike for usize {
fn into_span(self) -> Option<Range<usize>> {
Some(self..self + 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum ParseErrorKind {
Empty,
UnexpectedChar,
InvalidLiteral,
DoesNotStartWithDigit,
InvalidDigit,
NoDigits,
InvalidIntegerTypeSuffix,
InvalidFloatTypeSuffix,
NoExponentDigits,
UnknownEscape,
UnterminatedEscape,
InvalidXEscape,
NonAsciiXEscape,
UnicodeEscapeInByteLiteral,
InvalidStartOfUnicodeEscape,
UnicodeEscapeWithoutBrace,
NonHexDigitInUnicodeEscape,
TooManyDigitInUnicodeEscape,
InvalidUnicodeEscapeChar,
UnterminatedUnicodeEscape,
UnterminatedCharLiteral,
OverlongCharLiteral,
EmptyCharLiteral,
UnterminatedByteLiteral,
OverlongByteLiteral,
EmptyByteLiteral,
NonAsciiInByteLiteral,
UnescapedSingleQuote,
UnescapedSpecialWhitespace,
DoesNotStartWithQuote,
UnterminatedRawString,
UnterminatedString,
InvalidStringLiteralStart,
InvalidByteLiteralStart,
InvalidByteStringLiteralStart,
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(())
}
}