nom_tracer/
lib.rs

1// Copyright (c) Hexbee
2// SPDX-License-Identifier: Apache-2.0
3
4#[cfg(feature = "trace")]
5use crate::tags::TraceTags;
6#[cfg(feature = "trace-silencing")]
7use crate::traces::Trace;
8#[cfg(feature = "trace-context")]
9use nom::error::ContextError;
10use {
11    nom::{IResult, Parser},
12    std::fmt::Debug,
13};
14
15#[cfg(feature = "trace-color")]
16#[allow(dead_code)]
17pub(crate) mod ansi;
18#[cfg(feature = "trace")]
19pub mod events;
20#[cfg(feature = "trace")]
21pub mod tags;
22#[cfg(feature = "trace")]
23pub mod traces;
24
25pub mod macros;
26
27pub const DEFAULT_TAG: &str = "default";
28
29thread_local! {
30    /// Thread-local storage for the global [TraceTags].
31    ///
32    /// [TRACE_TAGS] provides a thread-safe way to access and modify the global trace list.
33    /// It's implemented as thread-local storage, ensuring that each thread has its own
34    /// independent trace list. This allows for concurrent tracing in multithreaded applications
35    /// without the need for explicit synchronization.
36    ///
37    /// The [RefCell](std::cell::RefCell) allows for interior mutability, so the [TraceTags] can be
38    /// modified even when accessed through a shared reference.
39    ///
40    /// Usage of [TRACE_TAGS] is typically wrapped in the [tr] and [tr_tag_ctx] functions,
41    /// which provide a more convenient interface for adding trace events.
42    #[cfg(feature = "trace")]
43    pub static TRACE_TAGS: std::cell::RefCell<TraceTags> = std::cell::RefCell::new(TraceTags::new());
44
45    /// Thread-local storage for silent tracing (used with trace-silencing feature)
46    #[cfg(feature = "trace-silencing")]
47    pub static TRACE_SILENT: std::cell::RefCell<Trace> = std::cell::RefCell::new(Trace::default());
48
49    /// Thread-local storage for tree silence levels (used with trace-silencing feature)
50    #[cfg(feature = "trace-silencing")]
51    pub static TREE_SILENCE_LEVELS: std::cell::RefCell<Vec<usize>> = const { std::cell::RefCell::new(vec![]) };
52}
53
54#[cfg(feature = "trace-context")]
55pub trait TraceError<I>: Debug + ContextError<I> {}
56#[cfg(feature = "trace-context")]
57impl<I, E> TraceError<I> for E where E: Debug + ContextError<I> {}
58
59#[cfg(not(feature = "trace-context"))]
60pub trait TraceError<I>: Debug {}
61#[cfg(not(feature = "trace-context"))]
62impl<I, E> TraceError<I> for E where E: Debug {}
63
64/// Main tracing function that wraps a parser with tracing functionality.
65///
66/// This function is the core of nom-tracer. It wraps a parser and adds tracing capabilities,
67/// recording the parser's execution path and results.
68///
69/// # Arguments
70///
71/// * `tag` - A static string used to categorize the trace events.
72/// * `context` - An optional static string providing additional context for the trace.
73/// * `name` - A static string identifying the parser being traced.
74/// * `parser` - The parser function to be wrapped with tracing.
75pub fn tr<I, O, E, F>(
76    #[cfg(feature = "trace")] tag: &'static str,
77    #[cfg(not(feature = "trace"))] _tag: &'static str,
78    #[cfg(any(feature = "trace", feature = "trace-context"))] context: Option<&'static str>,
79    #[cfg(not(any(feature = "trace", feature = "trace-context")))] _context: Option<&'static str>,
80    #[cfg(feature = "trace")] name: &'static str,
81    #[cfg(not(feature = "trace"))] _name: &'static str,
82    mut parser: F,
83) -> impl FnMut(I) -> IResult<I, O, E>
84where
85    I: AsRef<str>,
86    F: Parser<I, O, E>,
87    I: Clone,
88    O: Debug,
89    E: TraceError<I>,
90{
91    #[cfg(feature = "trace")]
92    {
93        move |input: I| {
94            let input1 = input.clone();
95            let input2 = input.clone();
96            #[cfg(feature = "trace-context")]
97            let input3 = input.clone();
98
99            #[cfg(feature = "trace-silencing")]
100            let silent = TREE_SILENCE_LEVELS.with(|levels| !levels.borrow().is_empty());
101
102            #[cfg(feature = "trace-silencing")]
103            if silent {
104                TRACE_SILENT.with(|trace| {
105                    (*trace.borrow_mut()).open(context, input1, name, true);
106                });
107            } else {
108                TRACE_TAGS.with(|tags| {
109                    (*tags.borrow_mut()).open(tag, context, input1, name, false);
110                });
111            };
112            #[cfg(not(feature = "trace-silencing"))]
113            TRACE_TAGS.with(|tags| {
114                (*tags.borrow_mut()).open(tag, context, input1, name, false);
115            });
116
117            let res = parser.parse(input);
118
119            #[cfg(feature = "trace-silencing")]
120            if silent {
121                TRACE_SILENT.with(|trace| {
122                    (*trace.borrow_mut()).close(context, input2, name, &res, true);
123                });
124            } else {
125                TRACE_TAGS.with(|tags| {
126                    (*tags.borrow_mut()).close(tag, context, input2, name, &res, false);
127                });
128            }
129
130            #[cfg(not(feature = "trace-silencing"))]
131            TRACE_TAGS.with(|tags| {
132                (*tags.borrow_mut()).close(tag, context, input2, name, &res, false);
133            });
134
135            #[cfg(not(feature = "trace-context"))]
136            return res;
137
138            #[cfg(feature = "trace-context")]
139            if let Some(context) = context {
140                add_context_to_err(context, input3, res)
141            } else {
142                res
143            }
144        }
145    }
146
147    #[cfg(not(feature = "trace"))]
148    {
149        #[cfg(feature = "trace-context")]
150        {
151            move |input: I| {
152                if let Some(context) = context {
153                    add_context_to_err(context, input.clone(), parser.parse(input))
154                } else {
155                    parser.parse(input)
156                }
157            }
158        }
159
160        #[cfg(not(feature = "trace-context"))]
161        move |input: I| parser.parse(input)
162    }
163}
164
165/// Function to silence tracing for a subtree of parsers.
166///
167/// This is used to reduce noise in the trace output for well-tested or less interesting
168/// parts of the parser.
169///
170/// # Arguments
171///
172/// * `tag` - A static string used to categorize the trace events.
173/// * `context` - An optional static string providing additional context for the trace.
174/// * `name` - A static string identifying the parser being silenced.
175/// * `parser` - The parser function to be silenced.
176#[cfg(feature = "trace-silencing")]
177pub fn silence_tree<I, O, E, F>(
178    tag: &'static str,
179    context: Option<&'static str>,
180    name: &'static str,
181    mut parser: F,
182) -> impl FnMut(I) -> IResult<I, O, E>
183where
184    I: AsRef<str>,
185    F: Parser<I, O, E>,
186    I: Clone,
187    O: Debug,
188    E: TraceError<I>,
189{
190    move |input: I| {
191        let input1 = input.clone();
192        let input2 = input.clone();
193        let input3 = input.clone();
194
195        let silent_cut_level = TRACE_SILENT.with(|tags| tags.borrow_mut().level);
196        let cut_level = TRACE_TAGS.with(|tags| (*tags.borrow_mut()).level_for_tag(tag));
197        let cut_level = silent_cut_level.max(cut_level);
198
199        TREE_SILENCE_LEVELS.with(|levels| {
200            (*levels.borrow_mut()).push(cut_level);
201        });
202
203        TRACE_SILENT.with(|trace| {
204            (*trace.borrow_mut()).set_level(cut_level);
205            (*trace.borrow_mut()).open(context, input1, name, true);
206        });
207
208        let res = parser.parse(input);
209
210        TRACE_SILENT.with(|trace| {
211            (*trace.borrow_mut()).close(context, input2, name, &res, true);
212        });
213
214        TREE_SILENCE_LEVELS.with(|levels| {
215            (*levels.borrow_mut()).pop();
216        });
217
218        #[cfg(feature = "trace-context")]
219        return add_context_to_err(name, input3, res);
220
221        #[cfg(not(feature = "trace-context"))]
222        res
223    }
224}
225
226/// Helper function to add context to error results.
227///
228/// This is used when the trace-context feature is enabled to provide more
229/// detailed error information.
230#[cfg(feature = "trace-context")]
231fn add_context_to_err<I, O, E>(
232    name: &'static str,
233    input: I,
234    res: IResult<I, O, E>,
235) -> IResult<I, O, E>
236where
237    I: AsRef<str>,
238    I: Clone,
239    O: Debug,
240    E: TraceError<I>,
241{
242    match res {
243        Ok(o) => Ok(o),
244        Err(nom::Err::Error(e)) => Err(nom::Err::Error(E::add_context(input, name, e))),
245        Err(nom::Err::Failure(e)) => Err(nom::Err::Failure(E::add_context(input, name, e))),
246        Err(nom::Err::Incomplete(i)) => Err(nom::Err::Incomplete(i)),
247    }
248}
249
250/// Retrieves the trace for a specific tag.
251///
252/// # Arguments
253///
254/// * `tag` - A static string identifying the tag for which to retrieve the trace.
255///
256/// # Returns
257///
258/// Returns a string representation of the trace, or a message if no trace is found.
259pub fn get_trace_for_tag(
260    #[cfg(feature = "trace")] tag: &'static str,
261    #[cfg(not(feature = "trace"))] _tag: &'static str,
262) -> Option<String> {
263    #[cfg(feature = "trace")]
264    {
265        TRACE_TAGS.with(|trace| trace.borrow().traces.get(tag).map(ToString::to_string))
266    }
267
268    #[cfg(not(feature = "trace"))]
269    None
270}
271
272/// Prints the trace for a specific tag.
273///
274/// # Arguments
275///
276/// * `tag` - A static string identifying the tag for which to print the trace.
277pub fn print_trace_for_tag(tag: &'static str) {
278    print(get_trace_for_tag(tag).unwrap_or(format!("No trace found for tag '{}'", tag)));
279}
280
281/// Helper function to for unbuffered output.
282///
283/// # Arguments
284///
285/// * `s` - The string to be printed.
286pub(crate) fn print<I: AsRef<str>>(s: I) {
287    use std::io::Write;
288    let stdout = std::io::stdout();
289    let mut handle = stdout.lock();
290    write!(handle, "{}", s.as_ref()).unwrap();
291}
292
293#[cfg(test)]
294mod tests {
295    use {
296        super::*,
297        nom::{bytes::complete::tag, error::VerboseError},
298    };
299
300    #[cfg(feature = "trace")]
301    mod trace_tests {
302        use {super::*, nom::sequence::tuple};
303
304        #[test]
305        fn test_tr_no_context() {
306            let mut parser = tr(
307                DEFAULT_TAG,
308                None,
309                "test_parser",
310                tag::<_, _, VerboseError<_>>("hello"),
311            );
312            let result = parser("hello world");
313            assert!(result.is_ok());
314
315            let trace = get_trace_for_tag(DEFAULT_TAG).unwrap();
316            assert!(trace.contains("test_parser"));
317            assert!(trace.contains("hello world"));
318            assert!(trace.contains("-> Ok"));
319        }
320
321        #[test]
322        fn test_tr_context() {
323            let mut parser = tr(
324                DEFAULT_TAG,
325                Some("context"),
326                "test_parser",
327                tag::<_, _, VerboseError<_>>("hello"),
328            );
329            let result = parser("hello world");
330            assert!(result.is_ok());
331
332            let trace = get_trace_for_tag(DEFAULT_TAG).unwrap();
333            assert!(trace.contains("test_parser"));
334            assert!(trace.contains("context"));
335            assert!(trace.contains("hello world"));
336            assert!(trace.contains("-> Ok"));
337        }
338
339        #[test]
340        fn test_nested_traces() {
341            fn parse_nested(input: &str) -> IResult<&str, (&str, &str)> {
342                tr(
343                    DEFAULT_TAG,
344                    None,
345                    "outer",
346                    tuple((
347                        tr(DEFAULT_TAG, None, "inner_a", tag("a")),
348                        tr(DEFAULT_TAG, None, "inner_b", tag("b")),
349                    )),
350                )(input)
351            }
352
353            let traced_parser = parse_nested;
354            let result = traced_parser("ab");
355            assert!(result.is_ok());
356
357            let trace = get_trace_for_tag(DEFAULT_TAG).unwrap();
358            assert!(trace.contains("outer"));
359            assert!(trace.contains("inner_a"));
360            assert!(trace.contains("inner_b"));
361        }
362
363        #[test]
364        fn test_get_trace_for_tag() {
365            let mut parser = tr(
366                DEFAULT_TAG,
367                None,
368                "test_parser",
369                tag::<_, _, VerboseError<_>>("hello"),
370            );
371            let _ = parser("hello world");
372
373            let trace = get_trace_for_tag(DEFAULT_TAG).unwrap();
374            assert!(trace.contains("test_parser"));
375            assert!(trace.contains("hello world"));
376        }
377
378        #[test]
379        fn test_get_trace_for_nonexistent_tag() {
380            let trace = get_trace_for_tag("nonexistent");
381            assert!(trace.is_none());
382        }
383    }
384
385    #[cfg(feature = "trace-silencing")]
386    mod trace_silencing_tests {
387        use {super::*, nom::sequence::tuple};
388
389        #[test]
390        fn test_silence_tree() {
391            let mut parser = silence_tree(
392                DEFAULT_TAG,
393                Some("context"),
394                "silent_parser",
395                tag::<_, _, VerboseError<_>>("hello"),
396            );
397            let result = parser("hello world");
398            assert!(result.is_ok());
399
400            let trace = get_trace_for_tag(DEFAULT_TAG).unwrap();
401            assert!(!trace.contains("silent_parser"));
402        }
403
404        #[test]
405        fn test_silence_tree_with_nested_parsers() {
406            let mut outer_parser = tr(
407                DEFAULT_TAG,
408                None,
409                "outer_parser",
410                silence_tree(
411                    DEFAULT_TAG,
412                    None,
413                    "inner_parser",
414                    tr(
415                        DEFAULT_TAG,
416                        None,
417                        "inner",
418                        tag::<_, _, VerboseError<_>>("hello"),
419                    ),
420                ),
421            );
422
423            let result = outer_parser("hello world");
424            assert!(result.is_ok());
425
426            let trace = get_trace_for_tag(DEFAULT_TAG).unwrap();
427            assert!(trace.contains("outer_parser"));
428            assert!(!trace.contains("inner_parser"));
429        }
430
431        #[test]
432        fn test_nested_silence_tree() {
433            #[allow(clippy::type_complexity)]
434            fn nested_parser(
435                input: &str,
436            ) -> IResult<&str, (&str, (&str, &str), &str), VerboseError<&str>> {
437                trace!(
438                    "outer",
439                    tuple((
440                        trace!("first", tag::<_, _, VerboseError<&str>>("a")),
441                        silence_tree!(
442                            "silent",
443                            tuple((
444                                trace!("second", tag("b")),
445                                silence_tree!("inner_silent", trace!("third", tag("c"))),
446                            ))
447                        ),
448                        trace!("fourth", tag("d")),
449                    ))
450                )(input)
451            }
452
453            // Run the parser
454            let result = nested_parser("abcd");
455            assert!(result.is_ok());
456
457            // Get the trace
458            let trace = get_trace!().unwrap();
459            println!("{}", trace);
460
461            // Check that the trace contains the expected elements
462            assert!(trace.contains("outer"));
463            assert!(trace.contains("first"));
464            assert!(trace.contains("fourth"));
465
466            // Check that the silenced parts are not in the trace
467            assert!(!trace.contains("silent"));
468            assert!(!trace.contains("second"));
469            assert!(!trace.contains("inner_silent"));
470            assert!(!trace.contains("third"));
471
472            // Additional checks to ensure correct nesting behavior
473            assert_eq!(trace.matches("[outer]").count(), 2); // Open and close for outer
474            assert_eq!(trace.matches("[first]").count(), 2); // Open and close for first
475            assert_eq!(trace.matches("[fourth]").count(), 2); // Open and close for fourth
476
477            // The silent part should not increase the visible nesting level
478            assert!(!trace.contains("| | |"));
479
480            // Check the order of operations
481            let trace_lines: Vec<&str> = trace.split('\n').collect();
482            assert!(
483                trace_lines
484                    .iter()
485                    .position(|&r| r.contains("first"))
486                    .unwrap()
487                    < trace_lines
488                        .iter()
489                        .position(|&r| r.contains("fourth"))
490                        .unwrap()
491            );
492        }
493    }
494
495    #[cfg(all(feature = "trace", feature = "trace-context"))]
496    mod trace_context_tests {
497        use {
498            super::*,
499            nom::error::{ErrorKind, VerboseErrorKind},
500        };
501
502        #[test]
503        fn test_add_context_to_err() {
504            let mut parser = tr(
505                DEFAULT_TAG,
506                Some("context"),
507                "test_parser",
508                tag::<_, _, VerboseError<_>>("hello"),
509            );
510            let result = parser("world");
511
512            assert!(result.is_err());
513
514            if let Err(nom::Err::Error(e)) = result {
515                assert_eq!(e.errors.len(), 2);
516                assert_eq!(e.errors[1].1, VerboseErrorKind::Context("context"));
517                assert_eq!(e.errors[0].1, VerboseErrorKind::Nom(ErrorKind::Tag));
518            } else {
519                panic!("Expected Err(nom::Err::Error)");
520            }
521        }
522    }
523
524    #[cfg(not(feature = "trace"))]
525    mod no_trace_tests {
526        use {
527            super::*,
528            nom::error::{ErrorKind, VerboseErrorKind},
529        };
530
531        #[test]
532        fn test_tr_without_trace() {
533            let mut parser = tr(
534                DEFAULT_TAG,
535                Some("context"),
536                "test_parser",
537                tag::<_, _, VerboseError<_>>("hello"),
538            );
539            let result = parser("hello world");
540            assert!(result.is_ok());
541            assert_eq!(result, Ok((" world", "hello")));
542        }
543
544        #[test]
545        fn test_get_trace_for_tag_without_trace() {
546            assert!(get_trace_for_tag(DEFAULT_TAG).is_none());
547        }
548
549        #[cfg(feature = "trace-context")]
550        mod context_without_trace_tests {
551            use {
552                super::*,
553                nom::error::{ErrorKind, VerboseErrorKind},
554            };
555
556            #[test]
557            fn test_context_addition_without_trace() {
558                let mut parser = tr(
559                    DEFAULT_TAG,
560                    Some("context"),
561                    "test_parser",
562                    tag::<_, _, VerboseError<_>>("hello"),
563                );
564                let result = parser("world");
565
566                assert!(result.is_err());
567
568                if let Err(nom::Err::Error(e)) = result {
569                    assert_eq!(e.errors.len(), 2);
570                    assert_eq!(e.errors[1].1, VerboseErrorKind::Context("context"));
571                    assert_eq!(e.errors[0].1, VerboseErrorKind::Nom(ErrorKind::Tag));
572                } else {
573                    panic!("Expected Err(nom::Err::Error)");
574                }
575            }
576        }
577
578        #[cfg(not(feature = "trace-context"))]
579        #[test]
580        fn test_error_without_trace_and_context() {
581            let mut parser = tr(
582                DEFAULT_TAG,
583                Some("context"),
584                "test_parser",
585                tag::<_, _, VerboseError<_>>("hello"),
586            );
587            let result = parser("world");
588
589            assert!(result.is_err());
590
591            if let Err(nom::Err::Error(e)) = result {
592                println!("{:?}", e);
593                assert_eq!(e.errors.len(), 1);
594                assert_eq!(e.errors[0].1, VerboseErrorKind::Nom(ErrorKind::Tag));
595            } else {
596                panic!("Expected Err(nom::Err::Error)");
597            }
598        }
599    }
600}