• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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