ast2str_lib/
lib.rs

1#![cfg_attr(feature = "allocator_api", feature(allocator_api,))]
2use std::collections::VecDeque;
3
4pub mod builder;
5pub mod utils;
6
7pub use builder::TreeBuilder;
8
9/// A trait for printing ASTs in a pretty manner.
10pub trait AstToStr {
11    /// This method is auto-implemented to call [`ast_to_str_impl`] with [`DefaultSymbols`].
12    ///
13    /// [`ast_to_str_impl`]: #tymethod.ast_to_str_impl
14    fn ast_to_str(&self) -> String {
15        self.ast_to_str_impl(&DefaultSymbols)
16    }
17
18    /// This method should serialize the struct or enum recursively, returning a tree.
19    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String;
20}
21
22/// A trait for supplying symbols to the AST formatting functions.
23pub trait Symbols {
24    /// The horizontal bar symbol used to draw horizontal tree branches, e.g. `─`.
25    fn horizontal_bar(&self) -> &'static str;
26
27    /// The vertical bar symbol used to draw the tree trunks of the tree and its subtrees, e.g. `│`.
28    fn vertical_bar(&self) -> &'static str;
29
30    /// A piece of trunk with a branch to the right, e.g. `├`.
31    fn right_branch(&self) -> &'static str;
32
33    /// A single indentation symbol, e.g. ` `.
34    fn indent(&self) -> &'static str;
35
36    /// The symbol for left upper corners, e.g. `╭`.
37    fn left_upper_corner(&self) -> &'static str;
38
39    /// The symbol for left bottom corners, e.g. `╰`.
40    fn left_bottom_corner(&self) -> &'static str;
41
42    /// The symbol for right upper corners, e.g. `╮`.
43    fn right_upper_corner(&self) -> &'static str;
44
45    /// The symbol for right bottom corners, e.g. `╯`.
46    fn right_bottom_corner(&self) -> &'static str;
47
48    /// The symbol to display when a list of items is empty, e.g. `✕`.
49    fn missing_items_symbol(&self) -> &'static str;
50
51    /// The symbol to display when a list of items is non-empty, e.g. `↓`.
52    fn item_list_symbol(&self) -> &'static str;
53
54    /// Used by the debug impl for `&dyn Symbols`.
55    fn description(&self) -> &'static str {
56        "dyn Symbols"
57    }
58}
59impl<'s> std::fmt::Debug for &'s dyn Symbols {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(f, "{}", self.description())
62    }
63}
64
65/// A macro for quick-and-dirty creation of `Symbols` implementations.
66/// Accepts either 10 different strings to use as symbols in the order they appear in the trait, or a single value to be used inside all methods.
67#[macro_export]
68macro_rules! create_symbols {
69    ($Ty:ident,
70        $horizontal_bar:expr,
71        $vertical_bar:expr,
72        $right_branch:expr,
73        $indent:expr,
74        $left_upper_corner:expr,
75        $left_bottom_corner:expr,
76        $right_upper_corner:expr,
77        $right_bottom_corner:expr,
78        $missing_items_symbol:expr,
79        $item_list_symbol:expr
80    ) => {
81        impl Symbols for $Ty {
82            fn description(&self) -> &'static str {
83                stringify!($Ty)
84            }
85            fn horizontal_bar(&self) -> &'static str {
86                $horizontal_bar
87            }
88            fn vertical_bar(&self) -> &'static str {
89                $vertical_bar
90            }
91            fn right_branch(&self) -> &'static str {
92                $right_branch
93            }
94            fn indent(&self) -> &'static str {
95                $indent
96            }
97            fn left_upper_corner(&self) -> &'static str {
98                $left_upper_corner
99            }
100            fn left_bottom_corner(&self) -> &'static str {
101                $left_bottom_corner
102            }
103            fn right_upper_corner(&self) -> &'static str {
104                $right_upper_corner
105            }
106            fn right_bottom_corner(&self) -> &'static str {
107                $right_bottom_corner
108            }
109            fn missing_items_symbol(&self) -> &'static str {
110                $missing_items_symbol
111            }
112            fn item_list_symbol(&self) -> &'static str {
113                $item_list_symbol
114            }
115        }
116    };
117    ($Ty:ident, $sym:tt) => {
118        $crate::create_symbols!($Ty, $sym, $sym, $sym, $sym, $sym, $sym, $sym, $sym, $sym, $sym);
119    };
120}
121
122/// The default set of symbols that produces neatly-drawn trees.
123///
124/// # Example
125/// ```bash
126/// StmtKind::Var
127/// ╰─declarations=↓
128///   ╰─Var
129///     ├─name: "x"
130///     ├─kind: Let
131///     ├─initializer: ExprKind::Binary
132///     │ ├─op: Add
133///     │ ├─left: Lit
134///     │ │ ├─token: "1"
135///     │ │ ╰─value: LitValue::Integer(1)
136///     │ ╰─right: Lit
137///     │   ├─token: "2.3"
138///     │   ╰─value: LitValue::Float(2.3)
139///     ╰─n_uses: 0
140/// StmtKind::Print
141/// ╰─value: ExprKind::Comma
142///   ╰─operands=↓
143///     ├─Lit
144///     │ ├─token: "\"hello, world!\""
145///     │ ╰─value: LitValue::Str("hello, world!")
146///     ╰─ExprKind::Variable
147///       ╰─name: "x"
148/// ```
149pub struct DefaultSymbols;
150
151create_symbols!(
152    DefaultSymbols,
153    symbols::HORIZONTAL_BAR,
154    symbols::VERTICAL_BAR,
155    symbols::BRANCH,
156    symbols::INDENT,
157    symbols::LEFT_UPPER_CORNER,
158    symbols::LEFT_BOTTOM_CORNER,
159    symbols::RIGHT_UPPER_CORNER,
160    symbols::RIGHT_BOTTOM_CORNER,
161    symbols::CROSS,
162    symbols::DOWNWARDS_POINTING_ARROW
163);
164
165/// A set of symbols where every symbol is either whitespace (` `) or an empty string.
166///
167/// # Example
168/// ```bash
169/// StmtKind::Var
170///   declarations=
171///     Var
172///       name: "x"
173///       kind: Let
174///       initializer: ExprKind::Binary
175///         op: Add
176///         left: Lit
177///           token: "1"
178///           value: LitValue::Integer(1)
179///         right: Lit
180///           token: "2.3"
181///           value: LitValue::Float(2.3)
182///       n_uses: 0
183/// StmtKind::Print
184///   value: ExprKind::Comma
185///     operands=
186///       Lit
187///         token: "\"hello, world!\""
188///         value: LitValue::Str("hello, world!")
189///       ExprKind::Variable
190///         name: "x"
191/// ```
192pub struct TestSymbols;
193create_symbols!(TestSymbols, " ", " ", " ", " ", " ", " ", " ", " ", "", "");
194
195/// The symbols used by [`DefaultSymbols]`.
196pub mod symbols {
197    pub static HORIZONTAL_BAR: &str = "─";
198    pub static VERTICAL_BAR: &str = "│";
199    pub static BRANCH: &str = "├";
200    pub static INDENT: &str = " ";
201    pub static LEFT_UPPER_CORNER: &str = "╭";
202    pub static LEFT_BOTTOM_CORNER: &str = "╰";
203    pub static RIGHT_UPPER_CORNER: &str = "╮";
204    pub static RIGHT_BOTTOM_CORNER: &str = "╯";
205    pub static CROSS: &str = "✕";
206    pub static DOWNWARDS_POINTING_ARROW: &str = "↓";
207}
208
209macro_rules! impl_ast {
210    (debug $T:ty) => {
211        impl<'a> AstToStr for $T {
212            fn ast_to_str_impl(&self, _: &dyn Symbols) -> String {
213                format!("{:?}", self)
214            }
215        }
216    };
217    (display $T:ty) => {
218        impl<'a> AstToStr for $T {
219            fn ast_to_str_impl(&self, _: &dyn Symbols) -> String {
220                self.to_string()
221            }
222        }
223    };
224    (ptr $Ptr:ty) => {
225        impl<T: AstToStr> AstToStr for $Ptr {
226            fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
227                (**self).ast_to_str_impl(s)
228            }
229        }
230    };
231    (debug $($T:ty),*) => {
232        $(
233            impl_ast!(debug $T);
234        )*
235    };
236    (display $($T:ty),*) => {
237        $(
238            impl_ast!(display $T);
239        )*
240    };
241    (ptr $($T:ty),*) => {
242        $(
243            impl_ast!(ptr $T);
244        )*
245    };
246}
247
248impl_ast!(debug str, &'a str, String, std::borrow::Cow<'a, str>, ());
249impl_ast!(display i8, i16, i32, i64, i128);
250impl_ast!(display u8, u16, u32, u64, u128);
251impl_ast!(display f32, f64);
252impl_ast!(display isize, usize);
253impl_ast!(display bool);
254impl_ast!(ptr std::rc::Rc<T>, std::sync::Arc<T>);
255
256#[cfg(not(feature = "allocator_api"))]
257impl<T: AstToStr> AstToStr for Box<T> {
258    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
259        (**self).ast_to_str_impl(s)
260    }
261}
262
263#[cfg(feature = "allocator_api")]
264impl<T: AstToStr, A: core::alloc::Allocator> AstToStr for Box<T, A> {
265    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
266        (**self).ast_to_str_impl(s)
267    }
268}
269
270#[cfg(not(feature = "allocator_api"))]
271impl<T: AstToStr> AstToStr for Vec<T> {
272    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
273        crate::builder::print_ast_list_without_node_name(self, |e| e.ast_to_str_impl(s), s)
274    }
275}
276
277#[cfg(feature = "allocator_api")]
278impl<T: AstToStr, A: core::alloc::Allocator> AstToStr for Vec<T, A> {
279    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
280        crate::builder::print_ast_list_without_node_name(self, |e| e.ast_to_str_impl(s), s)
281    }
282}
283
284#[cfg(not(feature = "allocator_api"))]
285impl<T: AstToStr> AstToStr for VecDeque<T> {
286    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
287        crate::builder::print_ast_list_without_node_name(self, |e| e.ast_to_str_impl(s), s)
288    }
289}
290
291#[cfg(feature = "allocator_api")]
292impl<T: AstToStr, A: core::alloc::Allocator> AstToStr for VecDeque<T, A> {
293    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
294        crate::builder::print_ast_list_without_node_name(self, |e| e.ast_to_str_impl(s), s)
295    }
296}
297
298impl<T: AstToStr> AstToStr for Option<T> {
299    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
300        if let Some(v) = self {
301            v.ast_to_str_impl(s)
302        } else {
303            "None".to_owned()
304        }
305    }
306}
307
308impl<V: AstToStr> AstToStr for std::cell::RefCell<V> {
309    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
310        self.borrow().ast_to_str_impl(s)
311    }
312}
313
314impl<V: Copy + AstToStr> AstToStr for std::cell::Cell<V> {
315    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
316        self.get().ast_to_str_impl(s)
317    }
318}
319
320impl<K: AstToStr, V: AstToStr, S> AstToStr for std::collections::HashMap<K, V, S> {
321    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
322        crate::builder::print_ast_list_without_node_name(
323            self.iter().enumerate(),
324            |(i, (k, v))| {
325                crate::builder::TreeBuilder::new(&format!("entry: {}", i), s)
326                    .field("key", k)
327                    .field("value", v)
328                    .build()
329            },
330            s,
331        )
332    }
333}
334
335impl<K: AstToStr, S> AstToStr for std::collections::HashSet<K, S> {
336    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
337        crate::builder::print_ast_list_without_node_name(self, |e| e.ast_to_str_impl(s), s)
338    }
339}
340
341#[cfg(all(feature = "impl_hashbrown", not(feature = "allocator_api")))]
342impl<K: AstToStr, V: AstToStr, S> AstToStr for hashbrown::HashMap<K, V, S> {
343    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
344        crate::builder::print_ast_list_without_node_name(
345            self.iter().enumerate(),
346            |(i, (k, v))| {
347                crate::builder::TreeBuilder::new(&format!("entry: {}", i), s)
348                    .field("key", k)
349                    .field("value", v)
350                    .build()
351            },
352            s,
353        )
354    }
355}
356
357#[cfg(all(feature = "impl_hashbrown", feature = "allocator_api"))]
358impl<K: AstToStr, V: AstToStr, S, A: core::alloc::Allocator + Clone> AstToStr
359    for hashbrown::HashMap<K, V, S, A>
360{
361    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
362        crate::builder::print_ast_list_without_node_name(
363            self.iter().enumerate(),
364            |(i, (k, v))| {
365                crate::builder::TreeBuilder::new(&format!("entry: {}", i), s)
366                    .field("key", k)
367                    .field("value", v)
368                    .build()
369            },
370            s,
371        )
372    }
373}
374
375#[cfg(all(feature = "impl_hashbrown", not(feature = "allocator_api")))]
376impl<K: AstToStr, S> AstToStr for hashbrown::HashSet<K, S> {
377    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
378        crate::builder::print_ast_list_without_node_name(self, |e| e.ast_to_str_impl(s), s)
379    }
380}
381
382#[cfg(all(feature = "impl_hashbrown", feature = "allocator_api"))]
383impl<K: AstToStr, S, A: core::alloc::Allocator + Clone> AstToStr for hashbrown::HashSet<K, S, A> {
384    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
385        crate::builder::print_ast_list_without_node_name(self, |e| e.ast_to_str_impl(s), s)
386    }
387}
388
389impl<A: AstToStr, B: AstToStr> AstToStr for (A, B) {
390    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
391        crate::builder::TreeBuilder::new("tuple", s)
392            .field("field0", &self.0)
393            .field("field1", &self.1)
394            .build()
395    }
396}
397
398impl<A: AstToStr, B: AstToStr, C: AstToStr> AstToStr for (A, B, C) {
399    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
400        crate::builder::TreeBuilder::new("tuple", s)
401            .field("field0", &self.0)
402            .field("field1", &self.1)
403            .field("field2", &self.2)
404            .build()
405    }
406}
407
408impl<A: AstToStr, B: AstToStr, C: AstToStr, D: AstToStr> AstToStr for (A, B, C, D) {
409    fn ast_to_str_impl(&self, s: &dyn Symbols) -> String {
410        crate::builder::TreeBuilder::new("tuple", s)
411            .field("field0", &self.0)
412            .field("field1", &self.1)
413            .field("field2", &self.2)
414            .field("field3", &self.3)
415            .build()
416    }
417}
418
419impl<T: std::fmt::Debug> AstToStr for std::ops::Range<T> {
420    fn ast_to_str_impl(&self, _: &dyn Symbols) -> String {
421        format!("{:?}", self)
422    }
423}
424
425impl std::fmt::Display for dyn AstToStr {
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        write!(f, "{}", self.ast_to_str())
428    }
429}