1#![doc = include_str!("README.md")]
2
3use argp::FromArgs;
4use bat::PrettyPrinter;
5
6pub use proc_debug_macro::proc_debug;
8use proc_macro2::{TokenStream, TokenTree};
9use quote::{quote, quote_spanned, ToTokens};
10use std::collections::VecDeque;
11use std::{io::Write, str::FromStr};
12use syn::*;
13use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
14
15fn print<R>(f: impl FnOnce(&mut StandardStream) -> R) -> R {
16 let mut stdout = StandardStream::stdout(ColorChoice::Always);
17 stdout
18 .set_color(
19 ColorSpec::new()
20 .set_bg(Some(Color::Cyan))
21 .set_fg(Some(Color::Black))
22 .set_bold(true),
23 )
24 .unwrap();
25 f(&mut stdout)
26}
27
28enum MacroOutput {
29 Expr(Expr),
30 Type(Type),
31 ImplItem(Vec<ImplItem>),
32 TraitItem(Vec<TraitItem>),
33 ForeignItem(Vec<ForeignItem>),
34 Item(Vec<Item>),
35 Stmt(Vec<Stmt>),
36 Other(TokenStream),
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40enum MacroKind {
41 Function,
42 Attribute,
43 Derive,
44 Other,
45}
46
47impl FromStr for MacroKind {
48 type Err = &'static str;
49
50 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
51 match s {
52 "function" => Ok(Self::Function),
53 "attribute" => Ok(Self::Attribute),
54 "derive" => Ok(Self::Derive),
55 _ => Ok(Self::Other),
56 }
57 }
58}
59
60impl ToTokens for MacroOutput {
61 fn to_tokens(&self, tokens: &mut TokenStream) {
62 let rhs = match self {
63 MacroOutput::Expr(expr) => quote!(#expr),
64 MacroOutput::Type(ty) => quote!(#ty),
65 MacroOutput::ImplItem(o) => quote!(#(#o)*),
66 MacroOutput::ForeignItem(o) => quote!(#(#o)*),
67 MacroOutput::TraitItem(o) => quote!(#(#o)*),
68 MacroOutput::Item(o) => quote!(#(#o)*),
69 MacroOutput::Stmt(o) => quote!(#(#o)*),
70 MacroOutput::Other(o) => o.clone(),
71 };
72 tokens.extend(rhs);
73 }
74}
75
76fn simplify_and_replace(tokens: TokenStream, depth: usize) -> TokenStream {
77 let mut out = TokenStream::new();
78 if depth == 0 {
79 out.extend(quote!(__proc_debug_ellipsis! {}));
80 return out;
81 }
82 let mut count = 0;
83 for token in tokens {
84 match &token {
85 TokenTree::Group(g) => {
86 let inner = simplify_and_replace(g.stream(), depth - 1);
87 out.extend(Some(TokenTree::Group(proc_macro2::Group::new(
88 g.delimiter(),
89 inner,
90 ))));
91 }
92 TokenTree::Punct(p) if p.as_char() == ';' => {
93 count += 1;
94 out.extend(Some(token.clone()));
95 if count >= depth {
96 out.extend(quote_spanned!(p.span() => __proc_debug_ellipsis!{}));
97 break;
98 }
99 }
100 TokenTree::Ident(ident) if &ident.to_string() == "$crate" => {
101 out.extend(quote_spanned!(ident.span() => __proc_debug_dollar_crate!{}));
102 }
103 _ => {
104 out.extend(Some(token));
105 }
106 }
107 }
108 out
109}
110
111fn unreplace(tokens: TokenStream) -> TokenStream {
112 let mut out = TokenStream::new();
113 let mut tokens: VecDeque<_> = tokens.into_iter().collect();
114 while let Some(token) = tokens.pop_front() {
115 if let TokenTree::Ident(ident) = token.clone() {
116 match tokens.pop_front() {
117 Some(TokenTree::Punct(p)) if p.as_char() == '!' => {
118 match tokens.pop_front() {
119 Some(TokenTree::Group(g))
120 if g.delimiter() == proc_macro2::Delimiter::Brace =>
121 {
122 match ident.to_string().as_str() {
123 "__proc_debug_ellipsis" => {
124 out.extend(quote_spanned!(ident.span() => ...));
125 continue;
126 }
127 "__proc_debug_dollar_crate" => {
128 out.extend(quote_spanned!(ident.span() => $crate));
129 continue;
130 }
131 _ => (),
132 }
133 tokens.push_front(TokenTree::Group(g));
134 }
135 Some(o) => tokens.push_front(o),
136 None => (),
137 }
138 tokens.push_front(TokenTree::Punct(p));
139 }
140 Some(o) => tokens.push_front(o),
141 None => (),
142 }
143 }
144 if let TokenTree::Group(g) = token.clone() {
145 let ng = proc_macro2::Group::new(g.delimiter(), unreplace(g.stream()));
146 out.extend(Some(TokenTree::Group(ng)));
147 } else {
148 out.extend(Some(token));
149 }
150 }
151 out
152}
153
154impl MacroOutput {
155 fn from_tokens(tokens: TokenStream, kind: &MacroKind) -> Self {
156 struct Sequentary<T>(Vec<T>);
157 impl<T> syn::parse::Parse for Sequentary<T>
158 where
159 T: syn::parse::Parse,
160 {
161 fn parse(input: parse::ParseStream) -> Result<Self> {
162 let mut v = Vec::new();
163 while !input.is_empty() {
164 v.push(input.parse()?)
165 }
166 Ok(Self(v))
167 }
168 }
169 if kind == &MacroKind::Function {
170 if let Ok(ident) = parse2::<Ident>(tokens.clone()) {
171 if ident.to_string().chars().next().unwrap().is_uppercase() {
172 return Self::Type(parse_quote! {#ident});
173 } else {
174 return Self::Expr(parse_quote!(#ident));
175 }
176 }
177 if let Ok(ty) = parse2::<Type>(tokens.clone()) {
178 return Self::Type(ty);
179 }
180 }
181 if let Ok(s) = parse2::<Sequentary<_>>(tokens.clone()) {
182 return Self::ImplItem(s.0);
183 }
184 if let Ok(s) = parse2::<Sequentary<_>>(tokens.clone()) {
185 return Self::TraitItem(s.0);
186 }
187 if let Ok(s) = parse2::<Sequentary<_>>(tokens.clone()) {
188 return Self::ForeignItem(s.0);
189 }
190 if let Ok(s) = parse2::<Sequentary<_>>(tokens.clone()) {
191 return Self::Item(s.0);
192 }
193 if let Ok(s) = parse2::<Sequentary<_>>(tokens.clone()) {
194 return Self::Stmt(s.0);
195 }
196 Self::Other(tokens)
197 }
198
199 fn emit(&self) -> TokenStream {
200 match self {
201 MacroOutput::Expr(expr) => quote! {#expr},
202 MacroOutput::Type(ty) => quote! {#ty},
203 o => quote! {#o},
204 }
205 }
206}
207
208fn show_macro_call(
209 modpath: &str,
210 macro_name: &str,
211 file: &str,
212 line: usize,
213 macro_kind: &str,
214 macro_inputs: &[String],
215) {
216 let content = match macro_kind {
217 "function" => format!("{macro_name}!{{{}}}", macro_inputs[0]),
218 "attribute" => format!(
219 "#[{}({})]\n{}",
220 macro_name, macro_inputs[0], macro_inputs[1]
221 ),
222 "derive" => format!("#[derive({})]\n{}", macro_inputs[0], macro_inputs[1]),
223 _ => format!("{}", macro_inputs.join(",")),
224 };
225 let content = content
226 .split("\n")
227 .map(|s| format!(" {}", s))
228 .collect::<Vec<_>>()
229 .join("\n");
230 print(|out| writeln!(out, "👉 input of {modpath}::{macro_name} ({file}:{line})",)).unwrap();
231 PrettyPrinter::new()
232 .input_from_reader(content.as_bytes())
233 .language("rust")
234 .print()
235 .unwrap();
236 writeln!(std::io::stdout(), "",).unwrap();
237}
238
239fn show_macro_output(modpath: &str, macro_name: &str, file: &str, line: usize, macro_output: &str) {
240 print(|out| writeln!(out, "👉 output of {modpath}::{macro_name} ({file}:{line})",)).unwrap();
241 let content = macro_output
242 .split("\n")
243 .map(|s| format!(" {}", s))
244 .collect::<Vec<_>>()
245 .join("\n");
246 PrettyPrinter::new()
247 .input_from_bytes(content.as_bytes())
248 .language("rust")
249 .print()
250 .unwrap();
251 writeln!(std::io::stdout(), "",).unwrap();
252}
253
254#[derive(FromArgs)]
256struct ProcDebugArgs {
257 #[argp(switch, short = 'a')]
259 all: bool,
260 #[argp(option, short = 'n')]
262 not: Vec<String>,
263 #[argp(option, short = 'p')]
265 path: Vec<String>,
266 #[argp(positional, greedy)]
268 queries: Vec<String>,
269 #[argp(option, short = 'd')]
271 depth: Option<usize>,
272 #[argp(switch, short = 'v')]
274 verbose: bool,
275}
276
277#[test]
278fn test_split_args() {
279 assert_eq!(
280 split_args(r#" --all -a ' b c ' " -d '' " "#),
281 vec![
282 "--all".to_owned(),
283 "-a".to_owned(),
284 " b c ".to_owned(),
285 " -d '' ".to_owned()
286 ]
287 );
288}
289
290fn split_args(s: &str) -> Vec<String> {
291 let mut it = s.chars().fuse();
292 let mut res = Vec::new();
293 let mut r = String::new();
294 while let Some(c) = it.next() {
295 match c {
296 '\\' => {
297 if let Some(c) = it.next() {
298 r.push(c);
299 }
300 }
301 '"' | '\'' => {
302 let delim = c;
303 while let Some(c) = it.next() {
304 match c {
305 '\\' => {
306 if let Some(c) = it.next() {
307 r.push(c);
308 }
309 }
310 c if c == delim => {
311 res.push(r);
312 r = String::new();
313 break;
314 }
315 c => r.push(c),
316 }
317 }
318 }
319 c if c.is_ascii_whitespace() => {
320 if r.len() > 0 {
321 res.push(r);
322 r = String::new();
323 }
324 }
325 c => r.push(c),
326 }
327 }
328 if r.len() > 0 {
329 res.push(r);
330 }
331 res
332}
333
334impl ProcDebugArgs {
335 fn from_env() -> Option<Self> {
336 let flags = std::env::var("PROC_DEBUG_FLAGS").ok()?;
337 let flags = split_args(&flags);
338 Some(
339 ProcDebugArgs::from_args(&["proc-debug"], &flags).unwrap_or_else(|early_exit| {
340 let mut stderr = StandardStream::stderr(ColorChoice::Always);
341 stderr
342 .set_color(
343 ColorSpec::new()
344 .set_bg(Some(Color::Yellow))
345 .set_fg(Some(Color::Black))
346 .set_bold(true),
347 )
348 .unwrap();
349 match early_exit {
350 argp::EarlyExit::Help(help) => {
351 writeln!(&mut stderr, "{}", help.generate_default()).unwrap()
352 }
353 argp::EarlyExit::Err(err) => writeln!(
354 &mut stderr,
355 "{} \n\n Set PROC_DEBUG_FLAGS=\"--help\" for more information.",
356 err
357 )
358 .unwrap(),
359 }
360 std::process::exit(1)
361 }),
362 )
363 }
364}
365
366#[allow(unused)]
367struct Entry<'a> {
368 label: &'a str,
369 file: &'a str,
370 line: usize,
371 modpath: &'a str,
372 macro_kind: &'a str,
373 macro_name: &'a str,
374 macro_inputs: &'a [String],
375}
376
377impl<'a> Entry<'a> {
378 fn check_filter(&self, args: &ProcDebugArgs) -> bool {
379 let content = [&self.label, &self.file, &self.modpath, &self.macro_name];
380 let pattern = format!("{}::{}", &self.modpath, &self.macro_name);
381
382 if args.all {
383 return true;
384 }
385 if content
386 .iter()
387 .any(|s| args.not.iter().any(|t| s.contains(t)))
388 {
389 return false;
390 }
391 if args.path.iter().any(|m| {
392 m == &pattern
393 || pattern.starts_with(&format!("{}::", m))
394 || pattern.ends_with(&format!("::{}", m))
395 }) {
396 return true;
397 }
398 if content
399 .iter()
400 .any(|s| args.queries.iter().any(|t| s.contains(t)))
401 {
402 return true;
403 }
404 false
405 }
406}
407
408#[doc(hidden)]
409pub fn proc_wrapper<F: FnOnce() -> TokenStream>(
410 label: &str,
411 file: &str,
412 line: usize,
413 modpath: &str,
414 macro_kind: &str,
415 macro_name: &str,
416 macro_inputs: &[String],
417 f: F,
418) -> TokenStream {
419 let entry = Entry {
420 label,
421 file,
422 line,
423 modpath,
424 macro_kind,
425 macro_name,
426 macro_inputs,
427 };
428 let ret = f();
429 if let Some(args) = ProcDebugArgs::from_env() {
430 if entry.check_filter(&args) {
431 show_macro_call(modpath, macro_name, file, line, macro_kind, macro_inputs);
432 let tokens: TokenStream = ret.into();
433 let output =
434 MacroOutput::from_tokens(tokens.clone(), &MacroKind::from_str(macro_kind).unwrap());
435 let simplified = simplify_and_replace(
436 tokens,
437 if args.verbose {
438 usize::MAX
439 } else {
440 args.depth.unwrap_or(4)
441 },
442 );
443
444 show_macro_output(
445 modpath,
446 macro_name,
447 file,
448 line,
449 &unreplace(simplified).to_string(),
450 );
451 output.emit().into()
452 } else {
453 ret
454 }
455 } else {
456 ret
457 }
458}