concat_idents_bruce0203/
lib.rs1extern crate proc_macro;
5
6use proc_macro::TokenStream;
7
8use quote::quote;
9use syn::parse::{self, Parse, ParseStream};
10use syn::spanned::Spanned;
11use syn::token::Underscore;
12use syn::{
13 parse_macro_input, Block, Ident, LitBool, LitByte, LitByteStr, LitChar, LitFloat, LitInt,
14 LitStr, Token,
15};
16
17#[cfg(test)]
18mod tests {
19 #[test]
20 fn test() {
21 let t = trybuild::TestCases::new();
22 t.pass("tests/pass.rs");
23 t.compile_fail("tests/fail/*.rs");
24 }
25}
26
27struct InputParser {
41 replace_ident: Ident,
42 concatenated_ident: Ident,
43 block: proc_macro2::TokenStream,
44}
45
46impl Parse for InputParser {
47 fn parse(input: ParseStream) -> parse::Result<Self> {
48 let replace_ident: Ident = input.parse()?;
49 let _: Token![=] = input.parse()?;
50 let IdentParser(concatenated_ident) = input.parse()?;
51 let block: Block = input.parse()?;
52 let stmts = block.stmts;
53 let block = quote! {#(#stmts)*};
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)) => {
92 return Err(syn::Error::new(
93 b.span(),
94 "Identifiers cannot consist of only one bool",
95 ))
96 }
97 Some(IdentPart::Int(i)) if ident_parts.len() > 1 => {
98 return Err(syn::Error::new(
99 i.span(),
100 "Identifiers cannot start with integers",
101 ))
102 }
103 Some(IdentPart::Int(i)) => {
104 return Err(syn::Error::new(
105 i.span(),
106 "Identifiers cannot start nor consist only of integers with integers",
107 ))
108 }
109 None => {
110 return Err(syn::Error::new(
111 input.span(),
112 "Expected at least one identifier",
113 ))
114 }
115 };
116
117 let mut ident = String::new();
118
119 for part in ident_parts {
120 match part {
121 IdentPart::Ident(i) => ident.push_str(i.to_string().trim_start_matches("r#")),
122 IdentPart::Underscore(_) => ident.push('_'),
123 IdentPart::Int(i) => ident.push_str(i.to_string().as_str()),
124 IdentPart::Bool(b) => ident.push_str(b.value.to_string().as_str()),
125 IdentPart::Str(s) => ident.push_str(s.value().as_str()),
126 IdentPart::Char(c) => ident.push(c.value()),
127 }
128 }
129
130 Ok(Self(Ident::new(ident.as_str(), span)))
131 }
132}
133
134enum IdentPart {
142 Underscore(Underscore),
143 Ident(Ident),
144 Int(LitInt),
145 Bool(LitBool),
146 Str(LitStr),
147 Char(LitChar),
148}
149
150impl Parse for IdentPart {
151 fn parse(input: ParseStream) -> parse::Result<Self> {
152 if input.peek(Ident) {
153 Ok(Self::Ident(input.parse()?))
154 } else if input.peek(Token![_]) {
155 Ok(Self::Underscore(input.parse()?))
156 } else if input.peek(LitInt) {
157 Ok(Self::Int(input.parse()?))
158 } else if input.peek(LitBool) {
159 Ok(Self::Bool(input.parse()?))
160 } else if input.peek(LitStr) {
161 let string = input.parse::<LitStr>()?;
162 if string
163 .value()
164 .contains(|c: char| !c.is_ascii_alphanumeric() && c != '_')
165 {
166 Err(syn::Error::new(
167 string.span(),
168 "Identifier parts can only contain [a-zA-Z0-9_]",
169 ))
170 } else {
171 Ok(Self::Str(string))
172 }
173 } else if input.peek(LitChar) {
174 let char = input.parse::<LitChar>()?;
175 let c = char.value();
176 if !c.is_ascii_alphanumeric() && c != '_' {
177 Err(syn::Error::new(
178 char.span(),
179 "Identifier parts can only contain [a-zA-Z0-9_]",
180 ))
181 } else {
182 Ok(Self::Char(char))
183 }
184 } else if input.peek(LitByteStr) {
185 Err(syn::Error::new(
186 input.span(),
187 "Identifiers cannot contain byte string",
188 ))
189 } else if input.peek(LitByte) {
190 Err(syn::Error::new(
191 input.span(),
192 "Identifiers cannot contain bytes",
193 ))
194 } else if input.peek(LitFloat) {
195 Err(syn::Error::new(
196 input.span(),
197 "Identifiers cannot contain floats",
198 ))
199 } else {
200 Err(syn::Error::new(
201 input.span(),
202 "Expected either an identifies, a `_`, an int, a bool, \
203 a string-literal, or a character-literal.\n\
204 Note: To create an Identifies from a reserved keywords like `struct`, or `return`, \
205 wrap it quotes, i.e. `\"struct\"`, or escape them with `r#`, i.e. `r#struct` .",
206 ))
207 }
208 }
209}
210
211struct IdentReplacer {
214 replace_ident: Ident,
215 concatenated_ident: Ident,
216 code_block: proc_macro2::TokenStream,
217}
218
219impl IdentReplacer {
220 fn from_input_parser(input_parser: InputParser) -> Self {
222 Self {
223 replace_ident: input_parser.replace_ident,
224 concatenated_ident: input_parser.concatenated_ident,
225 code_block: input_parser.block,
226 }
227 }
228
229 fn replace_idents(mut self) -> Self {
231 let mut token_trees = self.code_block.clone().into_iter().collect::<Vec<_>>();
232 for token_tree in token_trees.iter_mut() {
233 match token_tree {
234 proc_macro2::TokenTree::Ident(ref mut ident) => {
235 if *ident == self.replace_ident {
236 *ident = self.concatenated_ident.clone();
237 }
238 }
239 _ => {}
240 }
241 }
242 self.code_block = quote! { #(#token_trees)* };
243 self
244 }
245
246 fn produce_token_stream(self) -> TokenStream {
248 self.code_block.into()
249 }
250}
251
252#[proc_macro]
302pub fn concat_idents(item: TokenStream) -> TokenStream {
303 let input_parser = parse_macro_input!(item as InputParser);
304
305 IdentReplacer::from_input_parser(input_parser)
306 .replace_idents()
307 .produce_token_stream()
308}