ifmt.rs 8.4 KB

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