1#![doc = include_str!("../README.md")]
2
3use {
4 proc_macro2::{Delimiter, Group, TokenStream, TokenTree},
5 quote::{quote, quote_spanned},
6 std::fmt::Write,
7};
8
9#[proc_macro]
11pub fn literify(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
12 expand(ts.into()).into()
13}
14
15fn expand(ts: TokenStream) -> TokenStream {
16 let mut ts = ts.into_iter();
17 let mut out = TokenStream::default();
18
19 loop {
20 match ts.next() {
21 Some(TokenTree::Punct(p)) if p.as_char() == '~' => match ts.next() {
23 Some(TokenTree::Group(grp)) if grp.delimiter() == Delimiter::Parenthesis => {
25 let mut literal = String::new();
26
27 for tt in grp.stream() {
29 match tt {
30 TokenTree::Ident(id) => write!(literal, "{id}").unwrap(),
32
33 TokenTree::Literal(lit) => {
35 let span = lit.span();
36 if let litrs::Literal::String(lit) = litrs::Literal::from(lit) {
37 write!(literal, "{}", lit.value()).unwrap()
38 } else {
39 return quote_spanned!(span => compile_error!("Unsupported literal"));
40 }
41 }
42
43 TokenTree::Punct(punct) => write!(literal, "{punct}").unwrap(),
45
46 tt => {
48 return quote_spanned!(
49 tt.span() =>
50 compile_error!("Unsupported token tree (only string literals and identifiers are allowed)")
51 )
52 }
53 }
54 }
55
56 out.extend(quote!(#literal));
57 }
58 Some(TokenTree::Group(grp)) => out.extend([
60 TokenTree::Punct(p),
61 TokenTree::Group(Group::new(grp.delimiter(), expand(grp.stream()))),
62 ]),
63 Some(tt) => out.extend([TokenTree::Punct(p), tt]),
64 None => break,
65 },
66 Some(TokenTree::Group(grp)) => out.extend([TokenTree::Group(Group::new(
68 grp.delimiter(),
69 expand(grp.stream()),
70 ))]),
71 Some(tt) => out.extend([tt]),
72 None => break,
73 }
74 }
75
76 out
77}