1extern crate proc_macro;
123use proc_macro::TokenStream;
124
125use syn::parse::Parser;
126use syn::punctuated::Punctuated;
127use syn::visit::Visit;
128use syn::visit_mut::{visit_expr_mut, VisitMut};
129use syn::{
130 parse_macro_input, Expr, ExprAssign, ExprLit, ExprPath, Item, Lit, LitBool, Macro, Token,
131};
132
133use quote::{quote, ToTokens};
134
135struct NumericLiteralVisitor<'a> {
139 pub parameters: MacroParameters,
140 pub placeholder: &'a str,
141 pub float_replacement: &'a Expr,
142 pub int_replacement: &'a Expr,
143}
144
145struct FloatLiteralVisitor<'a> {
146 pub parameters: MacroParameters,
147 pub placeholder: &'a str,
148 pub replacement: &'a Expr,
149}
150
151struct IntLiteralVisitor<'a> {
152 pub parameters: MacroParameters,
153 pub placeholder: &'a str,
154 pub replacement: &'a Expr,
155}
156
157enum PrimitiveClass {
159 Float,
160 Int,
161 Other,
162}
163
164fn determine_primitive_class(lit_expr: &ExprLit) -> PrimitiveClass {
166 match &lit_expr.lit {
167 Lit::Float(_) => PrimitiveClass::Float,
169 Lit::Int(int_lit) if matches!(int_lit.suffix(), "f32" | "f64") => PrimitiveClass::Float,
171 Lit::Int(_) => PrimitiveClass::Int,
173 _ => PrimitiveClass::Other,
174 }
175}
176
177fn replace_literal(expr: &mut Expr, placeholder: &str, literal: &ExprLit) {
178 let mut replacer = ReplacementExpressionVisitor {
179 placeholder,
180 literal,
181 };
182 replacer.visit_expr_mut(expr);
183}
184
185fn try_parse_punctuated_macro<P: ToTokens, V: VisitMut, F: Parser<Output = Punctuated<Expr, P>>>(
186 visitor: &mut V,
187 mac: &mut Macro,
188 parser: F,
189) -> bool {
190 if let Ok(mut exprs) = mac.parse_body_with(parser) {
191 exprs
192 .iter_mut()
193 .for_each(|expr| visitor.visit_expr_mut(expr));
194 mac.tokens = exprs.into_token_stream();
195 return true;
196 }
197 return false;
198}
199
200fn visit_macros_mut<V: VisitMut>(visitor: &mut V, mac: &mut Macro) {
201 if let Ok(mut expr) = mac.parse_body::<Expr>() {
203 visitor.visit_expr_mut(&mut expr);
204 mac.tokens = expr.into_token_stream();
205 return;
206 }
207
208 let parser_comma = Punctuated::<Expr, Token![,]>::parse_terminated;
210 if try_parse_punctuated_macro(visitor, mac, parser_comma) {
211 return;
212 }
213
214 let parser_semicolon = Punctuated::<Expr, Token![;]>::parse_terminated;
216 if try_parse_punctuated_macro(visitor, mac, parser_semicolon) {
217 return;
218 }
219}
220
221impl<'a> VisitMut for FloatLiteralVisitor<'a> {
222 fn visit_expr_mut(&mut self, expr: &mut Expr) {
223 if let Expr::Lit(lit_expr) = expr {
224 if let PrimitiveClass::Float = determine_primitive_class(&lit_expr) {
225 let mut adapted_replacement = self.replacement.clone();
226 replace_literal(&mut adapted_replacement, self.placeholder, lit_expr);
227 *expr = adapted_replacement;
228 return;
229 }
230 }
231 visit_expr_mut(self, expr)
232 }
233
234 fn visit_macro_mut(&mut self, mac: &mut Macro) {
235 if self.parameters.visit_macros {
236 visit_macros_mut(self, mac);
237 }
238 }
239}
240
241impl<'a> VisitMut for IntLiteralVisitor<'a> {
242 fn visit_expr_mut(&mut self, expr: &mut Expr) {
243 if let Expr::Lit(lit_expr) = expr {
244 if let PrimitiveClass::Int = determine_primitive_class(&lit_expr) {
245 let mut adapted_replacement = self.replacement.clone();
246 replace_literal(&mut adapted_replacement, self.placeholder, lit_expr);
247 *expr = adapted_replacement;
248 return;
249 }
250 }
251 visit_expr_mut(self, expr)
252 }
253
254 fn visit_macro_mut(&mut self, mac: &mut Macro) {
255 if self.parameters.visit_macros {
256 visit_macros_mut(self, mac);
257 }
258 }
259}
260
261impl<'a> VisitMut for NumericLiteralVisitor<'a> {
262 fn visit_expr_mut(&mut self, expr: &mut Expr) {
263 if let Expr::Lit(lit_expr) = expr {
264 match determine_primitive_class(&lit_expr) {
269 PrimitiveClass::Float => {
270 let mut visitor = FloatLiteralVisitor {
271 parameters: self.parameters,
272 placeholder: self.placeholder,
273 replacement: self.float_replacement,
274 };
275 visitor.visit_expr_mut(expr);
276 return;
277 }
278 PrimitiveClass::Int => {
279 let mut visitor = IntLiteralVisitor {
280 parameters: self.parameters,
281 placeholder: self.placeholder,
282 replacement: self.int_replacement,
283 };
284 visitor.visit_expr_mut(expr);
285 return;
286 }
287 _ => {}
288 }
289 }
290 visit_expr_mut(self, expr)
291 }
292
293 fn visit_macro_mut(&mut self, mac: &mut Macro) {
294 if self.parameters.visit_macros {
295 visit_macros_mut(self, mac);
296 }
297 }
298}
299
300struct ReplacementExpressionVisitor<'a> {
303 pub placeholder: &'a str,
304 pub literal: &'a ExprLit,
305}
306
307impl<'a> VisitMut for ReplacementExpressionVisitor<'a> {
308 fn visit_expr_mut(&mut self, expr: &mut Expr) {
309 if let Expr::Path(path_expr) = expr {
310 if let Some(last_segment) = path_expr.path.segments.last() {
311 if last_segment.ident == self.placeholder {
312 *expr = Expr::Lit(self.literal.clone());
313 return;
314 }
315 }
316 }
317 visit_expr_mut(self, expr)
318 }
319}
320
321struct MacroParameterVisitor {
322 pub name: Option<String>,
323 pub value: Option<ParameterValue>,
324}
325
326impl MacroParameterVisitor {
327 fn parse_flag(expr: &Expr) -> Option<(String, ParameterValue)> {
328 let mut visitor = MacroParameterVisitor {
329 name: None,
330 value: None,
331 };
332 visitor.visit_expr(expr);
333 let name = visitor.name.take();
334 let value = visitor.value.take();
335 name.and_then(|n| value.and_then(|v| Some((n, v))))
336 }
337}
338
339impl<'ast> Visit<'ast> for MacroParameterVisitor {
340 fn visit_expr_assign(&mut self, expr: &'ast ExprAssign) {
341 self.visit_expr(&expr.left);
342 self.visit_expr(&expr.right);
343 }
344
345 fn visit_expr_path(&mut self, expr: &'ast ExprPath) {
346 let mut name = Vec::new();
347 expr.path
348 .leading_colon
349 .map(|_| name.push(String::from("::")));
350 for p in expr.path.segments.pairs() {
351 match p {
352 syn::punctuated::Pair::Punctuated(ps, _sep) => {
353 name.push(ps.ident.to_string());
354 name.push(String::from("::"));
355 }
356 syn::punctuated::Pair::End(ps) => {
357 name.push(ps.ident.to_string());
358 }
359 }
360 }
361 self.name = Some(name.concat());
362 }
363
364 fn visit_lit_bool(&mut self, expr: &'ast LitBool) {
365 self.value = Some(ParameterValue::Bool(expr.value));
366 }
367}
368
369enum ParameterValue {
370 Bool(bool),
371}
372
373#[derive(Copy, Clone)]
374struct MacroParameters {
375 pub visit_macros: bool,
376}
377
378impl Default for MacroParameters {
379 fn default() -> Self {
380 Self { visit_macros: true }
381 }
382}
383
384impl MacroParameters {
385 fn set(&mut self, name: &str, value: ParameterValue) {
386 match name {
387 "visit_macros" => match value {
388 ParameterValue::Bool(v) => self.visit_macros = v,
389 },
390 _ => {}
391 }
392 }
393}
394
395fn parse_macro_attribute(attr: TokenStream) -> Result<(Expr, MacroParameters), syn::Error> {
397 let parser = Punctuated::<Expr, Token![,]>::parse_separated_nonempty;
398 let attributes = parser.parse(attr)?;
399
400 let mut attr_iter = attributes.into_iter();
401 let replacement = attr_iter.next().expect("No replacement provided");
402
403 let user_parameters: Vec<_> = attr_iter
404 .filter_map(|expr| MacroParameterVisitor::parse_flag(&expr))
405 .collect();
406 let mut parameters = MacroParameters::default();
407 for (name, value) in user_parameters {
408 parameters.set(&name, value);
409 }
410
411 Ok((replacement, parameters))
412}
413
414#[proc_macro_attribute]
418pub fn replace_numeric_literals(attr: TokenStream, item: TokenStream) -> TokenStream {
419 let mut input = parse_macro_input!(item as Item);
420 let (replacement, parameters) = match parse_macro_attribute(attr) {
421 Ok(res) => res,
422 Err(err) => return TokenStream::from(err.to_compile_error()),
423 };
424
425 let mut replacer = NumericLiteralVisitor {
426 parameters,
427 placeholder: "literal",
428 int_replacement: &replacement,
429 float_replacement: &replacement,
430 };
431 replacer.visit_item_mut(&mut input);
432
433 let expanded = quote! { #input };
434
435 TokenStream::from(expanded)
436}
437
438#[proc_macro_attribute]
442pub fn replace_float_literals(attr: TokenStream, item: TokenStream) -> TokenStream {
443 let mut input = parse_macro_input!(item as Item);
444 let (replacement, parameters) = match parse_macro_attribute(attr) {
445 Ok(res) => res,
446 Err(err) => return TokenStream::from(err.to_compile_error()),
447 };
448
449 let mut replacer = FloatLiteralVisitor {
450 parameters,
451 placeholder: "literal",
452 replacement: &replacement,
453 };
454 replacer.visit_item_mut(&mut input);
455
456 let expanded = quote! { #input };
457
458 TokenStream::from(expanded)
459}
460
461#[proc_macro_attribute]
465pub fn replace_int_literals(attr: TokenStream, item: TokenStream) -> TokenStream {
466 let mut input = parse_macro_input!(item as Item);
467 let (replacement, parameters) = match parse_macro_attribute(attr) {
468 Ok(res) => res,
469 Err(err) => return TokenStream::from(err.to_compile_error()),
470 };
471
472 let mut replacer = IntLiteralVisitor {
473 parameters,
474 placeholder: "literal",
475 replacement: &replacement,
476 };
477 replacer.visit_item_mut(&mut input);
478
479 let expanded = quote! { #input };
480
481 TokenStream::from(expanded)
482}