use proc_macro2::{Punct, Spacing, TokenStream, TokenTree};
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Parse, ParseStream};
#[cfg(feature = "namespace_idents")]
use syn::LitStr;
use syn::{parse_macro_input, Expr, Result, Token};
use use_parser::{Use, UseItem};
mod prelude;
mod use_parser;
#[proc_macro]
pub fn quote_use(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let uses = parse_macro_input!(input as QuoteUse);
quote! {
::quote_use::__private::quote::quote!{
#uses
}
}
.into()
}
#[proc_macro]
pub fn quote_spanned_use(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let UsesSpanned(spanned, uses) = parse_macro_input!(input as UsesSpanned);
quote! {
::quote_use::__private::quote::quote_spanned!{
#spanned
#uses
}
}
.into()
}
#[proc_macro]
pub fn parse_quote_use(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let uses = parse_macro_input!(input as QuoteUse);
quote! {
::quote_use::__private::syn::parse_quote!{
#uses
}
}
.into()
}
#[proc_macro]
pub fn parse_quote_spanned_use(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let UsesSpanned(spanned, uses) = parse_macro_input!(input as UsesSpanned);
quote! {
::quote_use::__private::syn::parse_quote_spanned!{
#spanned
#uses
}
}
.into()
}
#[proc_macro]
#[cfg(feature = "namespace_idents")]
pub fn format_ident_namespaced(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let format_input = parse_macro_input!(input as FormatInput);
quote! {
::quote_use::__private::quote::format_ident!(#format_input)
}
.into()
}
#[cfg(feature = "namespace_idents")]
struct FormatInput(LitStr, TokenStream);
#[cfg(feature = "namespace_idents")]
impl Parse for FormatInput {
fn parse(input: ParseStream) -> Result<Self> {
let lit = input.parse()?;
Ok(Self(lit, input.parse()?))
}
}
#[cfg(feature = "namespace_idents")]
impl ToTokens for FormatInput {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self(lit, tail) = self;
let lit_span = lit.span();
if let Some(lit) = lit.value().strip_prefix('$') {
LitStr::new(&format!("{}{}", ident_prefix(), lit), lit_span).to_tokens(tokens);
} else {
lit.to_tokens(tokens)
};
tail.to_tokens(tokens);
}
}
#[cfg(feature = "namespace_idents")]
fn ident_prefix() -> String {
if let Ok(crate_name) = std::env::var("CARGO_PKG_NAME") {
format!("__{}_", crate_name.replace('-', "_"))
} else {
String::from("___procmacro_")
}
}
struct UsesSpanned(TokenStream, QuoteUse);
impl Parse for UsesSpanned {
fn parse(input: ParseStream) -> Result<Self> {
let expr = Expr::parse(input)?;
let arrow = <Token!(=>)>::parse(input)?;
Ok(Self(quote!(#expr #arrow), QuoteUse::parse(input)?))
}
}
struct QuoteUse(Vec<Use>, TokenStream);
impl Parse for QuoteUse {
fn parse(input: ParseStream) -> Result<Self> {
let mut uses = Vec::new();
while input.peek(Token![#]) && input.peek2(Token![use]) {
input.parse::<Token![#]>().expect("# was peeked before");
uses.extend_from_slice(&UseItem::parse(input)?.0);
}
Ok(QuoteUse(uses, input.parse()?))
}
}
impl ToTokens for QuoteUse {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self(uses, tail) = self;
let mut uses = uses.to_vec();
uses.extend(prelude::prelude());
#[cfg(feature = "namespace_idents")]
let ident_prefix = Some(ident_prefix());
#[cfg(not(feature = "namespace_idents"))]
let ident_prefix: Option<String> = None;
tokens.extend(replace_in_group(
&uses,
tail.clone(),
ident_prefix.as_deref(),
));
}
}
fn replace_in_group(uses: &[Use], tokens: TokenStream, ident_prefix: Option<&str>) -> TokenStream {
use State::*;
#[derive(Clone, Copy)]
enum State {
Path,
Pound,
Dollar,
DollarQuote,
Normal,
}
impl ToTokens for State {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Path | Normal | Pound => {}
Dollar => quote!($).to_tokens(tokens),
DollarQuote => unreachable!("lifetime `'` must be followed by ident"),
}
}
}
let mut state = Normal;
tokens
.into_iter()
.flat_map(|token| {
let mut prev = Normal;
match (&token, state) {
(TokenTree::Ident(ident), Dollar) if ident != "crate" => {
state = Normal;
return format_ident!("{}{ident}", ident_prefix.expect("ident prefix is set"))
.into_token_stream();
}
(TokenTree::Ident(ident), DollarQuote) => {
state = Normal;
return [
TokenTree::from(Punct::new('\'', Spacing::Joint)),
format_ident!("{}{ident}", ident_prefix.expect("ident prefix is set"))
.into(),
]
.into_iter()
.collect();
}
(TokenTree::Ident(ident), Normal) => {
if let Some(Use(path, _)) = uses.iter().find(|item| &item.1 == ident) {
return quote!(#path);
}
}
(TokenTree::Punct(punct), _)
if punct.spacing() == Spacing::Joint && punct.as_char() == ':' =>
{
prev = state;
state = Path;
}
(TokenTree::Punct(punct), _) if punct.as_char() == ':' => (),
(TokenTree::Punct(punct), _) if punct.as_char() == '#' => {
prev = state;
state = Pound;
}
(TokenTree::Punct(punct), Dollar) if punct.as_char() == '$' => {
state = Normal;
}
(TokenTree::Punct(punct), Normal | Pound | Path)
if punct.as_char() == '$' && ident_prefix.is_some() =>
{
state = Dollar;
return quote!();
}
(TokenTree::Punct(punct), Dollar)
if punct.as_char() == '\'' && ident_prefix.is_some() =>
{
state = DollarQuote;
return quote!();
}
(TokenTree::Group(group), _) => {
let tokens = replace_in_group(uses, group.stream(), ident_prefix);
return match group.delimiter() {
proc_macro2::Delimiter::Parenthesis => quote!((#tokens)),
proc_macro2::Delimiter::Brace => quote!({#tokens}),
proc_macro2::Delimiter::Bracket => quote!([#tokens]),
proc_macro2::Delimiter::None => tokens,
};
}
_ => {
prev = state;
state = Normal;
}
};
quote!(#prev #token)
})
.collect()
}