| 1 | |
| 2 |
|
| 3 | use std::collections::BTreeSet;
|
| 4 |
|
| 5 | use proc_macro2::{Ident, TokenStream, TokenTree};
|
| 6 | use quote::quote_spanned;
|
| 7 |
|
| 8 | |
| 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 | |
| 31 | |
| 32 | |
| 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 | |
| 62 | if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '=') {
|
| 63 | names.remove(acc.to_string().as_str());
|
| 64 | |
| 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 | }
|