tstr_proc_macros 0.3.0

Implementation detail of tstr.
Documentation
use proc_macro2::{Delimiter, Span, TokenStream, TokenTree};

use syn::{
    LitInt, LitStr,
    ext::IdentExt,
    parenthesized,
    parse::{Parse, ParseBuffer, ParseStream},
};

use super::{Inputs, TStr};

pub(crate) fn parse_inputs(
    ts: proc_macro2::TokenStream,
    crate_token: &mut Option<proc_macro::TokenStream>,
) -> Result<Inputs, syn::Error> {
    syn::parse::Parser::parse2(
        |input: ParseStream| {
            let content;
            parenthesized!(content in input);

            let crate_path = content
                .parse::<proc_macro2::TokenStream>()
                .expect("TokenStream never fails to parse");

            *crate_token = Some(crate_path.into());

            // unwrapping the :expr that the tstr macros passed in
            let expr_tokens = match input.parse::<TokenTree>()? {
                TokenTree::Group(group) if group.delimiter() == Delimiter::None => group.stream(),
                _ => panic!("not an :expr!"),
            };

            let string = syn::parse::Parser::parse2(
                |ts_input: ParseStream| {
                    let ret = TStr::parse(ts_input)?;

                    if !ts_input.is_empty() {
                        return Err(ts_input.error("unexpected token after the argument"));
                    }

                    Ok(ret)
                },
                expr_tokens,
            )?;

            Ok(Inputs { string })
        },
        ts,
    )
}

impl Parse for TStr {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        let (string, span) = if lookahead.peek(kw::concat) {
            input.parse::<kw::concat>()?;
            let (span, content) = parse_post_macro_name(input)?;
            let mut value = String::new();

            while !content.is_empty() {
                let tstr = content.parse::<TStr>()?;

                value.push_str(&tstr.string);

                if !content.is_empty() {
                    content.parse::<syn::Token!(,)>()?;
                }
            }

            (value, span)
        } else if lookahead.peek(kw::stringify) {
            input.parse::<kw::stringify>()?;
            let (span, content) = parse_post_macro_name(input)?;
            (content.parse::<TokenStream>()?.to_string(), span)
        } else if lookahead.peek(syn::Ident::peek_any) {
            let ident = input.call(syn::Ident::parse_any)?;
            let mut value = ident.to_string();
            if value.starts_with("r#") {
                value.drain(..2);
            }

            (value, ident.span())
        } else if lookahead.peek(LitStr) {
            let lit = input.parse::<LitStr>()?;
            (lit.value(), lit.span())
        } else if lookahead.peek(LitInt) {
            let lit = input.parse::<LitInt>()?;
            (lit.base10_digits().to_string(), lit.span())
        } else {
            return Err(lookahead.error());
        };
        Ok(Self { string, span })
    }
}

fn parse_post_macro_name(input: ParseStream) -> syn::Result<(Span, ParseBuffer)> {
    input.parse::<syn::Token!(!)>()?;
    let content;
    let paren = parenthesized!(content in input);
    Ok((paren.span.join(), content))
}

mod kw {
    syn::custom_keyword!(concat);
    syn::custom_keyword!(stringify);
}