use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, *, }; pub fn format_args_f_impl(input: IfmtInput) -> Result { // build format_literal let mut format_literal = String::new(); let mut expr_counter = 0; for segment in input.segments.iter() { match segment { Segment::Literal(s) => format_literal += &s, Segment::Formatted { format_args, segment, } => { format_literal += "{"; match segment { FormattedSegment::Expr(_) => { format_literal += &expr_counter.to_string(); expr_counter += 1; } FormattedSegment::Ident(ident) => { format_literal += &ident.to_string(); } } format_literal += ":"; format_literal += format_args; format_literal += "}"; } } } let positional_args = input.segments.iter().filter_map(|seg| { if let Segment::Formatted { segment, .. } = seg { if let FormattedSegment::Expr(expr) = segment { Some(expr) } else { None } } else { None } }); let named_args = input.segments.iter().filter_map(|seg| { if let Segment::Formatted { segment, .. } = seg { if let FormattedSegment::Ident(ident) = segment { Some(quote! {#ident = #ident}) } else { None } } else { None } }); Ok(quote! { format_args!( #format_literal #(, #positional_args)* #(, #named_args)* ) }) } #[allow(dead_code)] // dumb compiler does not see the struct being used... #[derive(Debug)] pub struct IfmtInput { pub segments: Vec, } impl IfmtInput { pub fn from_str(input: &str) -> Result { let mut chars = input.chars().peekable(); let mut segments = Vec::new(); let mut current_literal = String::new(); while let Some(c) = chars.next() { if c == '{' { if let Some(c) = chars.next_if(|c| *c == '{') { current_literal.push(c); continue; } segments.push(Segment::Literal(current_literal)); current_literal = String::new(); let mut current_captured = String::new(); while let Some(c) = chars.next() { if c == ':' { let mut current_format_args = String::new(); while let Some(c) = chars.next() { if c == '}' { segments.push(Segment::Formatted { format_args: current_format_args, segment: FormattedSegment::parse(¤t_captured)?, }); break; } current_format_args.push(c); } break; } if c == '}' { segments.push(Segment::Formatted { format_args: String::new(), segment: FormattedSegment::parse(¤t_captured)?, }); break; } current_captured.push(c); } } else { current_literal.push(c); } } segments.push(Segment::Literal(current_literal)); Ok(Self { segments }) } } #[derive(Debug)] pub enum Segment { Literal(String), Formatted { format_args: String, segment: FormattedSegment, }, } #[derive(Debug)] pub enum FormattedSegment { Expr(Expr), Ident(Ident), } impl FormattedSegment { fn parse(input: &str) -> Result { if let Ok(ident) = parse_str::(input) { if &ident.to_string() == input { return Ok(Self::Ident(ident)); } } // if let Ok(expr) = parse_str(&("{".to_string() + input + "}")) { if let Ok(expr) = parse_str(input) { Ok(Self::Expr(expr)) } else { Err(Error::new( Span::call_site(), "Expected Ident or Expression", )) } } } impl ToTokens for FormattedSegment { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Expr(expr) => expr.to_tokens(tokens), Self::Ident(ident) => ident.to_tokens(tokens), } } } impl Parse for IfmtInput { fn parse(input: ParseStream) -> Result { let input: LitStr = input.parse()?; let input_str = input.value(); IfmtInput::from_str(&input_str) } }