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    /// Visit a table cell
79    fn visit_table_cell(&mut self, cell: &TableCell) {
80        self.walk_table_cell(cell);
81    }
82
83    /// Visit a list item
84    fn visit_list_item(&mut self, item: &ListItem) {
85        self.walk_list_item(item);
86    }
87
88    /// Visit a table row
89    fn visit_table_row(&mut self, row: &TableRow) {
90        self.walk_table_row(row);
91    }
92
93    /// Visit a heading
94    fn visit_heading(&mut self, heading: &Heading) {
95        self.walk_heading(heading);
96    }
97
98    /// Visit a link
99    fn visit_link(&mut self, link: &Link) {
100        self.walk_link(link);
101    }
102
103    /// Visit an image
104    fn visit_image(&mut self, image: &Image) {
105        self.walk_image(image);
106    }
107
108    /// Visit a code block
109    fn visit_code_block(&mut self, code_block: &CodeBlock) {
110        self.walk_code_block(code_block);
111    }
112
113    /// Visit text content
114    fn visit_text(&mut self, text: &str) {
115        self.walk_text(text);
116    }
117
118    /// Visit a footnote definition
119    fn visit_footnote_definition(&mut self, footnote: &FootnoteDefinition) {
120        self.walk_footnote_definition(footnote);
121    }
122
123    /// Visit a GitHub alert
124    fn visit_github_alert(&mut self, alert: &GitHubAlert) {
125        self.walk_github_alert(alert);
126    }
127
128    /// Default traversal for document
129    fn walk_document(&mut self, doc: &Document) {
130        for block in &doc.blocks {
131            self.visit_block(block);
132        }
133    }
134
135    /// Default traversal for block nodes
136    fn walk_block(&mut self, block: &Block) {
137        match block {
138            Block::Paragraph(inlines) => {
139                for inline in inlines {
140                    self.visit_inline(inline);
141                }
142            }
143            Block::Heading(heading) => {
144                self.visit_heading(heading);
145            }
146            Block::BlockQuote(blocks) => {
147                for block in blocks {
148                    self.visit_block(block);
149                }
150            }
151            Block::List(list) => {
152                for item in &list.items {
153                    self.visit_list_item(item);
154                }
155            }
156            Block::Table(table) => {
157                for row in &table.rows {
158                    self.visit_table_row(row);
159                }
160            }
161            Block::FootnoteDefinition(footnote) => {
162                self.visit_footnote_definition(footnote);
163            }
164            Block::GitHubAlert(alert) => {
165                self.visit_github_alert(alert);
166            }
167            Block::Definition(def) => {
168                for inline in &def.label {
169                    self.visit_inline(inline);
170                }
171            }
172            Block::CodeBlock(code_block) => {
173                self.visit_code_block(code_block);
174            }
175            // Terminal nodes - no traversal needed
176            Block::ThematicBreak | Block::HtmlBlock(_) | Block::Empty => {}
177        }
178    }
179
180    /// Default traversal for inline nodes
181    fn walk_inline(&mut self, inline: &Inline) {
182        match inline {
183            Inline::Emphasis(inlines)
184            | Inline::Strong(inlines)
185            | Inline::Strikethrough(inlines) => {
186                for inline in inlines {
187                    self.visit_inline(inline);
188                }
189            }
190            Inline::Link(link) => {
191                self.visit_link(link);
192            }
193            Inline::LinkReference(link_ref) => {
194                for inline in &link_ref.label {
195                    self.visit_inline(inline);
196                }
197                for inline in &link_ref.text {
198                    self.visit_inline(inline);
199                }
200            }
201            Inline::Image(image) => {
202                self.visit_image(image);
203            }
204            Inline::Text(text) => {
205                self.visit_text(text);
206            }
207            // Terminal nodes - no traversal needed
208            Inline::LineBreak
209            | Inline::Code(_)
210            | Inline::Html(_)
211            | Inline::Autolink(_)
212            | Inline::FootnoteReference(_)
213            | Inline::Empty => {}
214        }
215    }
216
217    /// Default traversal for table cells
218    fn walk_table_cell(&mut self, cell: &TableCell) {
219        for inline in cell {
220            self.visit_inline(inline);
221        }
222    }
223
224    /// Default traversal for list items
225    fn walk_list_item(&mut self, item: &ListItem) {
226        for block in &item.blocks {
227            self.visit_block(block);
228        }
229    }
230
231    /// Default traversal for table rows
232    fn walk_table_row(&mut self, row: &TableRow) {
233        for cell in row {
234            self.visit_table_cell(cell);
235        }
236    }
237
238    /// Default traversal for headings
239    fn walk_heading(&mut self, heading: &Heading) {
240        for inline in &heading.content {
241            self.visit_inline(inline);
242        }
243    }
244
245    /// Default traversal for links
246    fn walk_link(&mut self, link: &Link) {
247        for inline in &link.children {
248            self.visit_inline(inline);
249        }
250    }
251
252    /// Default traversal for images
253    fn walk_image(&mut self, _image: &Image) {
254        // Images are terminal nodes with no child inlines to traverse
255    }
256
257    /// Default traversal for code blocks
258    fn walk_code_block(&mut self, _code_block: &CodeBlock) {
259        // Code blocks are terminal nodes
260    }
261
262    /// Default traversal for text
263    fn walk_text(&mut self, _text: &str) {
264        // Text is a terminal node
265    }
266
267    /// Default traversal for footnote definitions
268    fn walk_footnote_definition(&mut self, footnote: &FootnoteDefinition) {
269        for block in &footnote.blocks {
270            self.visit_block(block);
271        }
272    }
273
274    /// Default traversal for GitHub alerts
275    fn walk_github_alert(&mut self, alert: &GitHubAlert) {
276        for block in &alert.blocks {
277            self.visit_block(block);
278        }
279    }
280}
281
282/// Extension trait for visiting documents
283pub trait VisitWith {
284    /// Apply a visitor to this AST node
285    fn visit_with<V: Visitor>(&self, visitor: &mut V);
286}
287
288impl VisitWith for Document {
289    fn visit_with<V: Visitor>(&self, visitor: &mut V) {
290        visitor.visit_document(self);
291    }
292}
293
294impl VisitWith for Block {
295    fn visit_with<V: Visitor>(&self, visitor: &mut V) {
296        visitor.visit_block(self);
297    }
298}
299
300impl VisitWith for Inline {
301    fn visit_with<V: Visitor>(&self, visitor: &mut V) {
302        visitor.visit_inline(self);
303    }
304}