@@ -7,7 +7,8 @@ use std::str::Chars;
77
88/// Scan string literal to tokenstream, used by most of the macros
99///
10- /// - support $var, ${var} or ${var:?} for interpolation
10+ /// - support $var, ${var} or ${var:fmt} for interpolation, where `fmt` can be any
11+ /// of the standard Rust formatting specifiers (e.g., `?`, `x`, `X`, `o`, `b`, `p`, `e`, `E`).
1112/// - to escape '$' itself, use "$$"
1213/// - support normal rust character escapes:
1314/// https://doc.rust-lang.org/reference/tokens.html#ascii-escapes
@@ -49,53 +50,58 @@ pub fn scan_str_lit(lit: &Literal) -> TokenStream {
4950 // Before handling a variable, append any accumulated literal part.
5051 seal_current_literal_part ( & mut output, & mut current_literal_part) ;
5152
52- let mut debug_format = false ; // New flag for debug formatting
53+ let mut format_specifier = String :: new ( ) ; // To store the fmt specifier (e.g., "?", "x", "#x")
54+ let mut is_braced_interpolation = false ;
5355
5456 // Check for '{' to start a braced interpolation
5557 if chars. peek ( ) == Some ( & '{' ) {
58+ is_braced_interpolation = true ;
5659 chars. next ( ) ; // Consume '{'
60+ }
5761
58- let var_name = parse_variable_name ( & mut chars) ;
62+ let var_name = parse_variable_name ( & mut chars) ;
5963
60- // After variable name, check for ':?' for debug formatting
64+ if is_braced_interpolation {
65+ // If it's braced, we might have a format specifier or it might just be empty braces.
6166 if chars. peek ( ) == Some ( & ':' ) {
6267 chars. next ( ) ; // Consume ':'
63- if chars. peek ( ) == Some ( & '?' ) {
64- chars. next ( ) ; // Consume '?'
65- debug_format = true ;
66- } else {
67- // If it's ':' but not ':?', then it's a malformed substitution
68- abort ! ( lit. span( ) , "bad substitution: expected '?' after ':'" ) ;
68+ // Read the format specifier until '}'
69+ while let Some ( & c) = chars. peek ( ) {
70+ if c == '}' {
71+ break ;
72+ }
73+ format_specifier. push ( c) ;
74+ chars. next ( ) ; // Consume the character of the specifier
6975 }
7076 }
7177
7278 // Expect '}' to close the braced interpolation
7379 if chars. next ( ) != Some ( '}' ) {
7480 abort ! ( lit. span( ) , "bad substitution: expected '}'" ) ;
7581 }
82+ }
7683
77- if !var_name. is_empty ( ) {
78- let var_ident = syn:: parse_str :: < Ident > ( & var_name) . unwrap ( ) ;
79- if debug_format {
80- output. extend ( quote ! ( . append( format!( "{:?}" , #var_ident) ) ) ) ;
81- } else {
82- output. extend ( quote ! ( . append( format!( "{}" , #var_ident) ) ) ) ;
84+ if !var_name. is_empty ( ) {
85+ let var_ident = syn:: parse_str :: < Ident > ( & var_name) . unwrap ( ) ;
86+
87+ // To correctly handle all format specifiers (like {:02X}), we need to insert the
88+ // entire format string *as a literal* into the format! macro.
89+ // The `format_specifier` string itself needs to be embedded.
90+ let format_macro_call = if format_specifier. is_empty ( ) {
91+ quote ! {
92+ . append( format!( "{}" , #var_ident) )
8393 }
8494 } else {
85- // This covers cases like "${}" or "${:?}" with empty variable name
86- output. extend ( quote ! ( . append( "$" ) ) ) ;
87- }
95+ let format_literal_str = format ! ( "{{:{}}}" , format_specifier) ;
96+ let format_literal_token = Literal :: string ( & format_literal_str) ;
97+ quote ! {
98+ . append( format!( #format_literal_token, #var_ident) )
99+ }
100+ } ;
101+ output. extend ( format_macro_call) ;
88102 } else {
89- // Handle bare $var (no braces)
90- let var_name = parse_variable_name ( & mut chars) ;
91- if !var_name. is_empty ( ) {
92- let var_ident = syn:: parse_str :: < Ident > ( & var_name) . unwrap ( ) ;
93- output. extend ( quote ! ( . append( format!( "{}" , #var_ident) ) ) ) ;
94- } else {
95- // If '$' is not followed by a valid variable name or a valid brace,
96- // treat it as a literal "$".
97- output. extend ( quote ! ( . append( "$" ) ) ) ;
98- }
103+ // This covers cases like "${}" or "${:?}" with empty variable name
104+ output. extend ( quote ! ( . append( "$" ) ) ) ;
99105 }
100106 } else {
101107 current_literal_part. push ( ch) ;
0 commit comments