#![allow(clippy::needless_doctest_main)]
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
#[proc_macro_attribute]
pub fn culit(args: TokenStream, input: TokenStream) -> TokenStream {
if !args.is_empty() {
panic!("`#[culit]` does not take any arguments between `(...)`")
}
transform(input)
}
fn transform(ts: TokenStream) -> TokenStream {
ts.into_iter()
.flat_map(|tt| {
match tt {
TokenTree::Literal(tt_lit) => {
let span = tt_lit.span();
let lit = litrs::Literal::parse(tt_lit.to_string()).expect(concat!(
"bug in the implementation of `litrs`, ",
"`token_tree::Literal` -> `litrs::Literal` is infallible"
));
let suffix = lit.suffix();
if suffix.is_empty() {
return AnonIter::I2([TokenTree::Literal(tt_lit)].into_iter());
}
const RESERVED_MESSAGE: &str = concat!(
" is not currently used ",
"by rust, but it likely will be in the future",
". To avoid breakage and not compromise rust's compatibility guarantees, ",
"we forbid this suffix"
);
match &lit {
litrs::Literal::Integer(integer_lit) => {
if INT_SUFFIXES.contains(&suffix) {
return AnonIter::I2([TokenTree::Literal(tt_lit)].into_iter());
} else if INT_SUFFIXES_RESERVED.contains(&suffix) {
return AnonIter::I3(
CompileError::new(
span,
format!("suffix {suffix} {RESERVED_MESSAGE}"),
)
.into_iter(),
);
}
let mut int = String::with_capacity(integer_lit.raw_input().len());
int.push_str(integer_lit.base().prefix());
int.push_str(integer_lit.raw_main_part());
int.parse::<Literal>().expect(concat!(
"if it wasn't a valid literal, `litrs::Literal`",
" would not be able to parse it"
));
AnonIter::I1(
expand_custom_literal(
lit_name::INTEGER,
suffix,
span,
TokenStream::from(TokenTree::Literal(
int.parse::<Literal>().expect(concat!(
"if it wasn't a valid literal, `litrs::Literal`",
" would not be able to parse it"
)),
)),
)
.into_iter(),
)
}
litrs::Literal::String(string_lit) => AnonIter::I1(
expand_custom_literal(
lit_name::STRING,
suffix,
span,
TokenStream::from(
TokenTree::Literal(Literal::string(string_lit.value()))
.with_span(span),
),
)
.into_iter(),
),
litrs::Literal::Float(float_lit) => {
if FLOAT_SUFFIXES.contains(&suffix) {
return AnonIter::I2([TokenTree::Literal(tt_lit)].into_iter());
} else if FLOAT_SUFFIXES_RESERVED.contains(&suffix) {
return AnonIter::I3(
CompileError::new(
span,
format!("suffix {suffix} {RESERVED_MESSAGE}"),
)
.into_iter(),
);
}
AnonIter::I1(
expand_custom_literal(
lit_name::DECIMAL,
suffix,
span,
TokenStream::from(TokenTree::Literal(
float_lit.number_part().parse::<Literal>().expect(concat!(
"if it wasn't a valid literal, `litrs::Literal`",
" would not be able to parse it"
)),
)),
)
.into_iter(),
)
}
litrs::Literal::Char(char_lit) => AnonIter::I1(
expand_custom_literal(
lit_name::CHARACTER,
suffix,
span,
TokenStream::from(
TokenTree::Literal(Literal::character(char_lit.value()))
.with_span(span),
),
)
.into_iter(),
),
litrs::Literal::Byte(byte_lit) => AnonIter::I1(
expand_custom_literal(
lit_name::BYTE_CHARACTER,
suffix,
span,
TokenStream::from(
TokenTree::Literal(Literal::byte_character(byte_lit.value()))
.with_span(span),
),
)
.into_iter(),
),
litrs::Literal::ByteString(byte_string_lit) => {
AnonIter::I1(
expand_custom_literal(
lit_name::BYTE_STRING,
suffix,
span,
TokenStream::from(
TokenTree::Literal(Literal::byte_string(
byte_string_lit.value(),
))
.with_span(span),
),
)
.into_iter(),
)
}
litrs::Literal::CString(cstring_lit) => {
AnonIter::I1(
expand_custom_literal(
lit_name::C_STRING,
suffix,
span,
TokenStream::from(
TokenTree::Literal(Literal::c_string(cstring_lit.value()))
.with_span(span),
),
)
.into_iter(),
)
}
litrs::Literal::Bool(_bool_lit) => {
unreachable!(
"booleans aren't `TokenTree::Literal`, they're `TokenTree::Ident`"
)
}
}
}
TokenTree::Group(group) => {
AnonIter::I2(
[TokenTree::Group(Group::new(
group.delimiter(),
transform(group.stream()),
))]
.into_iter(),
)
}
next_tt => AnonIter::I2([next_tt].into_iter()),
}
})
.collect()
}
fn expand_custom_literal(
literal_type: &str,
suffix: &str,
span: Span,
ts: TokenStream,
) -> [TokenTree; 12] {
[
TokenTree::Ident(Ident::new("crate", Span::call_site())),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Ident(Ident::new("custom_literal", Span::call_site())),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Ident(Ident::new(literal_type, Span::call_site())),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)),
TokenTree::Ident(Ident::new(suffix, span)),
TokenTree::Punct(Punct::new('!', Spacing::Joint)).with_span(span),
TokenTree::Group(Group::new(proc_macro::Delimiter::Parenthesis, ts)).with_span(span),
]
}
struct CompileError {
pub span: Span,
pub message: String,
}
impl CompileError {
pub fn new(span: Span, message: impl AsRef<str>) -> Self {
Self {
span,
message: message.as_ref().to_string(),
}
}
}
impl IntoIterator for CompileError {
type Item = TokenTree;
type IntoIter = std::array::IntoIter<Self::Item, 8>;
fn into_iter(self) -> Self::IntoIter {
[
TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span),
TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span),
TokenTree::Ident(Ident::new("core", self.span)),
TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span),
TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span),
TokenTree::Ident(Ident::new("compile_error", self.span)),
TokenTree::Punct(Punct::new('!', Spacing::Alone)).with_span(self.span),
TokenTree::Group(Group::new(Delimiter::Brace, {
TokenStream::from(
TokenTree::Literal(Literal::string(&self.message)).with_span(self.span),
)
}))
.with_span(self.span),
]
.into_iter()
}
}
trait TokenTreeExt {
fn with_span(self, span: Span) -> TokenTree;
}
impl TokenTreeExt for TokenTree {
fn with_span(mut self, span: Span) -> TokenTree {
self.set_span(span);
self
}
}
mod lit_name {
pub const INTEGER: &str = "integer";
pub const DECIMAL: &str = "decimal";
pub const STRING: &str = "string";
pub const CHARACTER: &str = "character";
pub const BYTE_CHARACTER: &str = "byte_character";
pub const BYTE_STRING: &str = "byte_string";
pub const C_STRING: &str = "c_string";
}
#[rustfmt::skip]
const INT_SUFFIXES: &[&str] = &[
"i8", "i16", "i32", "i64", "i128", "isize",
"u8", "u16", "u32", "u64", "u128", "usize",
];
const INT_SUFFIXES_RESERVED: &[&str] = &["i256", "u256"];
const FLOAT_SUFFIXES: &[&str] = &["f32", "f64"];
const FLOAT_SUFFIXES_RESERVED: &[&str] = &["f16", "f128"];
enum AnonIter<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> {
I1(I1),
I2(I2),
I3(I3),
}
impl<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> Iterator
for AnonIter<T, I1, I2, I3>
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
match self {
AnonIter::I1(i1) => i1.next(),
AnonIter::I2(i2) => i2.next(),
AnonIter::I3(i3) => i3.next(),
}
}
}