@@ -103,7 +103,12 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
103103 output. push ( chr) ;
104104 extracted_expressions. push ( Arg :: Placeholder ) ;
105105 state = State :: NotArg ;
106- }
106+ } ,
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 ( '\\' ) ;
@@ -117,49 +122,40 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
117122 } else {
118123 state = State :: Expr ;
119124 }
120- }
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- }
125+ } ,
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 ( ) ;
144- }
145- ( State :: Ident | State :: Expr , ':' ) => {
131+ } ,
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 == ':' { State :: FormatOpts } else if chr == '}' { State :: NotArg } else { unreachable ! ( ) } ;
150+ } else if chr == '}' {
151+ // We're closing one brace met before inside of the expression.
152+ current_expr. push ( chr) ;
153+ inexpr_open_count -= 1 ;
154+ } else if chr == ':' {
159155 // We're inside of braced expression, assume that it's a struct field name/value delimiter.
160156 current_expr. push ( chr) ;
161157 }
162- }
158+ } ,
163159 ( State :: Ident | State :: Expr , '{' ) => {
164160 state = State :: Expr ;
165161 current_expr. push ( chr) ;
@@ -219,6 +215,10 @@ mod tests {
219215 ( "{expr} is {2 + 2}" , expect ! [ [ "{} is {}; expr, 2 + 2" ] ] ) ,
220216 ( "{expr:?}" , expect ! [ [ "{:?}; expr" ] ] ) ,
221217 ( "{expr:1$}" , expect ! [ [ r"{:1\$}; expr" ] ] ) ,
218+ ( "{:1$}" , expect ! [ [ r"{:1\$}; $1" ] ] ) ,
219+ ( "{:>padding$}" , expect ! [ [ r"{:>padding\$}; $1" ] ] ) ,
220+ ( "{}, {}, {0}" , expect ! [ [ r"{}, {}, {0}; $1, $2" ] ] ) ,
221+ ( "{}, {}, {0:b}" , expect ! [ [ r"{}, {}, {0:b}; $1, $2" ] ] ) ,
222222 ( "{$0}" , expect ! [ [ r"{}; \$0" ] ] ) ,
223223 ( "{malformed" , expect ! [ [ "-" ] ] ) ,
224224 ( "malformed}" , expect ! [ [ "-" ] ] ) ,
0 commit comments