// SPDX-License-Identifier: GPL-2.0use std::collections::BTreeSet;use proc_macro2::{Ident, TokenStream, TokenTree};use quote::quote_spanned;/// Please see [`crate::fmt`] for documentation.pub(crate) fn fmt(input: TokenStream) -> TokenStream { let mut input = input.into_iter(); let first_opt = input.next(); let first_owned_str; let mut names = BTreeSet::new(); let first_span = { let Some((mut first_str, first_span)) = (match first_opt.as_ref() { Some(TokenTree::Literal(first_lit)) => { first_owned_str = first_lit.to_string(); Some(first_owned_str.as_str()).and_then(|first| { let first = first.strip_prefix('"')?; let first = first.strip_suffix('"')?; Some((first, first_lit.span())) }) } _ => None, }) else { return first_opt.into_iter().chain(input).collect(); }; // Parse `identifier`s from the format string. // // See https://doc.rust-lang.org/std/fmt/index.html#syntax. while let Some((_, rest)) = first_str.split_once('{') { first_str = rest; if let Some(rest) = first_str.strip_prefix('{') { first_str = rest; continue; } if let Some((name, rest)) = first_str.split_once('}') { first_str = rest; let name = name.split_once(':').map_or(name, |(name, _)| name); if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) { names.insert(name); } } } first_span }; let adapter = quote_spanned!(first_span => ::kernel::fmt::Adapter); let mut args = TokenStream::from_iter(first_opt); { let mut flush = |args: &mut TokenStream, current: &mut TokenStream| { let current = std::mem::take(current); if !current.is_empty() { let (lhs, rhs) = (|| { let mut current = current.into_iter(); let mut acc = TokenStream::new(); while let Some(tt) = current.next() { // Split on `=` only once to handle cases like `a = b = c`. if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '=') { names.remove(acc.to_string().as_str()); // Include the `=` itself to keep the handling below uniform. acc.extend([tt]); return (Some(acc), current.collect::<TokenStream>()); } acc.extend([tt]); } (None, acc) })(); args.extend(quote_spanned!(first_span => #lhs #adapter(&(#rhs)))); } }; let mut current = TokenStream::new(); for tt in input { match &tt { TokenTree::Punct(p) if p.as_char() == ',' => { flush(&mut args, &mut current); &mut args } _ => &mut current, } .extend([tt]); } flush(&mut args, &mut current); } for name in names { let name = Ident::new(name, first_span); args.extend(quote_spanned!(first_span => , #name = #adapter(&#name))); } quote_spanned!(first_span => ::core::format_args!(#args))}
// SPDX-License-Identifier: GPL-2.0
use std::collections::BTreeSet;
use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::quote_spanned;
/// Please see [`crate::fmt`] for documentation.
pub(crate) fn fmt(input: TokenStream) -> TokenStream {
let mut input = input.into_iter();
let first_opt = input.next();
let first_owned_str;
let mut names = BTreeSet::new();
let first_span = {
let Some((mut first_str, first_span)) = (match first_opt.as_ref() {
Some(TokenTree::Literal(first_lit)) => {
first_owned_str = first_lit.to_string();
Some(first_owned_str.as_str()).and_then(|first| {
let first = first.strip_prefix('"')?;
let first = first.strip_suffix('"')?;
Some((first, first_lit.span()))
})
}
_ => None,
}) else {
return first_opt.into_iter().chain(input).collect();
};
// Parse `identifier`s from the format string.
//
// See https://doc.rust-lang.org/std/fmt/index.html#syntax.
while let Some((_, rest)) = first_str.split_once('{') {
first_str = rest;
if let Some(rest) = first_str.strip_prefix('{') {
first_str = rest;
continue;
}
if let Some((name, rest)) = first_str.split_once('}') {
first_str = rest;
let name = name.split_once(':').map_or(name, |(name, _)| name);
if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
names.insert(name);
}
}
}
first_span
};
let adapter = quote_spanned!(first_span => ::kernel::fmt::Adapter);
let mut args = TokenStream::from_iter(first_opt);
{
let mut flush = |args: &mut TokenStream, current: &mut TokenStream| {
let current = std::mem::take(current);
if !current.is_empty() {
let (lhs, rhs) = (|| {
let mut current = current.into_iter();
let mut acc = TokenStream::new();
while let Some(tt) = current.next() {
// Split on `=` only once to handle cases like `a = b = c`.
if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '=') {
names.remove(acc.to_string().as_str());
// Include the `=` itself to keep the handling below uniform.
acc.extend([tt]);
return (Some(acc), current.collect::<TokenStream>());
}
acc.extend([tt]);
}
(None, acc)
})();
args.extend(quote_spanned!(first_span => #lhs #adapter(&(#rhs))));
}
};
let mut current = TokenStream::new();
for tt in input {
match &tt {
TokenTree::Punct(p) if p.as_char() == ',' => {
flush(&mut args, &mut current);
&mut args
}
_ => &mut current,
}
.extend([tt]);
}
flush(&mut args, &mut current);
}
for name in names {
let name = Ident::new(name, first_span);
args.extend(quote_spanned!(first_span => , #name = #adapter(&#name)));
}
quote_spanned!(first_span => ::core::format_args!(#args))
}