literify 0.2.0

Stringifies tokens inside token stream
Documentation
#![doc = include_str!("../README.md")]

use {
    proc_macro2::{Delimiter, Group, TokenStream, TokenTree},
    quote::{quote, quote_spanned},
    std::fmt::Write,
};

/// Stringify (and concat, also with strings) identifiers inside token stream
#[proc_macro]
pub fn literify(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
    expand(ts.into()).into()
}

fn expand(ts: TokenStream) -> TokenStream {
    let mut ts = ts.into_iter();
    let mut out = TokenStream::default();

    loop {
        match ts.next() {
            // 1. Found ~
            Some(TokenTree::Punct(p)) if p.as_char() == '~' => match ts.next() {
                // 2. Found group delimited by parentheses
                Some(TokenTree::Group(grp)) if grp.delimiter() == Delimiter::Parenthesis => {
                    let mut literal = String::new();

                    // 3. Substitute tokens inside token stream
                    for tt in grp.stream() {
                        match tt {
                            // Copy-in identifier
                            TokenTree::Ident(id) => write!(literal, "{id}").unwrap(),

                            // Copy-in string literal
                            TokenTree::Literal(lit) => {
                                let span = lit.span();
                                if let litrs::Literal::String(lit) = litrs::Literal::from(lit) {
                                    write!(literal, "{}", lit.value()).unwrap()
                                } else {
                                    return quote_spanned!(span => compile_error!("Unsupported literal"));
                                }
                            }

                            // Copy-in punctuation
                            TokenTree::Punct(punct) => write!(literal, "{punct}").unwrap(),
                            
                            // Other tokens are not supported
                            tt => {
                                return quote_spanned!(
                                    tt.span() =>
                                        compile_error!("Unsupported token tree (only string literals and identifiers are allowed)")
                                )
                            }
                        }
                    }

                    out.extend(quote!(#literal));
                }
                // Recurse into groups
                Some(TokenTree::Group(grp)) => out.extend([
                    TokenTree::Punct(p),
                    TokenTree::Group(Group::new(grp.delimiter(), expand(grp.stream()))),
                ]),
                Some(tt) => out.extend([TokenTree::Punct(p), tt]),
                None => break,
            },
            // Recurse into groups
            Some(TokenTree::Group(grp)) => out.extend([TokenTree::Group(Group::new(
                grp.delimiter(),
                expand(grp.stream()),
            ))]),
            Some(tt) => out.extend([tt]),
            None => break,
        }
    }

    out
}