]>
Commit | Line | Data |
---|---|---|
49aad941 FG |
1 | #![cfg(feature = "std")] |
2 | ||
3 | use std::io::Write; | |
4 | ||
5 | use crate::error::ErrMode; | |
6 | use crate::stream::Stream; | |
781aab86 FG |
7 | use crate::*; |
8 | ||
9 | pub 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)] | |
30 | pub 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] | |
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 | } | |
49aad941 FG |
64 | |
65 | pub struct Depth { | |
66 | depth: usize, | |
67 | inc: bool, | |
68 | } | |
69 | ||
70 | impl Depth { | |
71 | pub 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 | ||
77 | pub 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 { | |
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)] | |
94 | 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)] | |
103 | 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 enum Severity { | |
111 | Success, | |
112 | Backtrack, | |
113 | Cut, | |
114 | Incomplete, | |
115 | } | |
116 | ||
117 | impl Severity { | |
118 | pub 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 | ||
128 | pub fn start<I: Stream>( | |
129 | depth: usize, | |
130 | name: &dyn crate::lib::std::fmt::Display, | |
131 | count: usize, | |
132 | input: &I, | |
133 | ) { | |
fe692bf9 FG |
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())); | |
49aad941 FG |
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 | ||
49aad941 FG |
147 | // The debug version of `slice` might be wider, either due to rendering one byte as two nibbles or |
148 | // escaping in strings. | |
add651ee | 149 | let mut debug_slice = format!("{:#?}", input.raw()); |
49aad941 FG |
150 | let (debug_slice, eof) = if let Some(debug_offset) = debug_slice |
151 | .char_indices() | |
152 | .enumerate() | |
add651ee | 153 | .find_map(|(pos, (offset, _))| (input_width <= pos).then_some(offset)) |
49aad941 FG |
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 | ||
781aab86 | 167 | let writer = anstream::stderr(); |
49aad941 | 168 | let mut writer = writer.lock(); |
fe692bf9 FG |
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 | ); | |
49aad941 FG |
179 | } |
180 | ||
181 | pub fn end( | |
182 | depth: usize, | |
183 | name: &dyn crate::lib::std::fmt::Display, | |
184 | count: usize, | |
add651ee | 185 | consumed: usize, |
49aad941 FG |
186 | severity: Severity, |
187 | ) { | |
fe692bf9 | 188 | let gutter_style = anstyle::Style::new().bold(); |
49aad941 FG |
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 | ||
fe692bf9 | 199 | let (status_style, status) = match severity { |
49aad941 | 200 | Severity::Success => { |
fe692bf9 | 201 | let style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into())); |
add651ee | 202 | let status = format!("+{}", consumed); |
49aad941 FG |
203 | (style, status) |
204 | } | |
205 | Severity::Backtrack => ( | |
fe692bf9 | 206 | anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())), |
49aad941 FG |
207 | "backtrack".to_owned(), |
208 | ), | |
209 | Severity::Cut => ( | |
fe692bf9 | 210 | anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())), |
49aad941 FG |
211 | "cut".to_owned(), |
212 | ), | |
213 | Severity::Incomplete => ( | |
fe692bf9 | 214 | anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())), |
49aad941 FG |
215 | "incomplete".to_owned(), |
216 | ), | |
217 | }; | |
49aad941 | 218 | |
781aab86 | 219 | let writer = anstream::stderr(); |
49aad941 FG |
220 | let mut writer = writer.lock(); |
221 | let _ = writeln!( | |
222 | writer, | |
fe692bf9 FG |
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(), | |
49aad941 FG |
228 | ); |
229 | } | |
230 | ||
231 | pub fn result(depth: usize, name: &dyn crate::lib::std::fmt::Display, severity: Severity) { | |
fe692bf9 | 232 | let gutter_style = anstyle::Style::new().bold(); |
49aad941 FG |
233 | |
234 | let (call_width, _) = column_widths(); | |
235 | ||
236 | let call_column = format!("{:depth$}| {name}", ""); | |
237 | ||
fe692bf9 | 238 | let (status_style, status) = match severity { |
49aad941 | 239 | Severity::Success => ( |
fe692bf9 | 240 | anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Green.into())), |
49aad941 FG |
241 | "", |
242 | ), | |
243 | Severity::Backtrack => ( | |
fe692bf9 | 244 | anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Yellow.into())), |
49aad941 FG |
245 | "backtrack", |
246 | ), | |
247 | Severity::Cut => ( | |
fe692bf9 | 248 | anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())), |
49aad941 FG |
249 | "cut", |
250 | ), | |
251 | Severity::Incomplete => ( | |
fe692bf9 | 252 | anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into())), |
49aad941 FG |
253 | "incomplete", |
254 | ), | |
255 | }; | |
49aad941 | 256 | |
781aab86 | 257 | let writer = anstream::stderr(); |
49aad941 FG |
258 | let mut writer = writer.lock(); |
259 | let _ = writeln!( | |
260 | writer, | |
fe692bf9 FG |
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(), | |
49aad941 FG |
266 | ); |
267 | } | |
268 | ||
49aad941 FG |
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 | ||
284 | fn term_width() -> usize { | |
285 | columns_env().or_else(query_width).unwrap_or(80) | |
286 | } | |
287 | ||
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 | ||
297 | fn columns_env() -> Option<usize> { | |
298 | std::env::var("COLUMNS") | |
299 | .ok() | |
300 | .and_then(|c| c.parse::<usize>().ok()) | |
301 | } |