@@ -104,6 +104,11 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
104
104
extracted_expressions. push ( Arg :: Placeholder ) ;
105
105
state = State :: NotArg ;
106
106
}
107
+ ( State :: MaybeArg , ':' ) => {
108
+ output. push ( chr) ;
109
+ extracted_expressions. push ( Arg :: Placeholder ) ;
110
+ state = State :: FormatOpts ;
111
+ }
107
112
( State :: MaybeArg , _) => {
108
113
if matches ! ( chr, '\\' | '$' ) {
109
114
current_expr. push ( '\\' ) ;
@@ -118,44 +123,41 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
118
123
state = State :: Expr ;
119
124
}
120
125
}
121
- ( State :: Ident | State :: Expr , '}' ) => {
122
- if inexpr_open_count == 0 {
123
- output. push ( chr) ;
124
-
125
- if matches ! ( state, State :: Expr ) {
126
- extracted_expressions. push ( Arg :: Expr ( current_expr. trim ( ) . into ( ) ) ) ;
127
- } else {
128
- extracted_expressions. push ( Arg :: Ident ( current_expr. trim ( ) . into ( ) ) ) ;
129
- }
130
-
131
- current_expr = String :: new ( ) ;
132
- state = State :: NotArg ;
133
- } else {
134
- // We're closing one brace met before inside of the expression.
135
- current_expr. push ( chr) ;
136
- inexpr_open_count -= 1 ;
137
- }
138
- }
139
126
( State :: Ident | State :: Expr , ':' ) if matches ! ( chars. peek( ) , Some ( ':' ) ) => {
140
127
// path separator
141
128
state = State :: Expr ;
142
129
current_expr. push_str ( "::" ) ;
143
130
chars. next ( ) ;
144
131
}
145
- ( State :: Ident | State :: Expr , ':' ) => {
132
+ ( State :: Ident | State :: Expr , ':' | '}' ) => {
146
133
if inexpr_open_count == 0 {
147
- // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
148
- output. push ( chr) ;
134
+ let trimmed = current_expr. trim ( ) ;
149
135
150
- if matches ! ( state, State :: Expr ) {
151
- extracted_expressions. push ( Arg :: Expr ( current_expr. trim ( ) . into ( ) ) ) ;
136
+ // if the expression consists of a single number, like "0" or "12", it can refer to
137
+ // format args in the order they are specified.
138
+ // see: https://doc.rust-lang.org/std/fmt/#positional-parameters
139
+ if trimmed. chars ( ) . fold ( true , |only_num, c| c. is_ascii_digit ( ) && only_num) {
140
+ output. push_str ( trimmed) ;
141
+ } else if matches ! ( state, State :: Expr ) {
142
+ extracted_expressions. push ( Arg :: Expr ( trimmed. into ( ) ) ) ;
152
143
} else {
153
- extracted_expressions. push ( Arg :: Ident ( current_expr . trim ( ) . into ( ) ) ) ;
144
+ extracted_expressions. push ( Arg :: Ident ( trimmed . into ( ) ) ) ;
154
145
}
155
146
156
- current_expr = String :: new ( ) ;
157
- state = State :: FormatOpts ;
158
- } else {
147
+ output. push ( chr) ;
148
+ current_expr. clear ( ) ;
149
+ state = if chr == ':' {
150
+ State :: FormatOpts
151
+ } else if chr == '}' {
152
+ State :: NotArg
153
+ } else {
154
+ unreachable ! ( )
155
+ } ;
156
+ } else if chr == '}' {
157
+ // We're closing one brace met before inside of the expression.
158
+ current_expr. push ( chr) ;
159
+ inexpr_open_count -= 1 ;
160
+ } else if chr == ':' {
159
161
// We're inside of braced expression, assume that it's a struct field name/value delimiter.
160
162
current_expr. push ( chr) ;
161
163
}
@@ -219,6 +221,10 @@ mod tests {
219
221
( "{expr} is {2 + 2}" , expect ! [ [ "{} is {}; expr, 2 + 2" ] ] ) ,
220
222
( "{expr:?}" , expect ! [ [ "{:?}; expr" ] ] ) ,
221
223
( "{expr:1$}" , expect ! [ [ r"{:1\$}; expr" ] ] ) ,
224
+ ( "{:1$}" , expect ! [ [ r"{:1\$}; $1" ] ] ) ,
225
+ ( "{:>padding$}" , expect ! [ [ r"{:>padding\$}; $1" ] ] ) ,
226
+ ( "{}, {}, {0}" , expect ! [ [ r"{}, {}, {0}; $1, $2" ] ] ) ,
227
+ ( "{}, {}, {0:b}" , expect ! [ [ r"{}, {}, {0:b}; $1, $2" ] ] ) ,
222
228
( "{$0}" , expect ! [ [ r"{}; \$0" ] ] ) ,
223
229
( "{malformed" , expect ! [ [ "-" ] ] ) ,
224
230
( "malformed}" , expect ! [ [ "-" ] ] ) ,
0 commit comments