1 #![cfg(feature = "std")]
2
3 use std::io::Write;
4
5 use crate::error::ErrMode;
6 use crate::stream::Stream;
7 use crate::*;
8
9 pub(crate) struct Trace<P, D, I, O, E>
10 where
11 P: Parser<I, O, E>,
12 I: Stream,
13 D: std::fmt::Display,
14 {
15 parser: P,
16 name: D,
17 call_count: usize,
18 i: core::marker::PhantomData<I>,
19 o: core::marker::PhantomData<O>,
20 e: core::marker::PhantomData<E>,
21 }
22
23 impl<P, D, I, O, E> Trace<P, D, I, O, E>
24 where
25 P: Parser<I, O, E>,
26 I: Stream,
27 D: std::fmt::Display,
28 {
29 #[inline(always)]
new(parser: P, name: D) -> Self30 pub(crate) fn new(parser: P, name: D) -> Self {
31 Self {
32 parser,
33 name,
34 call_count: 0,
35 i: Default::default(),
36 o: Default::default(),
37 e: Default::default(),
38 }
39 }
40 }
41
42 impl<P, D, I, O, E> Parser<I, O, E> for Trace<P, D, I, O, E>
43 where
44 P: Parser<I, O, E>,
45 I: Stream,
46 D: std::fmt::Display,
47 {
48 #[inline]
parse_next(&mut self, i: &mut I) -> PResult<O, E>49 fn parse_next(&mut self, i: &mut I) -> PResult<O, E> {
50 let depth = Depth::new();
51 let original = i.checkpoint();
52 start(*depth, &self.name, self.call_count, i);
53
54 let res = self.parser.parse_next(i);
55
56 let consumed = i.offset_from(&original);
57 let severity = Severity::with_result(&res);
58 end(*depth, &self.name, self.call_count, consumed, severity);
59 self.call_count += 1;
60
61 res
62 }
63 }
64
65 pub(crate) struct Depth {
66 depth: usize,
67 inc: bool,
68 }
69
70 impl Depth {
new() -> Self71 pub(crate) fn new() -> Self {
72 let depth = DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
73 let inc = true;
74 Self { depth, inc }
75 }
76
existing() -> Self77 pub(crate) fn existing() -> Self {
78 let depth = DEPTH.load(std::sync::atomic::Ordering::SeqCst);
79 let inc = false;
80 Self { depth, inc }
81 }
82 }
83
84 impl Drop for Depth {
drop(&mut self)85 fn drop(&mut self) {
86 if self.inc {
87 let _ = DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
88 }
89 }
90 }
91
92 impl AsRef<usize> for Depth {
93 #[inline(always)]
as_ref(&self) -> &usize94 fn as_ref(&self) -> &usize {
95 &self.depth
96 }
97 }
98
99 impl crate::lib::std::ops::Deref for Depth {
100 type Target = usize;
101
102 #[inline(always)]
deref(&self) -> &Self::Target103 fn deref(&self) -> &Self::Target {
104 &self.depth
105 }
106 }
107
108 static DEPTH: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
109
110 pub(crate) enum Severity {
111 Success,
112 Backtrack,
113 Cut,
114 Incomplete,
115 }
116
117 impl Severity {
with_result<T, E>(result: &Result<T, ErrMode<E>>) -> Self118 pub(crate) fn with_result<T, E>(result: &Result<T, ErrMode<E>>) -> Self {
119 match result {
120 Ok(_) => Self::Success,
121 Err(ErrMode::Backtrack(_)) => Self::Backtrack,
122 Err(ErrMode::Cut(_)) => Self::Cut,
123 Err(ErrMode::Incomplete(_)) => Self::Incomplete,
124 }
125 }
126 }
127
start<I: Stream>( depth: usize, name: &dyn crate::lib::std::fmt::Display, count: usize, input: &I, )128 pub(crate) fn start<I: Stream>(
129 depth: usize,
130 name: &dyn crate::lib::std::fmt::Display,
131 count: usize,
132 input: &I,
133 ) {
134 let gutter_style = anstyle::Style::new().bold();
135 let input_style = anstyle::Style::new().underline();
136 let eof_style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Cyan.into()));
137
138 let (call_width, input_width) = column_widths();
139
140 let count = if 0 < count {
141 format!(":{count}")
142 } else {
143 "".to_owned()
144 };
145 let call_column = format!("{:depth$}> {name}{count}", "");
146
147 // The debug version of `slice` might be wider, either due to rendering one byte as two nibbles or
148 // escaping in strings.
149 let mut debug_slice = format!("{:#?}", input.raw());
150 let (debug_slice, eof) = if let Some(debug_offset) = debug_slice
151 .char_indices()
152 .enumerate()
153 .find_map(|(pos, (offset, _))| (input_width <= pos).then_some(offset))
154 {
155 debug_slice.truncate(debug_offset);
156 let eof = "";
157 (debug_slice, eof)
158 } else {
159 let eof = if debug_slice.chars().count() < input_width {
160 "∅"
161 } else {
162 ""
163 };
164 (debug_slice, eof)
165 };
166
167 let writer = anstream::stderr();
168 let mut writer = writer.lock();
169 let _ = writeln!(
170 writer,
171 "{call_column:call_width$} {gutter_style}|{gutter_reset} {input_style}{debug_slice}{input_reset}{eof_style}{eof}{eof_reset}",
172 gutter_style=gutter_style.render(),
173 gutter_reset=gutter_style.render_reset(),
174 input_style=input_style.render(),
175 input_reset=input_style.render_reset(),
176 eof_style=eof_style.render(),
177 eof_reset=eof_style.render_reset(),
178 );
179 }
180
end( depth: usize, name: &dyn crate::lib::std::fmt::Display, count: usize, consumed: usize, severity: Severity, )181 pub(crate) fn end(
182 depth: usize,
183 name: &dyn crate::lib::std::fmt::Display,
184 count: usize,
185 consumed: usize,
186 severity: Severity,
187 ) {
188 let gutter_style = anstyle::Style::new().bold();
189
190 let (call_width, _) = column_widths();
191
192 let count = if 0 < count {
193 format!(":{count}")
194 } else {
195 "".to_owned()
196 };
197 let call_column = format!("{:depth$}< {name}{count}", "");
198
199 let (status_style, status) = match severity {
200 Severity::Success => {
201 let style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into()));
202 let status = format!("+{consumed}");
203 (style, status)
204 }
205 Severity::Backtrack => (
206 anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())),
207 "backtrack".to_owned(),
208 ),
209 Severity::Cut => (
210 anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
211 "cut".to_owned(),
212 ),
213 Severity::Incomplete => (
214 anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
215 "incomplete".to_owned(),
216 ),
217 };
218
219 let writer = anstream::stderr();
220 let mut writer = writer.lock();
221 let _ = writeln!(
222 writer,
223 "{status_style}{call_column:call_width$}{status_reset} {gutter_style}|{gutter_reset} {status_style}{status}{status_reset}",
224 gutter_style=gutter_style.render(),
225 gutter_reset=gutter_style.render_reset(),
226 status_style=status_style.render(),
227 status_reset=status_style.render_reset(),
228 );
229 }
230
result(depth: usize, name: &dyn crate::lib::std::fmt::Display, severity: Severity)231 pub(crate) fn result(depth: usize, name: &dyn crate::lib::std::fmt::Display, severity: Severity) {
232 let gutter_style = anstyle::Style::new().bold();
233
234 let (call_width, _) = column_widths();
235
236 let call_column = format!("{:depth$}| {name}", "");
237
238 let (status_style, status) = match severity {
239 Severity::Success => (
240 anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into())),
241 "",
242 ),
243 Severity::Backtrack => (
244 anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())),
245 "backtrack",
246 ),
247 Severity::Cut => (
248 anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
249 "cut",
250 ),
251 Severity::Incomplete => (
252 anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())),
253 "incomplete",
254 ),
255 };
256
257 let writer = anstream::stderr();
258 let mut writer = writer.lock();
259 let _ = writeln!(
260 writer,
261 "{status_style}{call_column:call_width$}{status_reset} {gutter_style}|{gutter_reset} {status_style}{status}{status_reset}",
262 gutter_style=gutter_style.render(),
263 gutter_reset=gutter_style.render_reset(),
264 status_style=status_style.render(),
265 status_reset=status_style.render_reset(),
266 );
267 }
268
column_widths() -> (usize, usize)269 fn column_widths() -> (usize, usize) {
270 let term_width = term_width();
271
272 let min_call_width = 40;
273 let min_input_width = 20;
274 let decor_width = 3;
275 let extra_width = term_width
276 .checked_sub(min_call_width + min_input_width + decor_width)
277 .unwrap_or_default();
278 let call_width = min_call_width + 2 * extra_width / 3;
279 let input_width = min_input_width + extra_width / 3;
280
281 (call_width, input_width)
282 }
283
term_width() -> usize284 fn term_width() -> usize {
285 columns_env().or_else(query_width).unwrap_or(80)
286 }
287
query_width() -> Option<usize>288 fn query_width() -> Option<usize> {
289 use is_terminal::IsTerminal;
290 if std::io::stderr().is_terminal() {
291 terminal_size::terminal_size().map(|(w, _h)| w.0.into())
292 } else {
293 None
294 }
295 }
296
columns_env() -> Option<usize>297 fn columns_env() -> Option<usize> {
298 std::env::var("COLUMNS")
299 .ok()
300 .and_then(|c| c.parse::<usize>().ok())
301 }
302