Skip to content

Commit fa97b63

Browse files
committed
feat: support more formats for interpolation string
1 parent 0e67481 commit fa97b63

File tree

1 file changed

+35
-29
lines changed

1 file changed

+35
-29
lines changed

macros/src/lexer.rs

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)