11use super :: UIfmt ;
22use alloy_primitives:: { Address , Bytes , FixedBytes , I256 , U256 } ;
3+ use std:: iter:: Peekable ;
34
45/// A format specifier.
56#[ derive( Clone , Copy , Debug , Default , PartialEq , Eq ) ]
@@ -13,21 +14,45 @@ pub enum FormatSpec {
1314 Integer ,
1415 /// %o format spec
1516 Object ,
16- /// %e format spec
17- Exponential ,
17+ /// %e format spec with an optional precision
18+ Exponential ( Option < usize > ) ,
1819 /// %x format spec
1920 Hexadecimal ,
2021}
2122
2223impl FormatSpec {
23- fn from_char ( ch : char ) -> Option < Self > {
24- match ch {
24+ fn from_chars < I > ( iter : & mut Peekable < I > ) -> Option < Self >
25+ where
26+ I : Iterator < Item = char > ,
27+ {
28+ match iter. next ( ) ? {
2529 's' => Some ( Self :: String ) ,
2630 'd' => Some ( Self :: Number ) ,
2731 'i' => Some ( Self :: Integer ) ,
2832 'o' => Some ( Self :: Object ) ,
29- 'e' => Some ( Self :: Exponential ) ,
33+ 'e' => Some ( Self :: Exponential ( None ) ) ,
3034 'x' => Some ( 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+ }
44+ }
45+ if let Some ( & ch) = iter. peek ( ) {
46+ if ch == 'e' {
47+ iter. next ( ) ;
48+ Some ( Self :: Exponential ( Some ( num. parse ( ) . ok ( ) ?) ) )
49+ } else {
50+ None
51+ }
52+ } else {
53+ None
54+ }
55+ }
3156 _ => None ,
3257 }
3358 }
@@ -46,7 +71,7 @@ impl ConsoleFmt for String {
4671 FormatSpec :: Object => format ! ( "'{}'" , self . clone( ) ) ,
4772 FormatSpec :: Number |
4873 FormatSpec :: Integer |
49- FormatSpec :: Exponential |
74+ FormatSpec :: Exponential ( _ ) |
5075 FormatSpec :: Hexadecimal => Self :: from ( "NaN" ) ,
5176 }
5277 }
@@ -58,7 +83,7 @@ impl ConsoleFmt for bool {
5883 FormatSpec :: String => self . pretty ( ) ,
5984 FormatSpec :: Object => format ! ( "'{}'" , self . pretty( ) ) ,
6085 FormatSpec :: Number => ( * self as i32 ) . to_string ( ) ,
61- FormatSpec :: Integer | FormatSpec :: Exponential | FormatSpec :: Hexadecimal => {
86+ FormatSpec :: Integer | FormatSpec :: Exponential ( _ ) | FormatSpec :: Hexadecimal => {
6287 String :: from ( "NaN" )
6388 }
6489 }
@@ -75,7 +100,7 @@ impl ConsoleFmt for U256 {
75100 let hex = format ! ( "{self:x}" ) ;
76101 format ! ( "0x{}" , hex. trim_start_matches( '0' ) )
77102 }
78- FormatSpec :: Exponential => {
103+ FormatSpec :: Exponential ( None ) => {
79104 let log = self . pretty ( ) . len ( ) - 1 ;
80105 let exp10 = Self :: from ( 10 ) . pow ( Self :: from ( log) ) ;
81106 let amount = * self ;
@@ -88,6 +113,18 @@ impl ConsoleFmt for U256 {
88113 format ! ( "{integer}e{log}" )
89114 }
90115 }
116+ FormatSpec :: Exponential ( Some ( precision) ) => {
117+ let exp10 = Self :: from ( 10 ) . pow ( Self :: from ( precision) ) ;
118+ let amount = * self ;
119+ let integer = amount / exp10;
120+ let decimal = ( amount % exp10) . to_string ( ) ;
121+ let decimal = format ! ( "{decimal:0>precision$}" ) . trim_end_matches ( '0' ) . to_string ( ) ;
122+ if !decimal. is_empty ( ) {
123+ format ! ( "{integer}.{decimal}" )
124+ } else {
125+ format ! ( "{integer}" )
126+ }
127+ }
91128 }
92129 }
93130}
@@ -102,7 +139,7 @@ impl ConsoleFmt for I256 {
102139 let hex = format ! ( "{self:x}" ) ;
103140 format ! ( "0x{}" , hex. trim_start_matches( '0' ) )
104141 }
105- FormatSpec :: Exponential => {
142+ FormatSpec :: Exponential ( None ) => {
106143 let amount = * self ;
107144 let sign = if amount. is_negative ( ) { "-" } else { "" } ;
108145 let log = if amount. is_negative ( ) {
@@ -117,7 +154,20 @@ impl ConsoleFmt for I256 {
117154 if !decimal. is_empty ( ) {
118155 format ! ( "{sign}{integer}.{decimal}e{log}" )
119156 } else {
120- format ! ( "{integer}e{log}" )
157+ format ! ( "{sign}{integer}e{log}" )
158+ }
159+ }
160+ FormatSpec :: Exponential ( Some ( precision) ) => {
161+ let amount = * self ;
162+ let sign = if amount. is_negative ( ) { "-" } else { "" } ;
163+ let exp10 = Self :: exp10 ( precision) ;
164+ let integer = ( amount / exp10) . twos_complement ( ) ;
165+ let decimal = ( amount % exp10) . twos_complement ( ) . to_string ( ) ;
166+ let decimal = format ! ( "{decimal:0>precision$}" ) . trim_end_matches ( '0' ) . to_string ( ) ;
167+ if !decimal. is_empty ( ) {
168+ format ! ( "{sign}{integer}.{decimal}" )
169+ } else {
170+ format ! ( "{sign}{integer}" )
121171 }
122172 }
123173 }
@@ -129,7 +179,7 @@ impl ConsoleFmt for Address {
129179 match spec {
130180 FormatSpec :: String | FormatSpec :: Hexadecimal => self . pretty ( ) ,
131181 FormatSpec :: Object => format ! ( "'{}'" , self . pretty( ) ) ,
132- FormatSpec :: Number | FormatSpec :: Integer | FormatSpec :: Exponential => {
182+ FormatSpec :: Number | FormatSpec :: Integer | FormatSpec :: Exponential ( _ ) => {
133183 String :: from ( "NaN" )
134184 }
135185 }
@@ -165,7 +215,7 @@ impl ConsoleFmt for [u8] {
165215 match spec {
166216 FormatSpec :: String | FormatSpec :: Hexadecimal => self . pretty ( ) ,
167217 FormatSpec :: Object => format ! ( "'{}'" , self . pretty( ) ) ,
168- FormatSpec :: Number | FormatSpec :: Integer | FormatSpec :: Exponential => {
218+ FormatSpec :: Number | FormatSpec :: Integer | FormatSpec :: Exponential ( _ ) => {
169219 String :: from ( "NaN" )
170220 }
171221 }
@@ -233,31 +283,37 @@ fn format_spec<'a>(
233283) -> Option < & ' a dyn ConsoleFmt > {
234284 let mut expect_fmt = false ;
235285 let mut current_value = values. next ( ) ;
286+ let mut chars = s. chars ( ) . peekable ( ) ;
236287
237- for ( i, ch) in s. char_indices ( ) {
238- // no more values
239- if current_value. is_none ( ) {
240- result. push_str ( & s[ i..] . replace ( "%%" , "%" ) ) ;
241- break
242- }
243-
288+ while let Some ( ch) = chars. next ( ) {
244289 if expect_fmt {
245290 expect_fmt = false ;
246- if let Some ( spec) = FormatSpec :: from_char ( ch) {
291+ let mut iter = std:: iter:: once ( ch) . chain ( chars. by_ref ( ) ) . peekable ( ) ;
292+ if let Some ( spec) = FormatSpec :: from_chars ( & mut iter) {
247293 // format and write the value
248- let string = current_value. unwrap ( ) . fmt ( spec) ;
249- result. push_str ( & string) ;
250- current_value = values. next ( ) ;
294+ if let Some ( value) = current_value {
295+ let string = value. fmt ( spec) ;
296+ result. push_str ( & string) ;
297+ current_value = values. next ( ) ;
298+ }
251299 } else {
252300 // invalid specifier or a second `%`, in both cases we ignore
301+ result. push ( '%' ) ;
253302 result. push ( ch) ;
254303 }
255- } else {
256- expect_fmt = ch == '%' ;
257- // push when not a `%` or it's the last char
258- if !expect_fmt || i == s. len ( ) - 1 {
304+ } else if ch == '%' {
305+ if let Some ( & next_ch) = chars. peek ( ) {
306+ if next_ch == '%' {
307+ result. push ( '%' ) ;
308+ chars. next ( ) ;
309+ } else {
310+ expect_fmt = true ;
311+ }
312+ } else {
259313 result. push ( ch) ;
260314 }
315+ } else {
316+ result. push ( ch) ;
261317 }
262318 }
263319
@@ -388,10 +444,20 @@ mod tests {
388444 assert_eq ! ( "100" , fmt_1( "%d" , & I256 :: try_from( 100 ) . unwrap( ) ) ) ;
389445 assert_eq ! ( "100" , fmt_1( "%i" , & I256 :: try_from( 100 ) . unwrap( ) ) ) ;
390446 assert_eq ! ( "1e2" , fmt_1( "%e" , & I256 :: try_from( 100 ) . unwrap( ) ) ) ;
447+ assert_eq ! ( "-1e2" , fmt_1( "%e" , & I256 :: try_from( -100 ) . unwrap( ) ) ) ;
391448 assert_eq ! ( "-1.0023e6" , fmt_1( "%e" , & I256 :: try_from( -1002300 ) . unwrap( ) ) ) ;
392449 assert_eq ! ( "-1.23e5" , fmt_1( "%e" , & I256 :: try_from( -123000 ) . unwrap( ) ) ) ;
393450 assert_eq ! ( "1.0023e6" , fmt_1( "%e" , & I256 :: try_from( 1002300 ) . unwrap( ) ) ) ;
394451 assert_eq ! ( "1.23e5" , fmt_1( "%e" , & I256 :: try_from( 123000 ) . unwrap( ) ) ) ;
452+
453+ // %ne
454+ assert_eq ! ( "10" , fmt_1( "%1e" , & I256 :: try_from( 100 ) . unwrap( ) ) ) ;
455+ assert_eq ! ( "-1" , fmt_1( "%2e" , & I256 :: try_from( -100 ) . unwrap( ) ) ) ;
456+ assert_eq ! ( "123000" , fmt_1( "%0e" , & I256 :: try_from( 123000 ) . unwrap( ) ) ) ;
457+ assert_eq ! ( "12300" , fmt_1( "%1e" , & I256 :: try_from( 123000 ) . unwrap( ) ) ) ;
458+ assert_eq ! ( "0.0123" , fmt_1( "%7e" , & I256 :: try_from( 123000 ) . unwrap( ) ) ) ;
459+ assert_eq ! ( "-0.0123" , fmt_1( "%7e" , & I256 :: try_from( -123000 ) . unwrap( ) ) ) ;
460+
395461 assert_eq ! ( "0x64" , fmt_1( "%x" , & I256 :: try_from( 100 ) . unwrap( ) ) ) ;
396462 assert_eq ! (
397463 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c" ,
@@ -430,6 +496,17 @@ mod tests {
430496 "foo 0xdEADBEeF00000000000000000000000000000000 %s true and 21 foo %" ,
431497 logf3!( log3)
432498 ) ;
499+
500+ // %ne
501+ let log4 = Log1 { p_0 : String :: from ( "%5e" ) , p_1 : U256 :: from ( 123456789 ) } ;
502+ assert_eq ! ( "1234.56789" , logf1!( log4) ) ;
503+
504+ let log5 = Log1 { p_0 : String :: from ( "foo %3e bar" ) , p_1 : U256 :: from ( 123456789 ) } ;
505+ assert_eq ! ( "foo 123456.789 bar" , logf1!( log5) ) ;
506+
507+ let log6 =
508+ Log2 { p_0 : String :: from ( "%e and %12e" ) , p_1 : false , p_2 : U256 :: from ( 123456789 ) } ;
509+ assert_eq ! ( "NaN and 0.000123456789" , logf2!( log6) ) ;
433510 }
434511
435512 #[ test]
0 commit comments