concat_strs_derive/
lib.rs1use proc_macro as pm;
2use proc_macro2::{Span, TokenStream};
3use quote::{quote, ToTokens};
4use syn::parse::{Parse, ParseStream};
5use syn::punctuated::Punctuated;
6use syn::token;
7
8#[proc_macro]
9pub fn concat_strs(input: pm::TokenStream) -> pm::TokenStream {
10 let macro_input = syn::parse_macro_input!(input as ConcatStrs);
11 impl_concat_strs(macro_input).into()
12}
13
14struct ConcatStrs {
15 exprs: Punctuated<syn::Expr, token::Comma>,
16}
17
18impl Parse for ConcatStrs {
19 fn parse(input: ParseStream) -> syn::Result<Self> {
20 Ok(Self {
21 exprs: Punctuated::parse_terminated(input)?,
22 })
23 }
24}
25
26impl ConcatStrs {
27 fn bytes_needed(&self) -> usize {
28 self.exprs.iter().map(byte_count).sum()
29 }
30}
31
32fn byte_count(expr: &syn::Expr) -> usize {
33 match expr {
34 syn::Expr::Lit(lit) => match &lit.lit {
35 syn::Lit::Str(lit) => lit.value().len(),
36 syn::Lit::Char(lit) => lit.value().len_utf8(),
37 syn::Lit::Int(_) | syn::Lit::Float(_) => lit.lit.to_token_stream().to_string().len(),
38 _ => 0,
39 },
40 _ => 0,
41 }
42}
43
44fn tmp_assign_tokens(
45 expr: &syn::Expr,
46 tmp_ident: &syn::Ident,
47) -> Option<(syn::Ident, TokenStream)> {
48 match expr {
49 syn::Expr::Lit(_) => None,
50 _ => {
51 let len_ident = syn::Ident::new(&format!("{}_len", tmp_ident), Span::call_site());
52 Some((
53 len_ident.clone(),
54 quote! {
55 let #tmp_ident = #expr;
56 let #len_ident = #tmp_ident.len();
57 },
58 ))
59 }
60 }
61}
62
63fn push_tokens(expr: &syn::Expr, tmp_ident: &syn::Ident, string_ident: &syn::Ident) -> TokenStream {
64 match expr {
65 syn::Expr::Lit(expr) => match expr.lit {
66 syn::Lit::Str(_) => {
67 quote! {
68 #string_ident.push_str(#expr);
69 }
70 }
71 syn::Lit::Char(_) => {
72 quote! {
73 #string_ident.push(#expr);
74 }
75 }
76 syn::Lit::Float(_) | syn::Lit::Int(_) => {
77 let expr_string =
78 syn::LitStr::new(&expr.lit.to_token_stream().to_string(), Span::call_site());
79 quote! {
80 #string_ident.push_str(#expr_string);
81 }
82 }
83 _ => panic!("Unsupported expression {}", expr.to_token_stream()),
84 },
85 _ => {
86 quote! {
87 #string_ident.push_str(#tmp_ident);
88 }
89 }
90 }
91}
92
93fn tmp_idents() -> impl Iterator<Item = syn::Ident> {
94 (0..).map(|n| syn::Ident::new(&format!("__tmp_{}", n), Span::call_site()))
95}
96
97fn impl_concat_strs(items: ConcatStrs) -> TokenStream {
98 let bytes = items.bytes_needed();
99 let string_ident: syn::Ident = syn::Ident::new("__ret", Span::call_site());
100
101 let tmp_assignments_idents = items
102 .exprs
103 .iter()
104 .zip(tmp_idents())
105 .map(|(expr, ident)| (tmp_assign_tokens(expr, &ident), ident))
106 .collect::<Vec<_>>();
107
108 let tmp_len_idents = tmp_assignments_idents
109 .iter()
110 .filter_map(|(assign, _ident)| {
111 if let Some((len_ident, _assign)) = assign {
112 Some(len_ident)
113 } else {
114 None
115 }
116 })
117 .collect::<Vec<_>>();
118
119 let tmp_assignments = tmp_assignments_idents
120 .iter()
121 .filter_map(|(assign, _ident)| assign.clone().map(|(_len_ident, assign)| assign))
122 .collect::<Vec<_>>();
123
124 let push_exprs = items
125 .exprs
126 .iter()
127 .zip(&tmp_assignments_idents)
128 .map(|(expr, (_expr, tmp_ident))| push_tokens(expr, tmp_ident, &string_ident))
129 .collect::<Vec<_>>();
130
131 quote! {{
132 #(#tmp_assignments)*
133 let mut #string_ident = String::with_capacity(#(#tmp_len_idents +)* #bytes);
134 #(#push_exprs)*
135 #string_ident
136 }}
137}