stringify_inner/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{convert::identity, str::FromStr};
4
5use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing::*, Span, TokenStream, TokenTree};
6
7fn err<T>(msg: &str, span: Span) -> Result<T, TokenStream> {
8    let s = |mut t: TokenTree| {
9        t.set_span(span.clone());
10        t
11    };
12    Err(TokenStream::from_iter([
13        s(Punct::new(':', Joint).into()),
14        s(Punct::new(':', Joint).into()),
15        s(Ident::new("core", span.clone()).into()),
16        s(Punct::new(':', Joint).into()),
17        s(Punct::new(':', Joint).into()),
18        s(Ident::new("compile_error", span.clone()).into()),
19        s(Punct::new('!', Joint).into()),
20        s(Group::new(Delimiter::Parenthesis, TokenStream::from_iter([
21            s(Literal::string(msg).into()),
22        ])).into()),
23    ]))
24}
25
26fn extract_str(s: &str, span: Span) -> Result<String, TokenStream> {
27    if s.starts_with('"') { return Ok(s[1..s.len()-1].to_owned()); }
28    if !s.starts_with('r') {
29        return err("invalid string literal", span);
30    }
31    let mut s = &s[1..];
32    while s.starts_with('#') {
33        s = &s[1..s.len()-1];
34    }
35    let escaped = format!("{:?}", &s[1..s.len()-1]);
36    return Ok(escaped[1..escaped.len()-1].to_string());
37}
38
39fn merge_str(a: &Literal, b: &Literal) -> Result<Literal, TokenStream> {
40    let (sa, sb) = (a.span(), b.span());
41    let (a, b) = (a.to_string(), b.to_string());
42    let (a, b) = (extract_str(&a, sa)?, extract_str(&b, sb)?);
43    Ok(Literal::from_str(&format!("\"{a}{b}\"")).unwrap())
44}
45
46fn expr_impl(stream: TokenStream) -> Result<TokenStream, TokenStream> {
47    let mut result = TokenStream::new();
48    let mut iter = stream.into_iter();
49    while let Some(tok) = iter.next() {
50        match tok {
51            proc_macro::TokenTree::Group(group) => {
52                let mut new_group = Group::new(
53                    group.delimiter(),
54                    expr_impl(group.stream())?,
55                );
56                new_group.set_span(group.span());
57                result.extend([TokenTree::from(new_group)]);
58            },
59            proc_macro::TokenTree::Punct(ref punct) if punct.as_char() == '#' => {
60                let Some(op) = iter.next() else {
61                    return err("unexpected end of input", punct.span());
62                };
63                match op {
64                    TokenTree::Punct(ref punct) => {
65                        if punct.as_char() == '#' {
66                            result.extend([op]);
67                        } else {
68                            return err("invalid operator", punct.span());
69                        }
70                    },
71                    TokenTree::Group(_) => {
72                        result.extend([tok]);
73                        result.extend(expr_impl(
74                            TokenStream::from_iter([op])
75                        )?);
76                    },
77                    TokenTree::Literal(tt) => return err("invalid operator", tt.span()),
78                    TokenTree::Ident(ident) => {
79                        let Some(param) = iter.next() else {
80                            return err("unexpected end of input", ident.span());
81                        };
82                        let TokenTree::Group(param) = param else {
83                            return err("invalid operation param", param.span());
84                        };
85                        let out_span = param.stream().into_iter().next()
86                            .map_or(param.span(), |t| t.span());
87                        let param = param.stream();
88                        match &*ident.to_string() {
89                            "stringify" => {
90                                let s = param.to_string();
91                                let mut tt = Literal::string(&s);
92                                tt.set_span(out_span);
93                                result.extend([TokenTree::from(tt)]);
94                            },
95                            "concat" => {
96                                let param = expr_impl(param)?;
97                                let mut s = Literal::string("");
98                                let mut iter = param.into_iter().peekable();
99                                while let Some(tt) = iter.next() {
100                                    iter.next_if(|p| matches!(p,
101                                            TokenTree::Punct(p) if p.as_char() == ','));
102                                    let TokenTree::Literal(lit) = tt else {
103                                        return err("is not a literal", tt.span())
104                                    };
105                                    match merge_str(&s, &lit) {
106                                        Ok(merged) => s = merged,
107                                        Err(e) => return Err(e),
108                                    }
109                                }
110                                s.set_span(out_span);
111                                result.extend([TokenTree::from(s)]);
112                            },
113                            _ => return err("unknown operator", ident.span()),
114                        }
115                    },
116                }
117            },
118            _ => result.extend([tok]),
119        }
120    }
121    Ok(result)
122}
123
124/// Run string expressions on expressions
125///
126/// - `#stringify(...)`: like `stringify!(...)`
127/// - `#concat(...)`: like `concat!(...)`
128/// - `##`: like `#`
129/// - `#[...]`: like `#[...]`
130///
131/// # Examples
132/// ```
133/// use stringify_inner::sexpr;
134///
135/// assert_eq!(sexpr!(#stringify(foo)), "foo");
136/// assert_eq!(sexpr!(&#stringify(foo)[1..]), "oo");
137/// assert_eq!(sexpr!(#concat(#stringify(foo), "bar")), "foobar");
138/// ```
139#[proc_macro]
140pub fn sexpr(stream: TokenStream) -> TokenStream {
141    expr_impl(stream).map_or_else(identity, identity)
142}
143
144/// Run string expressions on attribute
145///
146/// - `#stringify(...)`: like `stringify!(...)`
147/// - `#concat(...)`: like `concat!(...)`
148/// - `##`: like `#`
149/// - `#[...]`: like `#[...]`
150///
151/// # Examples
152/// ```
153/// use stringify_inner::sexpr_attr;
154///
155/// #[sexpr_attr(doc(alias = #stringify(bar)))]
156/// fn foo() {}
157/// ```
158#[proc_macro_attribute]
159pub fn sexpr_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
160    TokenStream::from_iter([
161        expr_impl(TokenStream::from_iter([
162            TokenTree::from(Punct::new('#', Joint)),
163            Group::new(Delimiter::Bracket, attr).into(),
164        ])).unwrap_or_else(|mut e| {
165            e.extend([TokenTree::from(Punct::new(';', Alone))]);
166            e
167        }),
168        item,
169    ])
170}