11use super :: UIfmt ;
22use alloy_primitives:: { Address , Bytes , FixedBytes , I256 , U256 } ;
3- use std:: iter:: Peekable ;
3+ use std:: fmt:: { self , Write } ;
4+
5+ /// A piece is a portion of the format string which represents the next part to emit.
6+ #[ derive( Clone , Debug , PartialEq , Eq ) ]
7+ pub enum Piece < ' a > {
8+ /// A literal string which should directly be emitted.
9+ String ( & ' a str ) ,
10+ /// A format specifier which should be replaced with the next argument.
11+ NextArgument ( FormatSpec ) ,
12+ }
413
514/// A format specifier.
6- #[ derive( Clone , Copy , Debug , Default , PartialEq , Eq ) ]
15+ #[ derive( Clone , Debug , Default , PartialEq , Eq ) ]
716pub enum FormatSpec {
8- /// %s format spec
17+ /// `%s`
918 #[ default]
1019 String ,
11- /// %d format spec
20+ /// `%d`
1221 Number ,
13- /// %i format spec
22+ /// `%i`
1423 Integer ,
15- /// %o format spec
24+ /// `%o`
1625 Object ,
17- /// %e format spec with an optional precision
26+ /// `%e`, `%18e`
1827 Exponential ( Option < usize > ) ,
19- /// %x format spec
28+ /// `%x`
2029 Hexadecimal ,
2130}
2231
23- impl FormatSpec {
24- fn from_chars < I > ( iter : & mut Peekable < I > ) -> Result < Self , String >
25- where
26- I : Iterator < Item = char > ,
27- {
28- match iter. next ( ) . ok_or_else ( String :: new) ? {
29- 's' => Ok ( Self :: String ) ,
30- 'd' => Ok ( Self :: Number ) ,
31- 'i' => Ok ( Self :: Integer ) ,
32- 'o' => Ok ( Self :: Object ) ,
33- 'e' => Ok ( Self :: Exponential ( None ) ) ,
34- 'x' => Ok ( Self :: Hexadecimal ) ,
35- ch if ch. is_ascii_digit ( ) => {
36- let mut num = ch. to_string ( ) ;
37- while let Some ( & ch) = iter. peek ( ) {
38- if ch. is_ascii_digit ( ) {
39- num. push ( ch) ;
40- iter. next ( ) ;
41- } else {
42- break ;
43- }
32+ impl fmt:: Display for FormatSpec {
33+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
34+ f. write_str ( "%" ) ?;
35+ match * self {
36+ Self :: String => f. write_str ( "s" ) ,
37+ Self :: Number => f. write_str ( "d" ) ,
38+ Self :: Integer => f. write_str ( "i" ) ,
39+ Self :: Object => f. write_str ( "o" ) ,
40+ Self :: Exponential ( Some ( n) ) => write ! ( f, "{n}e" ) ,
41+ Self :: Exponential ( None ) => f. write_str ( "e" ) ,
42+ Self :: Hexadecimal => f. write_str ( "x" ) ,
43+ }
44+ }
45+ }
46+
47+ enum ParseArgError {
48+ /// Failed to parse the argument.
49+ Err ,
50+ /// Escape `%%`.
51+ Skip ,
52+ }
53+
54+ /// Parses a format string into a sequence of [pieces][Piece].
55+ #[ derive( Debug ) ]
56+ pub struct Parser < ' a > {
57+ input : & ' a str ,
58+ chars : std:: str:: CharIndices < ' a > ,
59+ }
60+
61+ impl < ' a > Parser < ' a > {
62+ /// Creates a new parser for the given input.
63+ pub fn new ( input : & ' a str ) -> Self {
64+ Self { input, chars : input. char_indices ( ) }
65+ }
66+
67+ /// Parses a string until the next format specifier.
68+ ///
69+ /// `skip` is the number of format specifier characters (`%`) to ignore before returning the
70+ /// string.
71+ fn string ( & mut self , start : usize , mut skip : usize ) -> & ' a str {
72+ while let Some ( ( pos, c) ) = self . peek ( ) {
73+ if c == '%' {
74+ if skip == 0 {
75+ return & self . input [ start..pos] ;
4476 }
45- if let Some ( & ch) = iter. peek ( ) {
46- if ch == 'e' {
47- let num = num. parse ( ) . map_err ( |_| num) ?;
48- iter. next ( ) ;
49- Ok ( Self :: Exponential ( Some ( num) ) )
50- } else {
51- Err ( num)
52- }
53- } else {
54- Err ( num)
77+ skip -= 1 ;
78+ }
79+ self . chars . next ( ) ;
80+ }
81+ & self . input [ start..]
82+ }
83+
84+ /// Parses a format specifier.
85+ ///
86+ /// If `Err` is returned, the internal iterator may have been advanced and it may be in an
87+ /// invalid state.
88+ fn argument ( & mut self ) -> Result < FormatSpec , ParseArgError > {
89+ let ( start, ch) = self . peek ( ) . ok_or ( ParseArgError :: Err ) ?;
90+ let simple_spec = match ch {
91+ 's' => Some ( FormatSpec :: String ) ,
92+ 'd' => Some ( FormatSpec :: Number ) ,
93+ 'i' => Some ( FormatSpec :: Integer ) ,
94+ 'o' => Some ( FormatSpec :: Object ) ,
95+ 'e' => Some ( FormatSpec :: Exponential ( None ) ) ,
96+ 'x' => Some ( FormatSpec :: Hexadecimal ) ,
97+ // "%%" is a literal '%'.
98+ '%' => return Err ( ParseArgError :: Skip ) ,
99+ _ => None ,
100+ } ;
101+ if let Some ( spec) = simple_spec {
102+ self . chars . next ( ) ;
103+ return Ok ( spec) ;
104+ }
105+
106+ // %<n>e
107+ if ch. is_ascii_digit ( ) {
108+ let n = self . integer ( start) ;
109+ if let Some ( ( _, 'e' ) ) = self . peek ( ) {
110+ self . chars . next ( ) ;
111+ return Ok ( FormatSpec :: Exponential ( n) ) ;
112+ }
113+ }
114+
115+ Err ( ParseArgError :: Err )
116+ }
117+
118+ fn integer ( & mut self , start : usize ) -> Option < usize > {
119+ let mut end = start;
120+ while let Some ( ( pos, ch) ) = self . peek ( ) {
121+ if !ch. is_ascii_digit ( ) {
122+ end = pos;
123+ break ;
124+ }
125+ self . chars . next ( ) ;
126+ }
127+ self . input [ start..end] . parse ( ) . ok ( )
128+ }
129+
130+ fn current_pos ( & mut self ) -> usize {
131+ self . peek ( ) . map ( |( n, _) | n) . unwrap_or ( self . input . len ( ) )
132+ }
133+
134+ fn peek ( & mut self ) -> Option < ( usize , char ) > {
135+ self . peek_n ( 0 )
136+ }
137+
138+ fn peek_n ( & mut self , n : usize ) -> Option < ( usize , char ) > {
139+ self . chars . clone ( ) . nth ( n)
140+ }
141+ }
142+
143+ impl < ' a > Iterator for Parser < ' a > {
144+ type Item = Piece < ' a > ;
145+
146+ fn next ( & mut self ) -> Option < Self :: Item > {
147+ let ( mut start, ch) = self . peek ( ) ?;
148+ let mut skip = 0 ;
149+ if ch == '%' {
150+ let prev = self . chars . clone ( ) ;
151+ self . chars . next ( ) ;
152+ match self . argument ( ) {
153+ Ok ( arg) => {
154+ debug_assert_eq ! ( arg. to_string( ) , self . input[ start..self . current_pos( ) ] ) ;
155+ return Some ( Piece :: NextArgument ( arg) ) ;
156+ }
157+
158+ // Skip the argument if we encountered "%%".
159+ Err ( ParseArgError :: Skip ) => {
160+ start = self . current_pos ( ) ;
161+ skip += 1 ;
162+ }
163+
164+ // Reset the iterator if we failed to parse the argument, and include any
165+ // parsed and unparsed specifier in `String`.
166+ Err ( ParseArgError :: Err ) => {
167+ self . chars = prev;
168+ skip += 1 ;
55169 }
56170 }
57- ch => Err ( String :: from ( ch) ) ,
58171 }
172+ Some ( Piece :: String ( self . string ( start, skip) ) )
59173 }
60174}
61175
@@ -249,7 +363,7 @@ impl ConsoleFmt for [u8] {
249363/// assert_eq!(formatted, "foo has 3 characters");
250364/// ```
251365pub fn console_format ( spec : & str , values : & [ & dyn ConsoleFmt ] ) -> String {
252- let mut values = values. iter ( ) . copied ( ) . peekable ( ) ;
366+ let mut values = values. iter ( ) . copied ( ) ;
253367 let mut result = String :: with_capacity ( spec. len ( ) ) ;
254368
255369 // for the first space
@@ -275,45 +389,19 @@ pub fn console_format(spec: &str, values: &[&dyn ConsoleFmt]) -> String {
275389
276390fn format_spec < ' a > (
277391 s : & str ,
278- values : & mut Peekable < impl Iterator < Item = & ' a dyn ConsoleFmt > > ,
392+ mut values : impl Iterator < Item = & ' a dyn ConsoleFmt > ,
279393 result : & mut String ,
280394) {
281- let mut expect_fmt = false ;
282- let mut chars = s. chars ( ) . peekable ( ) ;
283-
284- while chars. peek ( ) . is_some ( ) {
285- if expect_fmt {
286- expect_fmt = false ;
287- match FormatSpec :: from_chars ( & mut chars) {
288- Ok ( spec) => {
289- let value = values. next ( ) . expect ( "value existence is checked" ) ;
290- // format and write the value
395+ for piece in Parser :: new ( s) {
396+ match piece {
397+ Piece :: String ( s) => result. push_str ( s) ,
398+ Piece :: NextArgument ( spec) => {
399+ if let Some ( value) = values. next ( ) {
291400 result. push_str ( & value. fmt ( spec) ) ;
292- }
293- Err ( consumed) => {
294- // on parser failure, write '%' and consumed characters
295- result. push ( '%' ) ;
296- result. push_str ( & consumed) ;
297- }
298- }
299- } else {
300- let ch = chars. next ( ) . unwrap ( ) ;
301- if ch == '%' {
302- if let Some ( & next_ch) = chars. peek ( ) {
303- if next_ch == '%' {
304- result. push ( '%' ) ;
305- chars. next ( ) ;
306- } else if values. peek ( ) . is_some ( ) {
307- // only try formatting if there are values to format
308- expect_fmt = true ;
309- } else {
310- result. push ( ch) ;
311- }
312401 } else {
313- result. push ( ch) ;
402+ // Write the format specifier as-is if there are no more values.
403+ write ! ( result, "{spec}" ) . unwrap ( ) ;
314404 }
315- } else {
316- result. push ( ch) ;
317405 }
318406 }
319407 }
0 commit comments