d_macro/
lib.rs

1use std::time::Instant;
2
3/// Only public for use in `d!()`.
4pub const RESET: &str = "\x1b[0m";
5/// Only public for use in `d!()`.
6pub const RED: &str   = "\x1b[31m";
7/// Only public for use in `d!()`.
8pub const GREY: &str  = "\x1b[90m";
9
10static mut START: Option<Instant> = None;
11
12pub fn d_prn<S: ToString>(s: S) {
13    let start = d_start();
14    let t = Instant::now() - start;
15
16    eprintln!("{} {}", internal::disp_time(&t), s.to_string());
17}
18
19pub fn d_start() -> Instant {
20    match unsafe { START } {
21        Some(t) => t,
22        None => {
23            eprintln!();
24            eprintln!("{} START", internal::fo(" s", " ms", " µs"));
25            let now = Instant::now();
26            unsafe { START = Some(now) };
27            now
28        }
29    }
30}
31
32pub fn d_end() {
33    d_prn("END");
34    eprintln!();
35}
36
37#[macro_export]
38macro_rules! d {
39    // Rules beginning with `@` are meant for internal use only.
40    // See https://danielkeep.github.io/tlborm/book/pat-internal-rules.html.
41
42    { @ $expr:expr => $val:ident ( $($format_args:tt)+ ) } => {
43        // See the source of `dbg!` for why `match` is needed.
44        match $expr {
45            $val => {
46                let args = format!($($format_args)+);
47                d!(@raw args);
48                $val
49            }
50        }
51    };
52    { @raw $expr:expr } => { {
53        let thread = format!("[{}]", std::thread::current().name().unwrap_or("???"));
54        let pos = format!("{}:{}", file!(), line!());
55        let val = $expr;
56        $crate::d_prn(format!("{:6} {}  {}{}{}", thread, &val, $crate::GREY, pos, $crate::RESET));
57        val
58    } };
59    { @ $($tt:tt)* } => {
60        d!(@raw format!($($tt)*))
61    };
62
63    {} => { d! { @() => val ("") } };
64
65    // I wanted to order these variants in the opposite order, but for some
66    // unknown-to-me reason doing so would cause the variant `{ #? $val:expr }`
67    // to no longer work, as it expects `[` after `#`.
68    { #? $val:expr } => { d! { @ $val => val ("{} = {:#?}", stringify!($val), val) } };
69    { ? $val:expr }  => { d! { @ $val => val ("{} = {:?}", stringify!($val), val) } };
70    { $val:expr }    => { d! { @ $val => val ("{}", val) } };
71}
72
73mod internal {
74    use std::time::Duration;
75
76    #[rustfmt::skip]
77    pub fn disp_time(t: &Duration) -> String {
78        let n = t.as_micros();
79
80        let s2 = String::from("  ");
81        let s3 = String::from("   ");
82
83        match                (n / 1000_000,       n / 1000 % 1000,      n % 1000) {
84            (0,  0,  0) => fo(s2,                 s3,                   "  0"),
85            (0,  0, us) => fo(s2,                 s3,                   format!( "{:3}", us)),
86            (0, ms, us) => fo(s2,                 format!( "{:3}", ms), format!("{:03}", us)),
87            (s, ms, us) => fo(format!("{:2}", s), format!("{:03}", ms), format!("{:03}", us)),
88        }
89    }
90
91    pub fn fo(s: impl Into<String>, ms: impl Into<String>, us: impl Into<String>) -> String {
92        const S: &str = super::RED;
93        const MS: &str = super::RESET;
94        const US: &str = super::GREY;
95        const RST: &str = super::RESET;
96
97        let (s, ms, us) = (s.into(), ms.into(), us.into());
98
99        format!("{s}{}{rst}{ms}{}{rst} {us}{}{rst}", s, ms, us, s = S, ms = MS, us = US, rst = RST)
100    }
101}