#![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 Some(value) = integer_lit.value::<u128>() else {
return AnonIter::I3(
CompileError::new(
span,
format!(
"custom integer literals are only supported for {} {}",
"integers who's absolute value does not exceed",
u128::MAX
),
)
.into_iter(),
);
};
let value =
TokenTree::Literal(Literal::u128_unsuffixed(value)).with_span(span);
AnonIter::I1(
expand_custom_literal(
lit_name::INTEGER,
suffix,
span,
TokenStream::from_iter([value]),
)
.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(),
);
}
let Ok(integral) = float_lit
.integer_part()
.split('_')
.collect::<String>()
.parse::<u128>()
else {
return AnonIter::I3(
CompileError::new(
span,
format!(
"custom float literals are only supported for {} {} {}",
"floats that who's integral part (before the `.`)",
"does not exceed",
u128::MAX
),
)
.into_iter(),
);
};
let Ok(fractional) = float_lit
.fractional_part()
.map(|it| it.split('_').collect::<String>().parse::<u128>())
.unwrap_or(Ok(0))
else {
return AnonIter::I3(
CompileError::new(
span,
format!(
concat!(
"custom float literals are only supported for ",
"floats that who's fractional ",
"part (after the `.`) does not exceed {}"
),
u128::MAX
),
)
.into_iter(),
);
};
let (is_negative, exponent) = match float_lit.exponent_part() {
"" => (false, 1),
exp => {
let first_part =
exp.get(1..).expect("first letter is `e` or `E`");
let without_minus = first_part.strip_prefix('-');
let is_negative = without_minus.is_some();
let without_minus = without_minus.unwrap_or(first_part);
let Ok(exp) = without_minus
.strip_prefix('+')
.unwrap_or(without_minus)
.split('_')
.collect::<String>()
.parse::<u128>()
else {
return AnonIter::I3(
CompileError::new(
span,
format!(
"custom float literals are only supported for {} {}",
"floats that who's exponent does not exceed",
u128::MAX
),
)
.into_iter(),
);
};
(is_negative, exp)
}
};
let exponent_sign = is_negative
.then(|| TokenTree::Punct(Punct::new('-', Spacing::Joint)));
AnonIter::I1(
expand_custom_literal(
lit_name::DECIMAL,
suffix,
span,
TokenStream::from_iter(
[
TokenTree::Literal(Literal::u128_unsuffixed(integral))
.with_span(span),
TokenTree::Literal(Literal::u128_unsuffixed(
fractional,
))
.with_span(span),
]
.into_iter()
.chain(exponent_sign)
.chain([
TokenTree::Literal(Literal::u128_unsuffixed(exponent))
.with_span(span),
]),
),
)
.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::u8_unsuffixed(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(),
)
}
#[cfg(not(has_c_string))]
litrs::Literal::CString(_cstring_lit) => {
return AnonIter::I2(CompileError::new(
tt_lit.span(),
concat!(
"custom c-string literal with suffix ",
"is only supported on Rust version >=1.79"
),
))
.into_iter()
.collect();
}
#[cfg(has_c_string)]
#[cfg_attr(has_c_string, allow(clippy::incompatible_msrv))]
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, 3>;
fn into_iter(self) -> Self::IntoIter {
[
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";
#[cfg(has_c_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(),
}
}
}