1
0

ifmt.rs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. use std::{collections::HashSet, str::FromStr};
  2. use proc_macro2::{Span, TokenStream};
  3. use quote::{quote, ToTokens, TokenStreamExt};
  4. use syn::{
  5. parse::{Parse, ParseStream},
  6. *,
  7. };
  8. pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
  9. Ok(input.into_token_stream())
  10. }
  11. #[allow(dead_code)] // dumb compiler does not see the struct being used...
  12. #[derive(Debug, PartialEq, Eq, Clone, Hash)]
  13. pub struct IfmtInput {
  14. pub source: Option<LitStr>,
  15. pub segments: Vec<Segment>,
  16. }
  17. impl IfmtInput {
  18. pub fn is_static(&self) -> bool {
  19. matches!(self.segments.as_slice(), &[Segment::Literal(_)] | &[])
  20. }
  21. }
  22. impl IfmtInput {
  23. pub fn to_static(&self) -> Option<String> {
  24. self.segments
  25. .iter()
  26. .try_fold(String::new(), |acc, segment| {
  27. if let Segment::Literal(seg) = segment {
  28. Some(acc + seg)
  29. } else {
  30. None
  31. }
  32. })
  33. }
  34. }
  35. impl FromStr for IfmtInput {
  36. type Err = syn::Error;
  37. fn from_str(input: &str) -> Result<Self> {
  38. let mut chars = input.chars().peekable();
  39. let mut segments = Vec::new();
  40. let mut current_literal = String::new();
  41. while let Some(c) = chars.next() {
  42. if c == '{' {
  43. if let Some(c) = chars.next_if(|c| *c == '{') {
  44. current_literal.push(c);
  45. continue;
  46. }
  47. if !current_literal.is_empty() {
  48. segments.push(Segment::Literal(current_literal));
  49. }
  50. current_literal = String::new();
  51. let mut current_captured = String::new();
  52. while let Some(c) = chars.next() {
  53. if c == ':' {
  54. let mut current_format_args = String::new();
  55. for c in chars.by_ref() {
  56. if c == '}' {
  57. segments.push(Segment::Formatted(FormattedSegment {
  58. format_args: current_format_args,
  59. segment: FormattedSegmentType::parse(&current_captured)?,
  60. }));
  61. break;
  62. }
  63. current_format_args.push(c);
  64. }
  65. break;
  66. }
  67. if c == '}' {
  68. segments.push(Segment::Formatted(FormattedSegment {
  69. format_args: String::new(),
  70. segment: FormattedSegmentType::parse(&current_captured)?,
  71. }));
  72. break;
  73. }
  74. current_captured.push(c);
  75. }
  76. } else {
  77. if '}' == c {
  78. if let Some(c) = chars.next_if(|c| *c == '}') {
  79. current_literal.push(c);
  80. continue;
  81. } else {
  82. return Err(Error::new(
  83. Span::call_site(),
  84. "unmatched closing '}' in format string",
  85. ));
  86. }
  87. }
  88. current_literal.push(c);
  89. }
  90. }
  91. if !current_literal.is_empty() {
  92. segments.push(Segment::Literal(current_literal));
  93. }
  94. Ok(Self {
  95. segments,
  96. source: None,
  97. })
  98. }
  99. }
  100. impl ToTokens for IfmtInput {
  101. fn to_tokens(&self, tokens: &mut TokenStream) {
  102. // build format_literal
  103. let mut format_literal = String::new();
  104. let mut expr_counter = 0;
  105. for segment in self.segments.iter() {
  106. match segment {
  107. Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
  108. Segment::Formatted(FormattedSegment {
  109. format_args,
  110. segment,
  111. }) => {
  112. format_literal += "{";
  113. match segment {
  114. FormattedSegmentType::Expr(_) => {
  115. format_literal += &expr_counter.to_string();
  116. expr_counter += 1;
  117. }
  118. FormattedSegmentType::Ident(ident) => {
  119. format_literal += &ident.to_string();
  120. }
  121. }
  122. format_literal += ":";
  123. format_literal += format_args;
  124. format_literal += "}";
  125. }
  126. }
  127. }
  128. let positional_args = self.segments.iter().filter_map(|seg| {
  129. if let Segment::Formatted(FormattedSegment {
  130. segment: FormattedSegmentType::Expr(expr),
  131. ..
  132. }) = seg
  133. {
  134. Some(expr)
  135. } else {
  136. None
  137. }
  138. });
  139. // remove duplicate idents
  140. let named_args_idents: HashSet<_> = self
  141. .segments
  142. .iter()
  143. .filter_map(|seg| {
  144. if let Segment::Formatted(FormattedSegment {
  145. segment: FormattedSegmentType::Ident(ident),
  146. ..
  147. }) = seg
  148. {
  149. Some(ident)
  150. } else {
  151. None
  152. }
  153. })
  154. .collect();
  155. let named_args = named_args_idents
  156. .iter()
  157. .map(|ident| quote!(#ident = #ident));
  158. quote! {
  159. format_args!(
  160. #format_literal
  161. #(, #positional_args)*
  162. #(, #named_args)*
  163. )
  164. }
  165. .to_tokens(tokens)
  166. }
  167. }
  168. #[derive(Debug, PartialEq, Eq, Clone, Hash)]
  169. pub enum Segment {
  170. Literal(String),
  171. Formatted(FormattedSegment),
  172. }
  173. #[derive(Debug, PartialEq, Eq, Clone, Hash)]
  174. pub struct FormattedSegment {
  175. format_args: String,
  176. segment: FormattedSegmentType,
  177. }
  178. impl ToTokens for FormattedSegment {
  179. fn to_tokens(&self, tokens: &mut TokenStream) {
  180. let (fmt, seg) = (&self.format_args, &self.segment);
  181. let fmt = format!("{{0:{}}}", fmt);
  182. tokens.append_all(quote! {
  183. format_args!(#fmt, #seg)
  184. });
  185. }
  186. }
  187. #[derive(Debug, PartialEq, Eq, Clone, Hash)]
  188. pub enum FormattedSegmentType {
  189. Expr(Box<Expr>),
  190. Ident(Ident),
  191. }
  192. impl FormattedSegmentType {
  193. fn parse(input: &str) -> Result<Self> {
  194. if let Ok(ident) = parse_str::<Ident>(input) {
  195. if ident == input {
  196. return Ok(Self::Ident(ident));
  197. }
  198. }
  199. if let Ok(expr) = parse_str(input) {
  200. Ok(Self::Expr(Box::new(expr)))
  201. } else {
  202. Err(Error::new(
  203. Span::call_site(),
  204. "Expected Ident or Expression",
  205. ))
  206. }
  207. }
  208. }
  209. impl ToTokens for FormattedSegmentType {
  210. fn to_tokens(&self, tokens: &mut TokenStream) {
  211. match self {
  212. Self::Expr(expr) => expr.to_tokens(tokens),
  213. Self::Ident(ident) => ident.to_tokens(tokens),
  214. }
  215. }
  216. }
  217. impl Parse for IfmtInput {
  218. fn parse(input: ParseStream) -> Result<Self> {
  219. let input: LitStr = input.parse()?;
  220. let input_str = input.value();
  221. let mut ifmt = IfmtInput::from_str(&input_str)?;
  222. ifmt.source = Some(input);
  223. Ok(ifmt)
  224. }
  225. }