ifmt.rs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. use std::str::FromStr;
  2. use proc_macro2::{Span, TokenStream};
  3. use quote::{quote, ToTokens};
  4. use syn::{
  5. parse::{Parse, ParseStream},
  6. *,
  7. };
  8. pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
  9. // build format_literal
  10. let mut format_literal = String::new();
  11. let mut expr_counter = 0;
  12. for segment in input.segments.iter() {
  13. match segment {
  14. Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
  15. Segment::Formatted {
  16. format_args,
  17. segment,
  18. } => {
  19. format_literal += "{";
  20. match segment {
  21. FormattedSegment::Expr(_) => {
  22. format_literal += &expr_counter.to_string();
  23. expr_counter += 1;
  24. }
  25. FormattedSegment::Ident(ident) => {
  26. format_literal += &ident.to_string();
  27. }
  28. }
  29. format_literal += ":";
  30. format_literal += format_args;
  31. format_literal += "}";
  32. }
  33. }
  34. }
  35. let positional_args = input.segments.iter().filter_map(|seg| {
  36. if let Segment::Formatted {
  37. segment: FormattedSegment::Expr(expr),
  38. ..
  39. } = seg
  40. {
  41. Some(expr)
  42. } else {
  43. None
  44. }
  45. });
  46. let named_args = input.segments.iter().filter_map(|seg| {
  47. if let Segment::Formatted {
  48. segment: FormattedSegment::Ident(ident),
  49. ..
  50. } = seg
  51. {
  52. Some(quote! {#ident = #ident})
  53. } else {
  54. None
  55. }
  56. });
  57. Ok(quote! {
  58. format_args!(
  59. #format_literal
  60. #(, #positional_args)*
  61. #(, #named_args)*
  62. )
  63. })
  64. }
  65. #[allow(dead_code)] // dumb compiler does not see the struct being used...
  66. #[derive(Debug)]
  67. pub struct IfmtInput {
  68. pub segments: Vec<Segment>,
  69. }
  70. impl FromStr for IfmtInput {
  71. type Err = syn::Error;
  72. fn from_str(input: &str) -> Result<Self> {
  73. let mut chars = input.chars().peekable();
  74. let mut segments = Vec::new();
  75. let mut current_literal = String::new();
  76. while let Some(c) = chars.next() {
  77. if c == '{' {
  78. if let Some(c) = chars.next_if(|c| *c == '{') {
  79. current_literal.push(c);
  80. continue;
  81. }
  82. segments.push(Segment::Literal(current_literal));
  83. current_literal = String::new();
  84. let mut current_captured = String::new();
  85. while let Some(c) = chars.next() {
  86. if c == ':' {
  87. let mut current_format_args = String::new();
  88. while let Some(c) = chars.next() {
  89. if c == '}' {
  90. segments.push(Segment::Formatted {
  91. format_args: current_format_args,
  92. segment: FormattedSegment::parse(&current_captured)?,
  93. });
  94. break;
  95. }
  96. current_format_args.push(c);
  97. }
  98. break;
  99. }
  100. if c == '}' {
  101. segments.push(Segment::Formatted {
  102. format_args: String::new(),
  103. segment: FormattedSegment::parse(&current_captured)?,
  104. });
  105. break;
  106. }
  107. current_captured.push(c);
  108. }
  109. } else {
  110. if '}' == c {
  111. if let Some(c) = chars.next_if(|c| *c == '}') {
  112. current_literal.push(c);
  113. continue;
  114. } else {
  115. return Err(Error::new(
  116. Span::call_site(),
  117. "unmatched closing '}' in format string",
  118. ));
  119. }
  120. }
  121. current_literal.push(c);
  122. }
  123. }
  124. segments.push(Segment::Literal(current_literal));
  125. Ok(Self { segments })
  126. }
  127. }
  128. #[derive(Debug)]
  129. pub enum Segment {
  130. Literal(String),
  131. Formatted {
  132. format_args: String,
  133. segment: FormattedSegment,
  134. },
  135. }
  136. #[derive(Debug)]
  137. pub enum FormattedSegment {
  138. Expr(Box<Expr>),
  139. Ident(Ident),
  140. }
  141. impl FormattedSegment {
  142. fn parse(input: &str) -> Result<Self> {
  143. if let Ok(ident) = parse_str::<Ident>(input) {
  144. if ident == input {
  145. return Ok(Self::Ident(ident));
  146. }
  147. }
  148. if let Ok(expr) = parse_str(input) {
  149. Ok(Self::Expr(Box::new(expr)))
  150. } else {
  151. Err(Error::new(
  152. Span::call_site(),
  153. "Expected Ident or Expression",
  154. ))
  155. }
  156. }
  157. }
  158. impl ToTokens for FormattedSegment {
  159. fn to_tokens(&self, tokens: &mut TokenStream) {
  160. match self {
  161. Self::Expr(expr) => expr.to_tokens(tokens),
  162. Self::Ident(ident) => ident.to_tokens(tokens),
  163. }
  164. }
  165. }
  166. impl Parse for IfmtInput {
  167. fn parse(input: ParseStream) -> Result<Self> {
  168. let input: LitStr = input.parse()?;
  169. let input_str = input.value();
  170. IfmtInput::from_str(&input_str)
  171. }
  172. }