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" )] |
11 | mod internals; |
12 | |
13 | use crate::error::ErrMode; |
14 | use crate::stream::Stream; |
15 | use crate::Parser; |
16 | |
17 | #[cfg (all(feature = "debug" , not(feature = "std" )))] |
18 | compile_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))] |
50 | pub 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))] |
86 | pub(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" )] |
103 | fn 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 | |