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}