96 lines · 3.5 KB · Rust
RawDownload
// 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))}