nom_tracable/
lib.rs

1//! `nom-tracable` is an extension of [nom](https://docs.rs/nom) to trace parser.
2//!
3//! ## Examples
4//!
5//! The following example show a quick example.
6//!
7//! ```
8//! use nom::character::complete::*;
9//! use nom::IResult;
10//! use nom_locate::LocatedSpan;
11//! use nom_tracable::{tracable_parser, TracableInfo};
12//!
13//! // Input type must implement trait Tracable
14//! // nom_locate::LocatedSpan<T, TracableInfo> implements it.
15//! type Span<'a> = LocatedSpan<&'a str, TracableInfo>;
16//!
17//! // Apply tracable_parser by custom attribute
18//! #[tracable_parser]
19//! pub fn term(s: Span) -> IResult<Span, String> {
20//!     let (s, x) = char('1')(s)?;
21//!     Ok((s, x.to_string()))
22//! }
23//!
24//! #[test]
25//! fn test() {
26//!     // Configure trace setting
27//!     let info = TracableInfo::new().forward(true).backward(true);
28//!     let ret = term(LocatedSpan::new_extra("1", info));
29//!     assert_eq!("\"1\"", format!("{:?}", ret.unwrap().1));
30//! }
31//! ```
32
33#[cfg(feature = "trace")]
34use nom::IResult;
35/// Custom attribute to enable trace
36pub use nom_tracable_macros::tracable_parser;
37use std::{collections::HashMap, io::Write};
38
39/// Trait to indicate the type can display as fragment.
40pub trait FragmentDisplay {
41    fn display(&self, width: usize) -> String;
42}
43
44impl FragmentDisplay for &[u8] {
45    fn display(&self, width: usize) -> String {
46        self.iter()
47            .take(width / 2)
48            .map(|x| format!("{:>02X}", x))
49            .collect()
50    }
51}
52
53impl FragmentDisplay for &str {
54    fn display(&self, width: usize) -> String {
55        self.lines()
56            .next()
57            .unwrap_or_else(|| "")
58            .chars()
59            .take(width)
60            .collect()
61    }
62}
63
64/// Trait to indicate the type has information for tracing.
65pub trait Tracable: HasTracableInfo {
66    fn inc_depth(self) -> Self;
67    fn dec_depth(self) -> Self;
68    fn format(&self) -> String;
69    fn header(&self) -> String;
70}
71
72/// Trait to indicate `TracableInfo` is provided.
73pub trait HasTracableInfo {
74    fn get_tracable_info(&self) -> TracableInfo;
75    fn set_tracable_info(self, info: TracableInfo) -> Self;
76}
77
78/// Struct to have trace configuration.
79#[derive(Clone, Copy, Debug, PartialEq)]
80pub struct TracableInfo {
81    #[cfg(feature = "trace")]
82    pub depth: usize,
83    #[cfg(feature = "trace")]
84    pub forward: bool,
85    #[cfg(feature = "trace")]
86    pub backward: bool,
87    #[cfg(feature = "trace")]
88    pub custom: bool,
89    #[cfg(feature = "trace")]
90    pub color: bool,
91    #[cfg(feature = "trace")]
92    pub count_width: usize,
93    #[cfg(feature = "trace")]
94    pub parser_width: usize,
95    #[cfg(feature = "trace")]
96    pub fragment_width: usize,
97    #[cfg(feature = "trace")]
98    pub fold: u64,
99}
100
101impl Default for TracableInfo {
102    fn default() -> Self {
103        TracableInfo {
104            #[cfg(feature = "trace")]
105            depth: 0,
106            #[cfg(feature = "trace")]
107            forward: true,
108            #[cfg(feature = "trace")]
109            backward: true,
110            #[cfg(feature = "trace")]
111            custom: true,
112            #[cfg(feature = "trace")]
113            color: true,
114            #[cfg(feature = "trace")]
115            count_width: 10,
116            #[cfg(feature = "trace")]
117            parser_width: 96,
118            #[cfg(feature = "trace")]
119            fragment_width: 96,
120            #[cfg(feature = "trace")]
121            fold: 0,
122        }
123    }
124}
125
126#[cfg(feature = "trace")]
127impl TracableInfo {
128    pub fn new() -> Self {
129        TracableInfo::default()
130    }
131
132    pub fn depth(mut self, x: usize) -> Self {
133        self.depth = x;
134        self
135    }
136
137    /// Set whether forward trace is displayed.
138    pub fn forward(mut self, x: bool) -> Self {
139        self.forward = x;
140        self
141    }
142
143    /// Set whether backward trace is displayed.
144    pub fn backward(mut self, x: bool) -> Self {
145        self.backward = x;
146        self
147    }
148
149    /// Set whether custom trace is displayed.
150    pub fn custom(mut self, x: bool) -> Self {
151        self.custom = x;
152        self
153    }
154
155    /// Set whether color is enabled.
156    pub fn color(mut self, x: bool) -> Self {
157        self.color = x;
158        self
159    }
160
161    /// Set the width of forward/backward count.
162    pub fn count_width(mut self, x: usize) -> Self {
163        self.count_width = x;
164        self
165    }
166
167    /// Set the width of parser name.
168    pub fn parser_width(mut self, x: usize) -> Self {
169        self.parser_width = x;
170        self
171    }
172
173    /// Set the width of fragment.
174    pub fn fragment_width(mut self, x: usize) -> Self {
175        self.fragment_width = x;
176        self
177    }
178
179    /// Set the name of folding parser.
180    pub fn fold(mut self, x: &str) -> Self {
181        let index =
182            crate::TRACABLE_STORAGE.with(|storage| storage.borrow_mut().get_parser_index(x));
183
184        let val = 1u64 << index;
185        let mask = !(1u64 << index);
186
187        self.fold = (self.fold & mask) | val;
188        self
189    }
190
191    fn folded(self, x: &str) -> bool {
192        let index =
193            crate::TRACABLE_STORAGE.with(|storage| storage.borrow_mut().get_parser_index(x));
194
195        if index < 64 {
196            ((self.fold >> index) & 1u64) == 1u64
197        } else {
198            false
199        }
200    }
201}
202
203#[cfg(not(feature = "trace"))]
204impl TracableInfo {
205    pub fn new() -> Self {
206        TracableInfo::default()
207    }
208
209    pub fn forward(self, _x: bool) -> Self {
210        self
211    }
212
213    pub fn backward(self, _x: bool) -> Self {
214        self
215    }
216
217    pub fn custom(self, _x: bool) -> Self {
218        self
219    }
220
221    pub fn color(self, _x: bool) -> Self {
222        self
223    }
224
225    pub fn count_width(self, _x: usize) -> Self {
226        self
227    }
228
229    pub fn parser_width(self, _x: usize) -> Self {
230        self
231    }
232
233    pub fn fragment_width(self, _x: usize) -> Self {
234        self
235    }
236
237    pub fn fold(self, _x: &str) -> Self {
238        self
239    }
240}
241
242impl HasTracableInfo for TracableInfo {
243    fn get_tracable_info(&self) -> TracableInfo {
244        *self
245    }
246
247    fn set_tracable_info(self, info: TracableInfo) -> Self {
248        info
249    }
250}
251
252#[cfg(feature = "trace")]
253impl<T, U: HasTracableInfo> HasTracableInfo for nom_locate::LocatedSpan<T, U> {
254    fn get_tracable_info(&self) -> TracableInfo {
255        self.extra.get_tracable_info()
256    }
257
258    fn set_tracable_info(mut self, info: TracableInfo) -> Self {
259        self.extra = self.extra.set_tracable_info(info);
260        self
261    }
262}
263
264#[cfg(feature = "trace")]
265impl<T: FragmentDisplay + nom::AsBytes, U: HasTracableInfo> Tracable
266    for nom_locate::LocatedSpan<T, U>
267{
268    fn inc_depth(self) -> Self {
269        let info = self.get_tracable_info();
270        let info = info.depth(info.depth + 1);
271        self.set_tracable_info(info)
272    }
273
274    fn dec_depth(self) -> Self {
275        let info = self.get_tracable_info();
276        let info = info.depth(info.depth - 1);
277        self.set_tracable_info(info)
278    }
279
280    fn format(&self) -> String {
281        let info = self.get_tracable_info();
282        let fragment = self.fragment().display(info.fragment_width);
283        format!("{:<8} : {}", self.location_offset(), fragment)
284    }
285
286    fn header(&self) -> String {
287        format!("{:<8} : {}", "offset", "fragment")
288    }
289}
290
291#[derive(Debug, Default)]
292struct TracableStorage {
293    forward_count: usize,
294    backward_count: usize,
295    parser_indexes: HashMap<String, usize>,
296    parser_index_next: usize,
297    histogram: HashMap<String, usize>,
298    cumulative_histogram: HashMap<String, usize>,
299    cumulative_working: HashMap<(String, usize), usize>,
300}
301
302#[allow(dead_code)]
303impl TracableStorage {
304    fn new() -> Self {
305        TracableStorage::default()
306    }
307
308    fn init(&mut self) {
309        self.forward_count = 0;
310        self.backward_count = 0;
311        self.histogram.clear();
312        self.cumulative_histogram.clear();
313        self.cumulative_working.clear();
314    }
315
316    fn get_forward_count(&self) -> usize {
317        self.forward_count
318    }
319
320    fn get_backward_count(&self) -> usize {
321        self.backward_count
322    }
323
324    fn inc_forward_count(&mut self) {
325        self.forward_count += 1
326    }
327
328    fn inc_backward_count(&mut self) {
329        self.backward_count += 1
330    }
331
332    fn inc_histogram(&mut self, key: &str) {
333        let next = if let Some(x) = self.histogram.get(key) {
334            x + 1
335        } else {
336            1
337        };
338        self.histogram.insert(String::from(key), next);
339    }
340
341    fn inc_cumulative_histogram(&mut self, key: &str, cnt: usize) {
342        let next = if let Some(x) = self.cumulative_histogram.get(key) {
343            x + cnt
344        } else {
345            cnt
346        };
347        self.cumulative_histogram.insert(String::from(key), next);
348    }
349
350    fn add_cumulative(&mut self, key: &str, depth: usize) {
351        self.cumulative_working
352            .insert((String::from(key), depth), 0);
353    }
354
355    fn inc_cumulative(&mut self) {
356        for val in self.cumulative_working.values_mut() {
357            *val = *val + 1;
358        }
359    }
360
361    fn del_cumulative(&mut self, key: &str, depth: usize) {
362        self.cumulative_working.remove(&(key.to_string(), depth));
363    }
364
365    fn get_cumulative(&mut self, key: &str, depth: usize) -> Option<&usize> {
366        self.cumulative_working.get(&(key.to_string(), depth))
367    }
368
369    fn get_parser_index(&mut self, key: &str) -> usize {
370        if let Some(x) = self.parser_indexes.get(key) {
371            *x
372        } else {
373            let new_index = self.parser_index_next;
374            self.parser_index_next += 1;
375            self.parser_indexes.insert(String::from(key), new_index);
376            new_index
377        }
378    }
379}
380
381#[cfg(feature = "trace")]
382thread_local!(
383    static TRACABLE_STORAGE: core::cell::RefCell<crate::TracableStorage> = {
384        core::cell::RefCell::new(crate::TracableStorage::new())
385    }
386);
387
388/// Show histogram of parser call count.
389///
390/// The statistics information to generate histogram is reset at each parser call.
391/// Therefore `histogram` should be called before next parser call.
392/// The information is thread independent because it is stored at thread local storage.
393///
394/// ```
395/// # use nom::character::complete::*;
396/// # use nom::IResult;
397/// # use nom_locate::LocatedSpan;
398/// # use nom_tracable::{cumulative_histogram, tracable_parser, TracableInfo};
399/// #
400/// # type Span<'a> = LocatedSpan<&'a str, TracableInfo>;
401/// #
402/// # #[tracable_parser]
403/// # pub fn term(s: Span) -> IResult<Span, String> {
404/// #     let (s, x) = char('1')(s)?;
405/// #     Ok((s, x.to_string()))
406/// # }
407/// #
408/// # #[test]
409/// # fn test() {
410///     let ret = term(LocatedSpan::new_extra("1", TracableInfo::new()));
411///     histogram(); // Show histogram of "1" parsing
412///
413///     let ret = term(LocatedSpan::new_extra("11", TracableInfo::new()));
414///     histogram(); // Show histogram of "11" parsing
415/// # }
416/// ```
417pub fn histogram() {
418    histogram_internal();
419}
420
421#[cfg(feature = "trace")]
422fn histogram_internal() {
423    crate::TRACABLE_STORAGE.with(|storage| {
424        let storage = storage.borrow();
425        show_histogram("histogram", &storage.histogram);
426    });
427}
428
429#[cfg(not(feature = "trace"))]
430fn histogram_internal() {}
431
432/// Show cumulative histogram of parser call count.
433///
434/// The call count includes the counts of children parsers.
435///
436/// The statistics information to generate histogram is reset at each parser call.
437/// Therefore `cumulative_histogram` should be called before next parser call.
438/// The information is thread independent because it is stored at thread local storage.
439///
440/// ```
441/// # use nom::character::complete::*;
442/// # use nom::IResult;
443/// # use nom_locate::LocatedSpan;
444/// # use nom_tracable::{cumulative_histogram, tracable_parser, TracableInfo};
445/// #
446/// # type Span<'a> = LocatedSpan<&'a str, TracableInfo>;
447/// #
448/// # #[tracable_parser]
449/// # pub fn term(s: Span) -> IResult<Span, String> {
450/// #     let (s, x) = char('1')(s)?;
451/// #     Ok((s, x.to_string()))
452/// # }
453/// #
454/// # #[test]
455/// # fn test() {
456///     let ret = term(LocatedSpan::new_extra("1", TracableInfo::new()));
457///     cumulative_histogram(); // Show cumulative histogram of "1" parsing
458///
459///     let ret = term(LocatedSpan::new_extra("11", TracableInfo::new()));
460///     cumulative_histogram(); // Show cumulative histogram of "11" parsing
461/// # }
462/// ```
463pub fn cumulative_histogram() {
464    cumulative_histogram_internal();
465}
466
467#[cfg(feature = "trace")]
468fn cumulative_histogram_internal() {
469    crate::TRACABLE_STORAGE.with(|storage| {
470        let storage = storage.borrow();
471        show_histogram("cumulative histogram", &storage.cumulative_histogram);
472    });
473}
474
475#[cfg(not(feature = "trace"))]
476fn cumulative_histogram_internal() {}
477
478#[allow(dead_code)]
479fn show_histogram(title: &str, map: &HashMap<String, usize>) {
480    let mut result = Vec::new();
481    let mut max_parser_len = "parser".len();
482    let mut max_count = 0;
483    let mut max_count_len = "count".len();
484    for (p, c) in map {
485        result.push((p, c));
486        max_parser_len = max_parser_len.max(p.len());
487        max_count = max_count.max(*c);
488        max_count_len = max_count_len.max(format!("{}", c).len());
489    }
490
491    result.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap());
492
493    let bar_length = 50;
494
495    let mut lock = if cfg!(feature = "stderr") {
496        Box::new(std::io::stderr().lock()) as Box<dyn Write>
497    } else {
498        Box::new(std::io::stdout().lock()) as Box<dyn Write>
499    };
500
501    writeln!(
502        lock,
503        "\n{:<parser$} | {:<bar$} | {}",
504        "parser",
505        title,
506        "count",
507        parser = max_parser_len,
508        bar = bar_length,
509    )
510    .unwrap();
511
512    writeln!(
513        lock,
514        "{:<parser$} | {:<bar$} | {}",
515        "-".repeat(max_parser_len),
516        "-".repeat(bar_length),
517        "-".repeat(max_count_len),
518        parser = max_parser_len,
519        bar = bar_length,
520    )
521    .unwrap();
522
523    for (p, c) in &result {
524        let bar = *c * bar_length / max_count;
525        if bar > 0 {
526            writeln!(
527                lock,
528                "{:<parser$} | {}{} | {}",
529                p,
530                ".".repeat(bar),
531                " ".repeat(bar_length - bar),
532                c,
533                parser = max_parser_len,
534            )
535            .unwrap();
536        }
537    }
538    writeln!(lock, "").unwrap()
539}
540
541/// Function to display forward trace.
542/// This is inserted by `#[tracable_parser]`.
543#[cfg(feature = "trace")]
544pub fn forward_trace<T: Tracable>(input: T, name: &str) -> (TracableInfo, T) {
545    let info = input.get_tracable_info();
546    let depth = info.depth;
547
548    let mut lock = if cfg!(feature = "stderr") {
549        Box::new(std::io::stderr().lock()) as Box<dyn Write>
550    } else {
551        Box::new(std::io::stdout().lock()) as Box<dyn Write>
552    };
553
554    if depth == 0 {
555        crate::TRACABLE_STORAGE.with(|storage| {
556            storage.borrow_mut().init();
557        });
558        let forward_backword = if info.forward & info.backward {
559            format!(
560                "{:<count_width$} {:<count_width$}",
561                "forward",
562                "backward",
563                count_width = info.count_width
564            )
565        } else if info.forward {
566            format!(
567                "{:<count_width$}",
568                "forward",
569                count_width = info.count_width
570            )
571        } else {
572            format!(
573                "{:<count_width$}",
574                "backward",
575                count_width = info.count_width
576            )
577        };
578
579        let control_witdh = if info.color { 11 } else { 0 };
580
581        writeln!(
582            lock,
583            "\n{} : {:<parser_width$} : {}",
584            forward_backword,
585            "parser",
586            input.header(),
587            parser_width = info.parser_width - control_witdh,
588        )
589        .unwrap();
590    }
591
592    if info.forward {
593        let forward_count = crate::TRACABLE_STORAGE.with(|storage| {
594            storage.borrow_mut().inc_forward_count();
595            storage.borrow().get_forward_count()
596        });
597
598        let forward_backword = if info.backward {
599            format!(
600                "{:<count_width$} {:<count_width$}",
601                forward_count,
602                "",
603                count_width = info.count_width
604            )
605        } else {
606            format!(
607                "{:<count_width$}",
608                forward_count,
609                count_width = info.count_width
610            )
611        };
612
613        let color = if info.color { "\u{001b}[1;37m" } else { "" };
614        let reset = if info.color { "\u{001b}[0m" } else { "" };
615        let folded = if info.folded(name) { "+" } else { " " };
616
617        writeln!(
618            lock,
619            "{} : {:<parser_width$} : {}",
620            forward_backword,
621            format!(
622                "{}{}-> {} {}{}",
623                color,
624                " ".repeat(depth),
625                name,
626                folded,
627                reset
628            ),
629            input.format(),
630            parser_width = info.parser_width,
631        )
632        .unwrap();
633    }
634
635    crate::TRACABLE_STORAGE.with(|storage| {
636        storage.borrow_mut().inc_histogram(name);
637        storage.borrow_mut().add_cumulative(name, depth);
638        storage.borrow_mut().inc_cumulative();
639    });
640
641    let input = if info.folded(name) {
642        let info = info.forward(false).backward(false).custom(false);
643        input.set_tracable_info(info)
644    } else {
645        input
646    };
647
648    let input = input.inc_depth();
649    (info, input)
650}
651
652/// Function to display backward trace.
653/// This is inserted by `#[tracable_parser]`.
654#[cfg(feature = "trace")]
655pub fn backward_trace<T: Tracable, U, V>(
656    input: IResult<T, U, V>,
657    name: &str,
658    info: TracableInfo,
659) -> IResult<T, U, V> {
660    let depth = info.depth;
661
662    crate::TRACABLE_STORAGE.with(|storage| {
663        let cnt = *storage.borrow_mut().get_cumulative(name, depth).unwrap();
664        storage.borrow_mut().inc_cumulative_histogram(name, cnt);
665    });
666
667    if info.backward {
668        let backward_count = crate::TRACABLE_STORAGE.with(|storage| {
669            storage.borrow_mut().inc_backward_count();
670            storage.borrow().get_backward_count()
671        });
672
673        let forward_backword = if info.forward {
674            format!(
675                "{:<count_width$} {:<count_width$}",
676                "",
677                backward_count,
678                count_width = info.count_width
679            )
680        } else {
681            format!(
682                "{:<count_width$}",
683                backward_count,
684                count_width = info.count_width
685            )
686        };
687
688        let color_ok = if info.color { "\u{001b}[1;32m" } else { "" };
689        let color_err = if info.color { "\u{001b}[1;31m" } else { "" };
690        let reset = if info.color { "\u{001b}[0m" } else { "" };
691        let folded = if info.folded(name) { "+" } else { " " };
692
693        let mut lock = if cfg!(feature = "stderr") {
694            Box::new(std::io::stderr().lock()) as Box<dyn Write>
695        } else {
696            Box::new(std::io::stdout().lock()) as Box<dyn Write>
697        };
698
699        match input {
700            Ok((s, x)) => {
701                writeln!(
702                    lock,
703                    "{} : {:<parser_width$} : {}",
704                    forward_backword,
705                    format!(
706                        "{}{}<- {} {}{}",
707                        color_ok,
708                        " ".repeat(depth),
709                        name,
710                        folded,
711                        reset
712                    ),
713                    s.format(),
714                    parser_width = info.parser_width,
715                )
716                .unwrap();
717
718                let s = if info.folded(name) {
719                    let info = s
720                        .get_tracable_info()
721                        .forward(info.forward)
722                        .backward(info.backward)
723                        .custom(info.custom);
724                    s.set_tracable_info(info)
725                } else {
726                    s
727                };
728
729                Ok((s.dec_depth(), x))
730            }
731            Err(x) => {
732                writeln!(
733                    lock,
734                    "{} : {:<parser_width$}",
735                    forward_backword,
736                    format!(
737                        "{}{}<- {} {}{}",
738                        color_err,
739                        " ".repeat(depth),
740                        name,
741                        folded,
742                        reset
743                    ),
744                    parser_width = info.parser_width,
745                )
746                .unwrap();
747                Err(x)
748            }
749        }
750    } else {
751        input
752    }
753}
754
755/// Function to display custom trace.
756#[cfg(feature = "trace")]
757pub fn custom_trace<T: Tracable>(input: &T, name: &str, message: &str, color: &str) {
758    let info = input.get_tracable_info();
759
760    if info.custom {
761        let depth = info.depth;
762        let forward_backword = format!(
763            "{:<count_width$} {:<count_width$}",
764            "",
765            "",
766            count_width = info.count_width
767        );
768
769        let color = if info.color { color } else { "" };
770        let reset = if info.color { "\u{001b}[0m" } else { "" };
771
772        let mut lock = if cfg!(feature = "stderr") {
773            Box::new(std::io::stderr().lock()) as Box<dyn Write>
774        } else {
775            Box::new(std::io::stdout().lock()) as Box<dyn Write>
776        };
777
778        writeln!(
779            lock,
780            "{} : {:<parser_width$} : {}",
781            forward_backword,
782            format!("{}{}   {}{}", color, " ".repeat(depth), name, reset),
783            message,
784            parser_width = info.parser_width,
785        )
786        .unwrap();
787    }
788}