Skip to main content

macros/
fmt.rs

1// SPDX-License-Identifier: GPL-2.0
2
3use std::collections::BTreeSet;
4
5use proc_macro2::{Ident, TokenStream, TokenTree};
6use quote::quote_spanned;
7
8/// Please see [`crate::fmt`] for documentation.
9pub(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}