1use super::UIfmt;
2use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256};
3use std::fmt::{self, Write};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum Piece<'a> {
8 String(&'a str),
10 NextArgument(FormatSpec),
12}
13
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub enum FormatSpec {
17 #[default]
19 String,
20 Number,
22 Integer,
24 Object,
26 Exponential(Option<usize>),
28 Hexadecimal,
30}
31
32impl 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
47enum ParseArgError {
48 Err,
50 Skip,
52}
53
54#[derive(Debug)]
56pub struct Parser<'a> {
57 input: &'a str,
58 chars: std::str::CharIndices<'a>,
59}
60
61impl<'a> Parser<'a> {
62 pub fn new(input: &'a str) -> Self {
64 Self { input, chars: input.char_indices() }
65 }
66
67 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];
76 }
77 skip -= 1;
78 }
79 self.chars.next();
80 }
81 &self.input[start..]
82 }
83
84 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 '%' => 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 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
143impl<'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 Err(ParseArgError::Skip) => {
160 start = self.current_pos();
161 skip += 1;
162 }
163
164 Err(ParseArgError::Err) => {
167 self.chars = prev;
168 skip += 1;
169 }
170 }
171 }
172 Some(Piece::String(self.string(start, skip)))
173 }
174}
175
176pub trait ConsoleFmt {
178 fn fmt(&self, spec: FormatSpec) -> String;
180}
181
182impl ConsoleFmt for String {
183 fn fmt(&self, spec: FormatSpec) -> String {
184 match spec {
185 FormatSpec::String => self.clone(),
186 FormatSpec::Object => format!("'{}'", self.clone()),
187 FormatSpec::Number |
188 FormatSpec::Integer |
189 FormatSpec::Exponential(_) |
190 FormatSpec::Hexadecimal => Self::from("NaN"),
191 }
192 }
193}
194
195impl ConsoleFmt for bool {
196 fn fmt(&self, spec: FormatSpec) -> String {
197 match spec {
198 FormatSpec::String => self.pretty(),
199 FormatSpec::Object => format!("'{}'", self.pretty()),
200 FormatSpec::Number => (*self as i32).to_string(),
201 FormatSpec::Integer | FormatSpec::Exponential(_) | FormatSpec::Hexadecimal => {
202 String::from("NaN")
203 }
204 }
205 }
206}
207
208impl ConsoleFmt for U256 {
209 fn fmt(&self, spec: FormatSpec) -> String {
210 match spec {
211 FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => {
212 self.pretty()
213 }
214 FormatSpec::Hexadecimal => {
215 let hex = format!("{self:x}");
216 format!("0x{}", hex.trim_start_matches('0'))
217 }
218 FormatSpec::Exponential(None) => {
219 let log = self.pretty().len() - 1;
220 let exp10 = Self::from(10).pow(Self::from(log));
221 let amount = *self;
222 let integer = amount / exp10;
223 let decimal = (amount % exp10).to_string();
224 let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string();
225 if !decimal.is_empty() {
226 format!("{integer}.{decimal}e{log}")
227 } else {
228 format!("{integer}e{log}")
229 }
230 }
231 FormatSpec::Exponential(Some(precision)) => {
232 let exp10 = Self::from(10).pow(Self::from(precision));
233 let amount = *self;
234 let integer = amount / exp10;
235 let decimal = (amount % exp10).to_string();
236 let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string();
237 if !decimal.is_empty() {
238 format!("{integer}.{decimal}")
239 } else {
240 format!("{integer}")
241 }
242 }
243 }
244 }
245}
246
247impl ConsoleFmt for I256 {
248 fn fmt(&self, spec: FormatSpec) -> String {
249 match spec {
250 FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => {
251 self.pretty()
252 }
253 FormatSpec::Hexadecimal => {
254 let hex = format!("{self:x}");
255 format!("0x{}", hex.trim_start_matches('0'))
256 }
257 FormatSpec::Exponential(None) => {
258 let amount = *self;
259 let sign = if amount.is_negative() { "-" } else { "" };
260 let log = if amount.is_negative() {
261 self.pretty().len() - 2
262 } else {
263 self.pretty().len() - 1
264 };
265 let exp10 = Self::exp10(log);
266 let integer = (amount / exp10).twos_complement();
267 let decimal = (amount % exp10).twos_complement().to_string();
268 let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string();
269 if !decimal.is_empty() {
270 format!("{sign}{integer}.{decimal}e{log}")
271 } else {
272 format!("{sign}{integer}e{log}")
273 }
274 }
275 FormatSpec::Exponential(Some(precision)) => {
276 let amount = *self;
277 let sign = if amount.is_negative() { "-" } else { "" };
278 let exp10 = Self::exp10(precision);
279 let integer = (amount / exp10).twos_complement();
280 let decimal = (amount % exp10).twos_complement().to_string();
281 let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string();
282 if !decimal.is_empty() {
283 format!("{sign}{integer}.{decimal}")
284 } else {
285 format!("{sign}{integer}")
286 }
287 }
288 }
289 }
290}
291
292impl ConsoleFmt for Address {
293 fn fmt(&self, spec: FormatSpec) -> String {
294 match spec {
295 FormatSpec::String | FormatSpec::Hexadecimal => self.pretty(),
296 FormatSpec::Object => format!("'{}'", self.pretty()),
297 FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential(_) => {
298 String::from("NaN")
299 }
300 }
301 }
302}
303
304impl ConsoleFmt for Vec<u8> {
305 fn fmt(&self, spec: FormatSpec) -> String {
306 self[..].fmt(spec)
307 }
308}
309
310impl ConsoleFmt for Bytes {
311 fn fmt(&self, spec: FormatSpec) -> String {
312 self[..].fmt(spec)
313 }
314}
315
316impl<const N: usize> ConsoleFmt for [u8; N] {
317 fn fmt(&self, spec: FormatSpec) -> String {
318 self[..].fmt(spec)
319 }
320}
321
322impl<const N: usize> ConsoleFmt for FixedBytes<N> {
323 fn fmt(&self, spec: FormatSpec) -> String {
324 self[..].fmt(spec)
325 }
326}
327
328impl ConsoleFmt for [u8] {
329 fn fmt(&self, spec: FormatSpec) -> String {
330 match spec {
331 FormatSpec::String | FormatSpec::Hexadecimal => self.pretty(),
332 FormatSpec::Object => format!("'{}'", self.pretty()),
333 FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential(_) => {
334 String::from("NaN")
335 }
336 }
337 }
338}
339
340pub fn console_format(spec: &str, values: &[&dyn ConsoleFmt]) -> String {
366 let mut values = values.iter().copied();
367 let mut result = String::with_capacity(spec.len());
368
369 let mut write_space = if spec.is_empty() {
371 false
372 } else {
373 format_spec(spec, &mut values, &mut result);
374 true
375 };
376
377 for v in values {
379 let fmt = v.fmt(FormatSpec::String);
380 if write_space {
381 result.push(' ');
382 }
383 result.push_str(&fmt);
384 write_space = true;
385 }
386
387 result
388}
389
390fn format_spec<'a>(
391 s: &str,
392 mut values: impl Iterator<Item = &'a dyn ConsoleFmt>,
393 result: &mut String,
394) {
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() {
400 result.push_str(&value.fmt(spec));
401 } else {
402 write!(result, "{spec}").unwrap();
404 }
405 }
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use alloy_primitives::{address, B256};
414 use foundry_macros::ConsoleFmt;
415 use std::str::FromStr;
416
417 macro_rules! logf1 {
418 ($a:ident) => {
419 console_format(&$a.p_0, &[&$a.p_1])
420 };
421 }
422
423 macro_rules! logf2 {
424 ($a:ident) => {
425 console_format(&$a.p_0, &[&$a.p_1, &$a.p_2])
426 };
427 }
428
429 macro_rules! logf3 {
430 ($a:ident) => {
431 console_format(&$a.p_0, &[&$a.p_1, &$a.p_2, &$a.p_3])
432 };
433 }
434
435 #[derive(Clone, Debug, ConsoleFmt)]
436 struct Log1 {
437 p_0: String,
438 p_1: U256,
439 }
440
441 #[derive(Clone, Debug, ConsoleFmt)]
442 struct Log2 {
443 p_0: String,
444 p_1: bool,
445 p_2: U256,
446 }
447
448 #[derive(Clone, Debug, ConsoleFmt)]
449 struct Log3 {
450 p_0: String,
451 p_1: Address,
452 p_2: bool,
453 p_3: U256,
454 }
455
456 #[expect(unused)]
457 #[derive(Clone, Debug, ConsoleFmt)]
458 enum Logs {
459 Log1(Log1),
460 Log2(Log2),
461 Log3(Log3),
462 }
463
464 #[test]
465 fn test_console_log_format_specifiers() {
466 let fmt_1 = |spec: &str, arg: &dyn ConsoleFmt| console_format(spec, &[arg]);
467
468 assert_eq!("foo", fmt_1("%s", &String::from("foo")));
469 assert_eq!("NaN", fmt_1("%d", &String::from("foo")));
470 assert_eq!("NaN", fmt_1("%i", &String::from("foo")));
471 assert_eq!("NaN", fmt_1("%e", &String::from("foo")));
472 assert_eq!("NaN", fmt_1("%x", &String::from("foo")));
473 assert_eq!("'foo'", fmt_1("%o", &String::from("foo")));
474 assert_eq!("%s foo", fmt_1("%%s", &String::from("foo")));
475 assert_eq!("% foo", fmt_1("%", &String::from("foo")));
476 assert_eq!("% foo", fmt_1("%%", &String::from("foo")));
477
478 assert_eq!("true", fmt_1("%s", &true));
479 assert_eq!("1", fmt_1("%d", &true));
480 assert_eq!("0", fmt_1("%d", &false));
481 assert_eq!("NaN", fmt_1("%i", &true));
482 assert_eq!("NaN", fmt_1("%e", &true));
483 assert_eq!("NaN", fmt_1("%x", &true));
484 assert_eq!("'true'", fmt_1("%o", &true));
485
486 let b32 =
487 B256::from_str("0xdeadbeef00000000000000000000000000000000000000000000000000000000")
488 .unwrap();
489 assert_eq!(
490 "0xdeadbeef00000000000000000000000000000000000000000000000000000000",
491 fmt_1("%s", &b32)
492 );
493 assert_eq!(
494 "0xdeadbeef00000000000000000000000000000000000000000000000000000000",
495 fmt_1("%x", &b32)
496 );
497 assert_eq!("NaN", fmt_1("%d", &b32));
498 assert_eq!("NaN", fmt_1("%i", &b32));
499 assert_eq!("NaN", fmt_1("%e", &b32));
500 assert_eq!(
501 "'0xdeadbeef00000000000000000000000000000000000000000000000000000000'",
502 fmt_1("%o", &b32)
503 );
504
505 let addr = address!("0xdEADBEeF00000000000000000000000000000000");
506 assert_eq!("0xdEADBEeF00000000000000000000000000000000", fmt_1("%s", &addr));
507 assert_eq!("NaN", fmt_1("%d", &addr));
508 assert_eq!("NaN", fmt_1("%i", &addr));
509 assert_eq!("NaN", fmt_1("%e", &addr));
510 assert_eq!("0xdEADBEeF00000000000000000000000000000000", fmt_1("%x", &addr));
511 assert_eq!("'0xdEADBEeF00000000000000000000000000000000'", fmt_1("%o", &addr));
512
513 let bytes = Bytes::from_str("0xdeadbeef").unwrap();
514 assert_eq!("0xdeadbeef", fmt_1("%s", &bytes));
515 assert_eq!("NaN", fmt_1("%d", &bytes));
516 assert_eq!("NaN", fmt_1("%i", &bytes));
517 assert_eq!("NaN", fmt_1("%e", &bytes));
518 assert_eq!("0xdeadbeef", fmt_1("%x", &bytes));
519 assert_eq!("'0xdeadbeef'", fmt_1("%o", &bytes));
520
521 assert_eq!("100", fmt_1("%s", &U256::from(100)));
522 assert_eq!("100", fmt_1("%d", &U256::from(100)));
523 assert_eq!("100", fmt_1("%i", &U256::from(100)));
524 assert_eq!("1e2", fmt_1("%e", &U256::from(100)));
525 assert_eq!("1.0023e6", fmt_1("%e", &U256::from(1002300)));
526 assert_eq!("1.23e5", fmt_1("%e", &U256::from(123000)));
527 assert_eq!("0x64", fmt_1("%x", &U256::from(100)));
528 assert_eq!("100", fmt_1("%o", &U256::from(100)));
529
530 assert_eq!("100", fmt_1("%s", &I256::try_from(100).unwrap()));
531 assert_eq!("100", fmt_1("%d", &I256::try_from(100).unwrap()));
532 assert_eq!("100", fmt_1("%i", &I256::try_from(100).unwrap()));
533 assert_eq!("1e2", fmt_1("%e", &I256::try_from(100).unwrap()));
534 assert_eq!("-1e2", fmt_1("%e", &I256::try_from(-100).unwrap()));
535 assert_eq!("-1.0023e6", fmt_1("%e", &I256::try_from(-1002300).unwrap()));
536 assert_eq!("-1.23e5", fmt_1("%e", &I256::try_from(-123000).unwrap()));
537 assert_eq!("1.0023e6", fmt_1("%e", &I256::try_from(1002300).unwrap()));
538 assert_eq!("1.23e5", fmt_1("%e", &I256::try_from(123000).unwrap()));
539
540 assert_eq!("10", fmt_1("%1e", &I256::try_from(100).unwrap()));
542 assert_eq!("-1", fmt_1("%2e", &I256::try_from(-100).unwrap()));
543 assert_eq!("123000", fmt_1("%0e", &I256::try_from(123000).unwrap()));
544 assert_eq!("12300", fmt_1("%1e", &I256::try_from(123000).unwrap()));
545 assert_eq!("0.0123", fmt_1("%7e", &I256::try_from(123000).unwrap()));
546 assert_eq!("-0.0123", fmt_1("%7e", &I256::try_from(-123000).unwrap()));
547
548 assert_eq!("0x64", fmt_1("%x", &I256::try_from(100).unwrap()));
549 assert_eq!(
550 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c",
551 fmt_1("%x", &I256::try_from(-100).unwrap())
552 );
553 assert_eq!(
554 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffe8b7891800",
555 fmt_1("%x", &I256::try_from(-100000000000i64).unwrap())
556 );
557 assert_eq!("100", fmt_1("%o", &I256::try_from(100).unwrap()));
558
559 assert_eq!("%333d%3e%5F", console_format("%333d%3e%5F", &[]));
561 assert_eq!(
562 "%5d123456.789%2f%3f%e1",
563 console_format("%5d%3e%2f%3f%e1", &[&U256::from(123456789)])
564 );
565 }
566
567 #[test]
568 fn test_console_log_format() {
569 let mut log1 = Log1 { p_0: "foo %s".to_string(), p_1: U256::from(100) };
570 assert_eq!("foo 100", logf1!(log1));
571 log1.p_0 = String::from("foo");
572 assert_eq!("foo 100", logf1!(log1));
573 log1.p_0 = String::from("%s foo");
574 assert_eq!("100 foo", logf1!(log1));
575
576 let mut log2 = Log2 { p_0: "foo %s %s".to_string(), p_1: true, p_2: U256::from(100) };
577 assert_eq!("foo true 100", logf2!(log2));
578 log2.p_0 = String::from("foo");
579 assert_eq!("foo true 100", logf2!(log2));
580 log2.p_0 = String::from("%s %s foo");
581 assert_eq!("true 100 foo", logf2!(log2));
582
583 let log3 = Log3 {
584 p_0: String::from("foo %s %%s %s and %d foo %%"),
585 p_1: address!("0xdEADBEeF00000000000000000000000000000000"),
586 p_2: true,
587 p_3: U256::from(21),
588 };
589 assert_eq!(
590 "foo 0xdEADBEeF00000000000000000000000000000000 %s true and 21 foo %",
591 logf3!(log3)
592 );
593
594 let log4 = Log1 { p_0: String::from("%5e"), p_1: U256::from(123456789) };
596 assert_eq!("1234.56789", logf1!(log4));
597
598 let log5 = Log1 { p_0: String::from("foo %3e bar"), p_1: U256::from(123456789) };
599 assert_eq!("foo 123456.789 bar", logf1!(log5));
600
601 let log6 =
602 Log2 { p_0: String::from("%e and %12e"), p_1: false, p_2: U256::from(123456789) };
603 assert_eq!("NaN and 0.000123456789", logf2!(log6));
604 }
605
606 #[test]
607 fn test_derive_format() {
608 let log1 = Log1 { p_0: String::from("foo %s bar"), p_1: U256::from(42) };
609 assert_eq!(log1.fmt(Default::default()), "foo 42 bar");
610 let call = Logs::Log1(log1);
611 assert_eq!(call.fmt(Default::default()), "foo 42 bar");
612 }
613}