Skip to content

Commit e9c8bf5

Browse files
qiweiiimattsse
andauthored
Add "%ne" format support for forge console log (#8543)
* wip * fix * fix sign * fix --------- Co-authored-by: Matthias Seitz <[email protected]>
1 parent 1ba907b commit e9c8bf5

File tree

1 file changed

+104
-27
lines changed

1 file changed

+104
-27
lines changed

crates/common/fmt/src/console.rs

Lines changed: 104 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::UIfmt;
22
use 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

2223
impl 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

Comments
 (0)