Skip to main content

syntree/
print.rs

1//! Helper utilities for pretty-printing trees.
2
3#![cfg(feature = "std")]
4#![cfg_attr(docsrs, doc(cfg(feature = "std")))]
5
6use std::fmt;
7use std::io::{Error, Write};
8
9use crate::flavor::Flavor;
10use crate::span::Span;
11use crate::tree::Tree;
12
13/// Pretty-print a tree without a source.
14///
15/// Each element is printed on its own line as its value and span, indented
16/// by depth. Elements without children are followed by a source reference,
17/// which this function replaces with `+`. If you have a source available you
18/// can use [`print_with_source`] instead.
19///
20/// Elements are told apart by whether they have children, so a node without
21/// children is printed exactly like a token.
22///
23/// # Examples
24///
25/// ```
26/// #[derive(Debug, Clone, Copy)]
27/// enum Syntax {
28///     NUMBER,
29///     WHITESPACE,
30///     OPERATOR,
31///     PLUS,
32/// }
33///
34/// use Syntax::*;
35///
36/// let tree = syntree::tree! {
37///     NUMBER => {
38///         (NUMBER, 3),
39///     },
40///     (WHITESPACE, 1),
41///     OPERATOR => {
42///         (PLUS, 1)
43///     },
44///     (WHITESPACE, 1),
45///     NUMBER => {
46///         (NUMBER, 2),
47///     },
48/// };
49///
50/// let mut s = Vec::new();
51/// syntree::print::print(&mut s, &tree)?;
52/// # let s = String::from_utf8(s)?;
53/// # assert_eq!(s, "NUMBER@0..3\n  NUMBER@0..3 +\nWHITESPACE@3..4 +\nOPERATOR@4..5\n  PLUS@4..5 +\nWHITESPACE@5..6 +\nNUMBER@6..8\n  NUMBER@6..8 +\n");
54/// # Ok::<_, Box<dyn core::error::Error>>(())
55/// ```
56///
57/// This would write:
58///
59/// ```text
60/// NUMBER@0..3
61///   NUMBER@0..3 +
62/// WHITESPACE@3..4 +
63/// OPERATOR@4..5
64///   PLUS@4..5 +
65/// WHITESPACE@5..6 +
66/// NUMBER@6..8
67///   NUMBER@6..8 +
68/// ```
69pub fn print<O, T, F>(o: O, tree: &Tree<T, F>) -> Result<(), Error>
70where
71    O: Write,
72    T: Copy + fmt::Debug,
73    F: Flavor<Index: fmt::Display>,
74{
75    print_with_lookup(o, tree, |_| None)
76}
77
78/// Pretty-print a tree with the source spans printed.
79///
80/// This prints like [`print`], except that the source reference of each
81/// element without children is the text of `source` covered by its span. If
82/// the span is out of range of `source` or does not fall on character
83/// boundaries, `+` is printed instead.
84///
85/// Elements are told apart by whether they have children, so a node without
86/// children is printed exactly like a token.
87///
88/// # Examples
89///
90/// ```
91/// #[derive(Debug, Clone, Copy)]
92/// enum Syntax {
93///     NUMBER,
94///     WHITESPACE,
95///     OPERATOR,
96///     PLUS,
97/// }
98///
99/// use Syntax::*;
100///
101/// let source = "128 + 64";
102///
103/// let tree = syntree::tree! {
104///     NUMBER => {
105///         (NUMBER, 3),
106///     },
107///     (WHITESPACE, 1),
108///     OPERATOR => {
109///         (PLUS, 1)
110///     },
111///     (WHITESPACE, 1),
112///     NUMBER => {
113///         (NUMBER, 2),
114///     },
115/// };
116///
117/// let mut s = Vec::new();
118/// syntree::print::print_with_source(&mut s, &tree, source)?;
119/// # let s = String::from_utf8(s)?;
120/// # assert_eq!(s, "NUMBER@0..3\n  NUMBER@0..3 \"128\"\nWHITESPACE@3..4 \" \"\nOPERATOR@4..5\n  PLUS@4..5 \"+\"\nWHITESPACE@5..6 \" \"\nNUMBER@6..8\n  NUMBER@6..8 \"64\"\n");
121/// # Ok::<_, Box<dyn core::error::Error>>(())
122/// ```
123///
124/// This would write:
125///
126/// ```text
127/// NUMBER@0..3
128///   NUMBER@0..3 "128"
129/// WHITESPACE@3..4 " "
130/// OPERATOR@4..5
131///   PLUS@4..5 "+"
132/// WHITESPACE@5..6 " "
133/// NUMBER@6..8
134///   NUMBER@6..8 "64"
135/// ```
136pub fn print_with_source<O, T, F>(o: O, tree: &Tree<T, F>, source: &str) -> Result<(), Error>
137where
138    O: Write,
139    T: Copy + fmt::Debug,
140    F: Flavor<Index: fmt::Display>,
141{
142    print_with_lookup(o, tree, |span| source.get(span.range()))
143}
144
145fn print_with_lookup<'a, O, T, F>(
146    mut o: O,
147    tree: &Tree<T, F>,
148    source: impl Fn(&Span<F::Index>) -> Option<&'a str>,
149) -> Result<(), Error>
150where
151    O: Write,
152    T: Copy + fmt::Debug,
153    F: Flavor<Index: fmt::Display>,
154{
155    for (depth, node) in tree.walk().with_depths() {
156        let n = (depth * 2) as usize;
157        let data = node.value();
158        let span = node.span();
159
160        if node.has_children() {
161            writeln!(o, "{:n$}{:?}@{}", "", data, span)?;
162        } else if let Some(source) = source(span) {
163            writeln!(o, "{:n$}{:?}@{} {:?}", "", data, span, source)?;
164        } else {
165            writeln!(o, "{:n$}{:?}@{} +", "", data, span)?;
166        }
167    }
168
169    Ok(())
170}