1extern crate proc_macro;
5
6use proc_macro::TokenStream;
7
8use quote::quote;
9use syn::{
10 Block, Ident, LitBool, LitByte, LitByteStr,
11 LitChar, LitFloat, LitInt, LitStr, parse_macro_input, Token, visit_mut,
12};
13use syn::parse::{self, Parse, ParseStream};
14use syn::spanned::Spanned;
15use syn::token::Underscore;
16use syn::visit_mut::VisitMut;
17
18#[cfg(test)]
19mod tests {
20 #[test]
21 fn test() {
22 let t = trybuild::TestCases::new();
23 t.pass("tests/pass.rs");
24 t.compile_fail("tests/fail/*.rs");
25 }
26}
27
28struct InputParser {
42 replace_ident: Ident,
43 concatenated_ident: Ident,
44 block: Block,
45}
46
47impl Parse for InputParser {
48 fn parse(input: ParseStream) -> parse::Result<Self> {
49 let replace_ident: Ident = input.parse()?;
50 let _: Token![=] = input.parse()?;
51 let IdentParser(concatenated_ident) = input.parse()?;
52 let block: Block = input.parse()?;
53
54 Ok(InputParser {
55 replace_ident,
56 concatenated_ident,
57 block,
58 })
59 }
60}
61
62struct IdentParser(Ident);
69
70impl Parse for IdentParser {
71 fn parse(input: ParseStream) -> parse::Result<Self> {
72 let mut ident_parts = vec![];
73
74 while !input.peek(syn::token::Brace) {
75 ident_parts.push(IdentPart::parse(input)?);
76
77 if input.peek(Token![,]) {
78 input.parse::<Token![,]>()?;
79 } else {
80 break;
81 }
82 }
83
84 let span = match ident_parts.first() {
85 Some(IdentPart::Ident(i)) => i.span(),
86 Some(IdentPart::Underscore(u)) => u.span(),
87 Some(IdentPart::Str(s)) => s.span(),
88 Some(IdentPart::Char(c)) => c.span(),
89 Some(IdentPart::Bool(b)) if ident_parts.len() > 1 => b.span(),
90
91 Some(IdentPart::Bool(b)) => return Err(syn::Error::new(
92 b.span(),
93 "Identifiers cannot consist of only one bool",
94 )),
95 Some(IdentPart::Int(i)) if ident_parts.len() > 1 => return Err(syn::Error::new(
96 i.span(),
97 "Identifiers cannot start with integers",
98 )),
99 Some(IdentPart::Int(i)) => return Err(syn::Error::new(
100 i.span(),
101 "Identifiers cannot start nor consist only of integers with integers",
102 )),
103 None => return Err(syn::Error::new(
104 input.span(),
105 "Expected at least one identifier",
106 ))
107 };
108
109 let mut ident = String::new();
110
111 for part in ident_parts {
112 match part {
113 IdentPart::Ident(i) => ident.push_str(i.to_string().trim_start_matches("r#")),
114 IdentPart::Underscore(_) => ident.push('_'),
115 IdentPart::Int(i) => ident.push_str(i.to_string().as_str()),
116 IdentPart::Bool(b) => ident.push_str(b.value.to_string().as_str()),
117 IdentPart::Str(s) => ident.push_str(s.value().as_str()),
118 IdentPart::Char(c) => ident.push(c.value())
119 }
120 }
121
122 Ok(Self(Ident::new(ident.as_str(), span)))
123 }
124}
125
126enum IdentPart {
134 Underscore(Underscore),
135 Ident(Ident),
136 Int(LitInt),
137 Bool(LitBool),
138 Str(LitStr),
139 Char(LitChar),
140}
141
142impl Parse for IdentPart {
143 fn parse(input: ParseStream) -> parse::Result<Self> {
144 if input.peek(Ident) {
145 Ok(Self::Ident(input.parse()?))
146 } else if input.peek(Token![_]) {
147 Ok(Self::Underscore(input.parse()?))
148 } else if input.peek(LitInt) {
149 Ok(Self::Int(input.parse()?))
150 } else if input.peek(LitBool) {
151 Ok(Self::Bool(input.parse()?))
152 } else if input.peek(LitStr) {
153 let string = input.parse::<LitStr>()?;
154 if string.value().contains(|c: char| !c.is_ascii_alphanumeric() && c != '_') {
155 Err(syn::Error::new(
156 string.span(),
157 "Identifier parts can only contain [a-zA-Z0-9_]",
158 ))
159 } else {
160 Ok(Self::Str(string))
161 }
162 } else if input.peek(LitChar) {
163 let char = input.parse::<LitChar>()?;
164 let c = char.value();
165 if !c.is_ascii_alphanumeric() && c != '_' {
166 Err(syn::Error::new(
167 char.span(),
168 "Identifier parts can only contain [a-zA-Z0-9_]",
169 ))
170 } else {
171 Ok(Self::Char(char))
172 }
173 } else if input.peek(LitByteStr) {
174 Err(syn::Error::new(input.span(), "Identifiers cannot contain byte string"))
175 } else if input.peek(LitByte) {
176 Err(syn::Error::new(input.span(), "Identifiers cannot contain bytes"))
177 } else if input.peek(LitFloat) {
178 Err(syn::Error::new(input.span(), "Identifiers cannot contain floats"))
179 } else {
180 Err(syn::Error::new(
181 input.span(),
182 "Expected either an identifies, a `_`, an int, a bool, \
183 a string-literal, or a character-literal.\n\
184 Note: To create an Identifies from a reserved keywords like `struct`, or `return`, \
185 wrap it quotes, i.e. `\"struct\"`, or escape them with `r#`, i.e. `r#struct` .",
186 ))
187 }
188 }
189}
190
191struct IdentReplacer {
194 replace_ident: Ident,
195 concatenated_ident: Ident,
196 code_block: Option<Block>,
197}
198
199impl IdentReplacer {
200 fn from_input_parser(input_parser: InputParser) -> Self {
202 Self {
203 replace_ident: input_parser.replace_ident,
204 concatenated_ident: input_parser.concatenated_ident,
205 code_block: Some(input_parser.block),
206 }
207 }
208
209 fn replace_idents(mut self) -> Self {
211 let mut code = self.code_block
212 .take()
213 .unwrap();
214 self.visit_block_mut(&mut code);
215 self.code_block = Some(code);
216
217 self
218 }
219
220 fn produce_token_stream(self) -> TokenStream {
222 let statements = self.code_block.unwrap().stmts;
223 (quote! { #( #statements )* }).into()
224 }
225}
226
227impl VisitMut for IdentReplacer {
228 fn visit_ident_mut(&mut self, node: &mut Ident) {
229 if *node == self.replace_ident {
230 *node = self.concatenated_ident.clone();
231 }
232
233 visit_mut::visit_ident_mut(self, node);
235 }
236}
237
238#[proc_macro]
288pub fn concat_idents(item: TokenStream) -> TokenStream {
289 let input_parser = parse_macro_input!(item as InputParser);
290
291 IdentReplacer::from_input_parser(input_parser)
292 .replace_idents()
293 .produce_token_stream()
294}