@@ -104,6 +104,11 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
104104 extracted_expressions. push ( Arg :: Placeholder ) ;
105105 state = State :: NotArg ;
106106 }
107+ ( State :: MaybeArg , ':' ) => {
108+ output. push ( chr) ;
109+ extracted_expressions. push ( Arg :: Placeholder ) ;
110+ state = State :: FormatOpts ;
111+ }
107112 ( State :: MaybeArg , _) => {
108113 if matches ! ( chr, '\\' | '$' ) {
109114 current_expr. push ( '\\' ) ;
@@ -118,44 +123,41 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
118123 state = State :: Expr ;
119124 }
120125 }
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- }
139126 ( State :: Ident | State :: Expr , ':' ) if matches ! ( chars. peek( ) , Some ( ':' ) ) => {
140127 // path separator
141128 state = State :: Expr ;
142129 current_expr. push_str ( "::" ) ;
143130 chars. next ( ) ;
144131 }
145- ( State :: Ident | State :: Expr , ':' ) => {
132+ ( State :: Ident | State :: Expr , ':' | '}' ) => {
146133 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 ( ) ;
149135
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 ( ) ) ) ;
152143 } else {
153- extracted_expressions. push ( Arg :: Ident ( current_expr . trim ( ) . into ( ) ) ) ;
144+ extracted_expressions. push ( Arg :: Ident ( trimmed . into ( ) ) ) ;
154145 }
155146
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 == ':' {
159161 // We're inside of braced expression, assume that it's a struct field name/value delimiter.
160162 current_expr. push ( chr) ;
161163 }
@@ -219,6 +221,10 @@ mod tests {
219221 ( "{expr} is {2 + 2}" , expect ! [ [ "{} is {}; expr, 2 + 2" ] ] ) ,
220222 ( "{expr:?}" , expect ! [ [ "{:?}; expr" ] ] ) ,
221223 ( "{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" ] ] ) ,
222228 ( "{$0}" , expect ! [ [ r"{}; \$0" ] ] ) ,
223229 ( "{malformed" , expect ! [ [ "-" ] ] ) ,
224230 ( "malformed}" , expect ! [ [ "-" ] ] ) ,
0 commit comments