ifmt.rs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. use std::str::FromStr;
  2. use proc_macro2::{Span, TokenStream};
  3. use quote::{quote, quote_spanned, 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. /// Try to convert this into a single _.to_string() call if possible
  59. ///
  60. /// Using "{single_expression}" is pretty common, but you don't need to go through the whole format! machinery for that, so we optimize it here.
  61. fn try_to_string(&self) -> Option<TokenStream> {
  62. let mut single_dynamic = None;
  63. for segment in &self.segments {
  64. match segment {
  65. Segment::Literal(literal) => {
  66. if !literal.is_empty() {
  67. return None;
  68. }
  69. }
  70. Segment::Formatted(FormattedSegment {
  71. segment,
  72. format_args,
  73. }) => {
  74. if format_args.is_empty() {
  75. match single_dynamic {
  76. Some(current_string) => {
  77. single_dynamic =
  78. Some(quote!(#current_string + &(#segment).to_string()));
  79. }
  80. None => {
  81. single_dynamic = Some(quote!((#segment).to_string()));
  82. }
  83. }
  84. } else {
  85. return None;
  86. }
  87. }
  88. }
  89. }
  90. single_dynamic
  91. }
  92. }
  93. impl FromStr for IfmtInput {
  94. type Err = syn::Error;
  95. fn from_str(input: &str) -> Result<Self> {
  96. let mut chars = input.chars().peekable();
  97. let mut segments = Vec::new();
  98. let mut current_literal = String::new();
  99. while let Some(c) = chars.next() {
  100. if c == '{' {
  101. if let Some(c) = chars.next_if(|c| *c == '{') {
  102. current_literal.push(c);
  103. continue;
  104. }
  105. if !current_literal.is_empty() {
  106. segments.push(Segment::Literal(current_literal));
  107. }
  108. current_literal = String::new();
  109. let mut current_captured = String::new();
  110. while let Some(c) = chars.next() {
  111. if c == ':' {
  112. // two :s in a row is a path, not a format arg
  113. if chars.next_if(|c| *c == ':').is_some() {
  114. current_captured.push_str("::");
  115. continue;
  116. }
  117. let mut current_format_args = String::new();
  118. for c in chars.by_ref() {
  119. if c == '}' {
  120. segments.push(Segment::Formatted(FormattedSegment {
  121. format_args: current_format_args,
  122. segment: FormattedSegmentType::parse(&current_captured)?,
  123. }));
  124. break;
  125. }
  126. current_format_args.push(c);
  127. }
  128. break;
  129. }
  130. if c == '}' {
  131. segments.push(Segment::Formatted(FormattedSegment {
  132. format_args: String::new(),
  133. segment: FormattedSegmentType::parse(&current_captured)?,
  134. }));
  135. break;
  136. }
  137. current_captured.push(c);
  138. }
  139. } else {
  140. if '}' == c {
  141. if let Some(c) = chars.next_if(|c| *c == '}') {
  142. current_literal.push(c);
  143. continue;
  144. } else {
  145. return Err(Error::new(
  146. Span::call_site(),
  147. "unmatched closing '}' in format string",
  148. ));
  149. }
  150. }
  151. current_literal.push(c);
  152. }
  153. }
  154. if !current_literal.is_empty() {
  155. segments.push(Segment::Literal(current_literal));
  156. }
  157. Ok(Self {
  158. segments,
  159. source: None,
  160. })
  161. }
  162. }
  163. impl ToTokens for IfmtInput {
  164. fn to_tokens(&self, tokens: &mut TokenStream) {
  165. // Try to turn it into a single _.to_string() call
  166. if let Some(single_dynamic) = self.try_to_string() {
  167. tokens.extend(single_dynamic);
  168. return;
  169. }
  170. // build format_literal
  171. let mut format_literal = String::new();
  172. let mut expr_counter = 0;
  173. for segment in self.segments.iter() {
  174. match segment {
  175. Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
  176. Segment::Formatted(FormattedSegment { format_args, .. }) => {
  177. format_literal += "{";
  178. format_literal += &expr_counter.to_string();
  179. expr_counter += 1;
  180. format_literal += ":";
  181. format_literal += format_args;
  182. format_literal += "}";
  183. }
  184. }
  185. }
  186. let span = match self.source.as_ref() {
  187. Some(source) => source.span(),
  188. None => Span::call_site(),
  189. };
  190. let positional_args = self.segments.iter().filter_map(|seg| {
  191. if let Segment::Formatted(FormattedSegment { segment, .. }) = seg {
  192. let mut segment = segment.clone();
  193. // We set the span of the ident here, so that we can use it in diagnostics
  194. if let FormattedSegmentType::Ident(ident) = &mut segment {
  195. ident.set_span(span);
  196. }
  197. Some(segment)
  198. } else {
  199. None
  200. }
  201. });
  202. quote_spanned! {
  203. span =>
  204. ::std::format_args!(
  205. #format_literal
  206. #(, #positional_args)*
  207. )
  208. }
  209. .to_tokens(tokens)
  210. }
  211. }
  212. #[derive(Debug, PartialEq, Eq, Clone, Hash)]
  213. pub enum Segment {
  214. Literal(String),
  215. Formatted(FormattedSegment),
  216. }
  217. #[derive(Debug, PartialEq, Eq, Clone, Hash)]
  218. pub struct FormattedSegment {
  219. format_args: String,
  220. segment: FormattedSegmentType,
  221. }
  222. impl ToTokens for FormattedSegment {
  223. fn to_tokens(&self, tokens: &mut TokenStream) {
  224. let (fmt, seg) = (&self.format_args, &self.segment);
  225. let fmt = format!("{{0:{fmt}}}");
  226. tokens.append_all(quote! {
  227. format_args!(#fmt, #seg)
  228. });
  229. }
  230. }
  231. #[derive(Debug, PartialEq, Eq, Clone, Hash)]
  232. pub enum FormattedSegmentType {
  233. Expr(Box<Expr>),
  234. Ident(Ident),
  235. }
  236. impl FormattedSegmentType {
  237. fn parse(input: &str) -> Result<Self> {
  238. if let Ok(ident) = parse_str::<Ident>(input) {
  239. if ident == input {
  240. return Ok(Self::Ident(ident));
  241. }
  242. }
  243. if let Ok(expr) = parse_str(input) {
  244. Ok(Self::Expr(Box::new(expr)))
  245. } else {
  246. Err(Error::new(
  247. Span::call_site(),
  248. "Expected Ident or Expression",
  249. ))
  250. }
  251. }
  252. }
  253. impl ToTokens for FormattedSegmentType {
  254. fn to_tokens(&self, tokens: &mut TokenStream) {
  255. match self {
  256. Self::Expr(expr) => expr.to_tokens(tokens),
  257. Self::Ident(ident) => ident.to_tokens(tokens),
  258. }
  259. }
  260. }
  261. impl Parse for IfmtInput {
  262. fn parse(input: ParseStream) -> Result<Self> {
  263. let input: LitStr = input.parse()?;
  264. let input_str = input.value();
  265. let mut ifmt = IfmtInput::from_str(&input_str)?;
  266. ifmt.source = Some(input);
  267. Ok(ifmt)
  268. }
  269. }