1//! Parser execution tracing
2//!
3//! By default, nothing happens and tracing gets compiled away as a no-op. To enable tracing, use
4//! `--features debug`.
5//!
6//! # Example
7//!
8//!![Trace output from string example](https://raw.githubusercontent.com/winnow-rs/winnow/main/assets/trace.svg "Example output")
9
10#[cfg(feature = "debug")]
11mod internals;
12
13use crate::error::ErrMode;
14use crate::stream::Stream;
15use crate::Parser;
16
17#[cfg(all(feature = "debug", not(feature = "std")))]
18compile_error!("`debug` requires `std`");
19
20/// Trace the execution of the parser
21///
22/// Note that [`Parser::context` also provides high level trace information.
23///
24/// See [`trace` module][self] for more details.
25///
26/// # Example
27///
28/// ```rust
29/// # use winnow::{error::ErrMode, error::{Error, ErrorKind}, error::Needed, IResult};
30/// # use winnow::token::take_while;
31/// # use winnow::stream::AsChar;
32/// # use winnow::prelude::*;
33/// use winnow::trace::trace;
34///
35/// fn short_alpha(s: &[u8]) -> IResult<&[u8], &[u8]> {
36/// trace("short_alpha",
37/// take_while(3..=6, AsChar::is_alpha)
38/// ).parse_next(s)
39/// }
40///
41/// assert_eq!(short_alpha(b"latin123"), Ok((&b"123"[..], &b"latin"[..])));
42/// assert_eq!(short_alpha(b"lengthy"), Ok((&b"y"[..], &b"length"[..])));
43/// assert_eq!(short_alpha(b"latin"), Ok((&b""[..], &b"latin"[..])));
44/// assert_eq!(short_alpha(b"ed"), Err(ErrMode::Backtrack(Error::new(&b"ed"[..], ErrorKind::Slice))));
45/// assert_eq!(short_alpha(b"12345"), Err(ErrMode::Backtrack(Error::new(&b"12345"[..], ErrorKind::Slice))));
46/// ```
47#[cfg_attr(not(feature = "debug"), allow(unused_variables))]
48#[cfg_attr(not(feature = "debug"), allow(unused_mut))]
49#[cfg_attr(not(feature = "debug"), inline(always))]
50pub fn trace<I: Stream, O, E>(
51 name: impl crate::lib::std::fmt::Display,
52 mut parser: impl Parser<I, O, E>,
53) -> impl Parser<I, O, E> {
54 #[cfg(feature = "debug")]
55 {
56 let mut call_count = 0;
57 move |i: I| {
58 let depth = internals::Depth::new();
59 let original = i.clone();
60 internals::start(*depth, &name, call_count, &original);
61
62 let res = parser.parse_next(i);
63
64 let consumed = res.as_ref().ok().map(|(i, _)| {
65 if i.eof_offset() == 0 {
66 // Sometimes, an unrelated empty string is returned which can break `offset_to`
67 original.eof_offset()
68 } else {
69 original.offset_to(i)
70 }
71 });
72 let severity = internals::Severity::with_result(&res);
73 internals::end(*depth, &name, call_count, consumed, severity);
74 call_count += 1;
75
76 res
77 }
78 }
79 #[cfg(not(feature = "debug"))]
80 {
81 parser
82 }
83}
84
85#[cfg_attr(not(feature = "debug"), allow(unused_variables))]
86pub(crate) fn trace_result<T, E>(
87 name: impl crate::lib::std::fmt::Display,
88 res: &Result<T, ErrMode<E>>,
89) {
90 #[cfg(feature = "debug")]
91 {
92 let depth = internals::Depth::existing();
93 let severity = internals::Severity::with_result(res);
94 internals::result(*depth, &name, severity);
95 }
96}
97
98#[test]
99#[cfg(feature = "std")]
100#[cfg_attr(miri, ignore)]
101#[cfg(unix)]
102#[cfg(feature = "debug")]
103fn example() {
104 use term_transcript::{test::TestConfig, ShellOptions};
105
106 let path = snapbox::cmd::compile_example("string", ["--features=debug"]).unwrap();
107
108 let current_dir = path.parent().unwrap();
109 let cmd = path.file_name().unwrap();
110 // HACK: term_transcript doesn't allow non-UTF8 paths
111 let cmd = format!("./{}", cmd.to_string_lossy());
112
113 TestConfig::new(
114 ShellOptions::default()
115 .with_current_dir(current_dir)
116 .with_env("CLICOLOR_FORCE", "1"),
117 )
118 .test("assets/trace.svg", [cmd.as_str()]);
119}
120