xref: /linux/rust/macros/fmt.rs (revision 23b0f90ba871f096474e1c27c3d14f455189d2d9)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 use std::collections::BTreeSet;
4 
5 use proc_macro2::{Ident, TokenStream, TokenTree};
6 use quote::quote_spanned;
7 
8 /// Please see [`crate::fmt`] for documentation.
9 pub(crate) fn fmt(input: TokenStream) -> TokenStream {
10     let mut input = input.into_iter();
11 
12     let first_opt = input.next();
13     let first_owned_str;
14     let mut names = BTreeSet::new();
15     let first_span = {
16         let Some((mut first_str, first_span)) = (match first_opt.as_ref() {
17             Some(TokenTree::Literal(first_lit)) => {
18                 first_owned_str = first_lit.to_string();
19                 Some(first_owned_str.as_str()).and_then(|first| {
20                     let first = first.strip_prefix('"')?;
21                     let first = first.strip_suffix('"')?;
22                     Some((first, first_lit.span()))
23                 })
24             }
25             _ => None,
26         }) else {
27             return first_opt.into_iter().chain(input).collect();
28         };
29 
30         // Parse `identifier`s from the format string.
31         //
32         // See https://doc.rust-lang.org/std/fmt/index.html#syntax.
33         while let Some((_, rest)) = first_str.split_once('{') {
34             first_str = rest;
35             if let Some(rest) = first_str.strip_prefix('{') {
36                 first_str = rest;
37                 continue;
38             }
39             if let Some((name, rest)) = first_str.split_once('}') {
40                 first_str = rest;
41                 let name = name.split_once(':').map_or(name, |(name, _)| name);
42                 if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
43                     names.insert(name);
44                 }
45             }
46         }
47         first_span
48     };
49 
50     let adapter = quote_spanned!(first_span => ::kernel::fmt::Adapter);
51 
52     let mut args = TokenStream::from_iter(first_opt);
53     {
54         let mut flush = |args: &mut TokenStream, current: &mut TokenStream| {
55             let current = std::mem::take(current);
56             if !current.is_empty() {
57                 let (lhs, rhs) = (|| {
58                     let mut current = current.into_iter();
59                     let mut acc = TokenStream::new();
60                     while let Some(tt) = current.next() {
61                         // Split on `=` only once to handle cases like `a = b = c`.
62                         if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '=') {
63                             names.remove(acc.to_string().as_str());
64                             // Include the `=` itself to keep the handling below uniform.
65                             acc.extend([tt]);
66                             return (Some(acc), current.collect::<TokenStream>());
67                         }
68                         acc.extend([tt]);
69                     }
70                     (None, acc)
71                 })();
72                 args.extend(quote_spanned!(first_span => #lhs #adapter(&(#rhs))));
73             }
74         };
75 
76         let mut current = TokenStream::new();
77         for tt in input {
78             match &tt {
79                 TokenTree::Punct(p) if p.as_char() == ',' => {
80                     flush(&mut args, &mut current);
81                     &mut args
82                 }
83                 _ => &mut current,
84             }
85             .extend([tt]);
86         }
87         flush(&mut args, &mut current);
88     }
89 
90     for name in names {
91         let name = Ident::new(name, first_span);
92         args.extend(quote_spanned!(first_span => , #name = #adapter(&#name)));
93     }
94 
95     quote_spanned!(first_span => ::core::format_args!(#args))
96 }
97