interpol_impl/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use proc_macro2::{Group, Ident, Span, TokenTree};
5use proc_macro_hack::proc_macro_hack;
6use quote::quote;
7use std::iter::FromIterator;
8use syn::parse::Parser;
9use syn::punctuated::Punctuated;
10use syn::{parse_macro_input, parse_quote, parse_str, Expr, LitStr, Token};
11
12fn rewrite_site(e: proc_macro2::TokenStream, span: Span) -> proc_macro2::TokenStream {
13    proc_macro2::TokenStream::from_iter(e.into_iter().map(|tt| match tt {
14        TokenTree::Ident(ident) => TokenTree::Ident(Ident::new(&ident.to_string(), span)),
15        TokenTree::Group(group) => TokenTree::Group(Group::new(
16            group.delimiter(),
17            rewrite_site(group.stream(), span),
18        )),
19        tt => tt,
20    }))
21}
22
23fn gen(macro_call: impl Fn(String, Vec<Expr>) -> TokenStream, s: LitStr) -> TokenStream {
24    let call_site = s.span();
25    let s = s.value();
26
27    let mut fmt_string = String::new();
28
29    let mut args: Vec<Expr> = vec![];
30
31    let s: Vec<char> = s.chars().collect();
32    let mut s = &s[0..];
33
34    while !s.is_empty() {
35        if s[0] == '}' {
36            if s.len() >= 2 && s[1] == '}' {
37                fmt_string.push_str("}}");
38                s = &s[2..];
39                continue;
40            } else {
41                panic!("incorrect occurence of `}`");
42            }
43        }
44
45        if s[0] != '{' {
46            fmt_string.push(s[0]);
47            s = &s[1..];
48            continue;
49        }
50
51        // escaping ("{{")
52        if s.len() >= 2 && s[1] == '{' {
53            fmt_string.push_str("{{");
54            s = &s[2..];
55            continue;
56        }
57
58        // process interpolation
59        s = &s[1..];
60
61        let mut expr = vec![];
62        let mut level = 1;
63
64        // find corresponding '}'
65        while !s.is_empty() {
66            let c = s[0];
67            s = &s[1..];
68
69            if c == '}' {
70                level -= 1;
71                if level == 0 {
72                    break;
73                }
74            } else if c == '{' {
75                level += 1;
76            }
77
78            expr.push(c);
79        }
80
81        let mut manip_ix = None;
82
83        // find last ':' except a part of "::"
84        for i in 0..expr.len() {
85            if !(i >= 1 && expr[i - 1] == ':')
86                && expr[i] == ':'
87                && !(i + 1 < expr.len() && expr[i + 1] == ':')
88            {
89                manip_ix = Some(i);
90            }
91        }
92
93        let (expr, manip) = if let Some(manip_ix) = manip_ix {
94            (&expr[..manip_ix], &expr[manip_ix..])
95        } else {
96            (&expr[..], &[] as &[char])
97        };
98
99        let expr: String = expr.iter().collect();
100        let manip: String = manip.iter().collect();
101
102        fmt_string.push_str(&format!("{{{}}}", manip));
103
104        let expr: Expr = parse_str(&expr).expect(&format!("Failed to parse: `{}`", &expr));
105
106        let expr = rewrite_site(quote! { #expr }, call_site);
107        let expr: Expr = parse_quote! { #expr };
108
109        args.push(expr);
110    }
111
112    macro_call(fmt_string, args)
113}
114
115#[proc_macro_hack]
116pub fn format(input: TokenStream) -> TokenStream {
117    gen(
118        |fmt_string, args| quote!(std::format!(#fmt_string #(, #args )*)).into(),
119        parse_macro_input!(input as LitStr),
120    )
121}
122
123#[proc_macro_hack]
124pub fn print(input: TokenStream) -> TokenStream {
125    gen(
126        |fmt_string, args| quote!(std::print!(#fmt_string #(, #args )*)).into(),
127        parse_macro_input!(input as LitStr),
128    )
129}
130
131#[proc_macro_hack]
132pub fn println(input: TokenStream) -> TokenStream {
133    gen(
134        |fmt_string, args| quote!(std::println!(#fmt_string #(, #args )*)).into(),
135        parse_macro_input!(input as LitStr),
136    )
137}
138
139#[proc_macro_hack]
140pub fn eprint(input: TokenStream) -> TokenStream {
141    gen(
142        |fmt_string, args| quote!(std::eprint!(#fmt_string #(, #args )*)).into(),
143        parse_macro_input!(input as LitStr),
144    )
145}
146
147#[proc_macro_hack]
148pub fn eprintln(input: TokenStream) -> TokenStream {
149    gen(
150        |fmt_string, args| quote!(std::eprintln!(#fmt_string #(, #args )*)).into(),
151        parse_macro_input!(input as LitStr),
152    )
153}
154
155fn parse_write_arg(input: TokenStream) -> (Expr, LitStr) {
156    let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
157    let args = parser.parse(input).unwrap();
158    if args.len() != 2 {
159        panic!("too many arguments");
160    }
161    let mut it = args.iter();
162    let dst = it.next().unwrap();
163    let fmt = it.next().unwrap();
164    (dst.clone(), parse_quote! { #fmt })
165}
166
167#[proc_macro_hack]
168pub fn write(input: TokenStream) -> TokenStream {
169    let (dst, fmt) = parse_write_arg(input);
170    gen(
171        |fmt_string, args| quote!(std::write!(#dst, #fmt_string #(, #args )*)).into(),
172        fmt,
173    )
174}
175
176#[proc_macro_hack]
177pub fn writeln(input: TokenStream) -> TokenStream {
178    let (dst, fmt) = parse_write_arg(input);
179    gen(
180        |fmt_string, args| quote!(std::writeln!(#dst, #fmt_string #(, #args )*)).into(),
181        fmt,
182    )
183}