markdown_ppp/ast_transform/
visitor.rs

1//! Visitor pattern for read-only AST traversal
2//!
3//! This module provides the Visitor trait for read-only traversal of AST nodes.
4//! Visitors are useful for collecting information, counting elements, or performing
5//! analysis without modifying the AST structure.
6//!
7//! # Example
8//!
9//! ```rust
10//! use markdown_ppp::ast::*;
11//! use markdown_ppp::ast_transform::{Visitor, VisitWith};
12//!
13//! struct TextCollector {
14//!     texts: Vec<String>,
15//! }
16//!
17//! impl Visitor for TextCollector {
18//!     fn visit_inline(&mut self, inline: &Inline) {
19//!         if let Inline::Text(text) = inline {
20//!             self.texts.push(text.clone());
21//!         }
22//!         self.walk_inline(inline);
23//!     }
24//! }
25//!
26//! let doc = Document {
27//!     blocks: vec![Block::Paragraph(vec![Inline::Text("hello".to_string())])],
28//! };
29//!
30//! let mut collector = TextCollector { texts: Vec::new() };
31//! doc.visit_with(&mut collector);
32//! assert_eq!(collector.texts, vec!["hello"]);
33//! ```
34
35use crate::ast::*;
36
37/// Visitor trait for traversing AST nodes without modification
38///
39/// Provides default implementations that recursively visit child nodes.
40/// Override specific methods to implement custom logic for different node types.
41///
42/// # Example
43///
44/// ```rust
45/// use markdown_ppp::ast::*;
46/// use markdown_ppp::ast_transform::Visitor;
47///
48/// struct TextCollector {
49///     texts: Vec<String>,
50/// }
51///
52/// impl Visitor for TextCollector {
53///     fn visit_inline(&mut self, inline: &Inline) {
54///         if let Inline::Text(text) = inline {
55///             self.texts.push(text.clone());
56///         }
57///         // Continue with default traversal
58///         self.walk_inline(inline);
59///     }
60/// }
61/// ```
62pub trait Visitor {
63    /// Visit a document node
64    fn visit_document(&mut self, doc: &Document) {
65        self.walk_document(doc);
66    }
67
68    /// Visit a block node
69    fn visit_block(&mut self, block: &Block) {
70        self.walk_block(block);
71    }
72
73    /// Visit an inline node
74    fn visit_inline(&mut self, inline: &Inline) {
75        self.walk_inline(inline);
76    }
77
78    /// Default traversal for document
79    fn walk_document(&mut self, doc: &Document) {
80        for block in &doc.blocks {
81            self.visit_block(block);
82        }
83    }
84
85    /// Default traversal for block nodes
86    fn walk_block(&mut self, block: &Block) {
87        match block {
88            Block::Paragraph(inlines) => {
89                for inline in inlines {
90                    self.visit_inline(inline);
91                }
92            }
93            Block::Heading(heading) => {
94                for inline in &heading.content {
95                    self.visit_inline(inline);
96                }
97            }
98            Block::BlockQuote(blocks) => {
99                for block in blocks {
100                    self.visit_block(block);
101                }
102            }
103            Block::List(list) => {
104                for item in &list.items {
105                    for block in &item.blocks {
106                        self.visit_block(block);
107                    }
108                }
109            }
110            Block::Table(table) => {
111                for row in &table.rows {
112                    for cell in row {
113                        for inline in cell {
114                            self.visit_inline(inline);
115                        }
116                    }
117                }
118            }
119            Block::FootnoteDefinition(footnote) => {
120                for block in &footnote.blocks {
121                    self.visit_block(block);
122                }
123            }
124            Block::GitHubAlert(alert) => {
125                for block in &alert.blocks {
126                    self.visit_block(block);
127                }
128            }
129            Block::Definition(def) => {
130                for inline in &def.label {
131                    self.visit_inline(inline);
132                }
133            }
134            // Terminal nodes - no traversal needed
135            Block::ThematicBreak | Block::CodeBlock(_) | Block::HtmlBlock(_) | Block::Empty => {}
136        }
137    }
138
139    /// Default traversal for inline nodes
140    fn walk_inline(&mut self, inline: &Inline) {
141        match inline {
142            Inline::Emphasis(inlines)
143            | Inline::Strong(inlines)
144            | Inline::Strikethrough(inlines) => {
145                for inline in inlines {
146                    self.visit_inline(inline);
147                }
148            }
149            Inline::Link(link) => {
150                for inline in &link.children {
151                    self.visit_inline(inline);
152                }
153            }
154            Inline::LinkReference(link_ref) => {
155                for inline in &link_ref.label {
156                    self.visit_inline(inline);
157                }
158                for inline in &link_ref.text {
159                    self.visit_inline(inline);
160                }
161            }
162            // Terminal nodes - no traversal needed
163            Inline::Text(_)
164            | Inline::LineBreak
165            | Inline::Code(_)
166            | Inline::Html(_)
167            | Inline::Image(_)
168            | Inline::Autolink(_)
169            | Inline::FootnoteReference(_)
170            | Inline::Empty => {}
171        }
172    }
173}
174
175/// Extension trait for visiting documents
176pub trait VisitWith {
177    /// Apply a visitor to this AST node
178    fn visit_with<V: Visitor>(&self, visitor: &mut V);
179}
180
181impl VisitWith for Document {
182    fn visit_with<V: Visitor>(&self, visitor: &mut V) {
183        visitor.visit_document(self);
184    }
185}
186
187impl VisitWith for Block {
188    fn visit_with<V: Visitor>(&self, visitor: &mut V) {
189        visitor.visit_block(self);
190    }
191}
192
193impl VisitWith for Inline {
194    fn visit_with<V: Visitor>(&self, visitor: &mut V) {
195        visitor.visit_inline(self);
196    }
197}