fmt.rs 3.5 KB

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