lex_core/lex/ast/
traits.rs

1//! AST traits - Common interfaces for uniform node access
2//!
3//! This module defines the common traits that provide uniform access
4//! to AST node information across all node types.
5
6use super::elements::ContentItem;
7use super::elements::VerbatimLine;
8use super::range::{Position, Range};
9use super::text_content::TextContent;
10
11/// Visitor trait for traversing the AST
12///
13/// Implement this trait to walk the AST. Each visit method corresponds to a node type.
14/// Default implementations are empty, so you only need to override the methods you care about.
15///
16/// # Example
17///
18/// ```ignore
19/// struct MyVisitor;
20///
21/// impl Visitor for MyVisitor {
22///     fn visit_paragraph(&mut self, para: &Paragraph) {
23///         println!("Found paragraph: {}", para.text());
24///     }
25/// }
26///
27/// let mut visitor = MyVisitor;
28/// document.accept(&mut visitor);
29/// ```
30pub trait Visitor {
31    // Container nodes with labels and children
32    fn visit_session(&mut self, _session: &super::Session) {}
33    fn leave_session(&mut self, _session: &super::Session) {}
34
35    fn visit_definition(&mut self, _definition: &super::Definition) {}
36    fn leave_definition(&mut self, _definition: &super::Definition) {}
37
38    fn visit_list(&mut self, _list: &super::List) {}
39    fn leave_list(&mut self, _list: &super::List) {}
40
41    fn visit_list_item(&mut self, _list_item: &super::ListItem) {}
42    fn leave_list_item(&mut self, _list_item: &super::ListItem) {}
43
44    // Leaf nodes (some contain lines)
45    fn visit_paragraph(&mut self, _paragraph: &super::Paragraph) {}
46    fn leave_paragraph(&mut self, _paragraph: &super::Paragraph) {}
47
48    fn visit_text_line(&mut self, _text_line: &super::elements::paragraph::TextLine) {}
49    fn leave_text_line(&mut self, _text_line: &super::elements::paragraph::TextLine) {}
50
51    fn visit_verbatim_block(&mut self, _verbatim_block: &super::Verbatim) {}
52    fn leave_verbatim_block(&mut self, _verbatim_block: &super::Verbatim) {}
53
54    fn visit_verbatim_group(&mut self, _group: &super::elements::verbatim::VerbatimGroupItemRef) {}
55    fn leave_verbatim_group(&mut self, _group: &super::elements::verbatim::VerbatimGroupItemRef) {}
56
57    fn visit_verbatim_line(&mut self, _verbatim_line: &VerbatimLine) {}
58    fn leave_verbatim_line(&mut self, _verbatim_line: &VerbatimLine) {}
59
60    fn visit_annotation(&mut self, _annotation: &super::Annotation) {}
61    fn leave_annotation(&mut self, _annotation: &super::Annotation) {}
62
63    fn visit_blank_line_group(
64        &mut self,
65        _blank_line_group: &super::elements::blank_line_group::BlankLineGroup,
66    ) {
67    }
68    fn leave_blank_line_group(
69        &mut self,
70        _blank_line_group: &super::elements::blank_line_group::BlankLineGroup,
71    ) {
72    }
73}
74
75/// Helper function to visit all children in a ContentItem slice
76pub fn visit_children(visitor: &mut dyn Visitor, items: &[ContentItem]) {
77    for item in items {
78        item.accept(visitor);
79    }
80}
81
82/// Common interface for all AST nodes
83pub trait AstNode {
84    fn node_type(&self) -> &'static str;
85    fn display_label(&self) -> String;
86    fn range(&self) -> &Range;
87    fn start_position(&self) -> Position {
88        self.range().start
89    }
90
91    /// Accept a visitor for traversing this node and its children
92    fn accept(&self, visitor: &mut dyn Visitor);
93}
94
95/// Trait for container nodes that have a label and children
96pub trait Container: AstNode {
97    fn label(&self) -> &str;
98    fn children(&self) -> &[ContentItem];
99    fn children_mut(&mut self) -> &mut Vec<ContentItem>;
100}
101
102/// Trait for leaf nodes that contain text
103pub trait TextNode: AstNode {
104    fn text(&self) -> String;
105    fn lines(&self) -> &[TextContent];
106}
107
108/// Trait describing visual/structural properties of nodes for line-oriented rendering
109///
110/// This trait captures whether a node has a direct visual representation in the source,
111/// whether it has a header line separate from its content, and whether it's a homogeneous
112/// container whose children can be visually collapsed with the parent.
113pub trait VisualStructure: AstNode {
114    /// Whether this node corresponds to a line in the source document
115    ///
116    /// Returns true for nodes like TextLine, ListItem, VerbatimLine, BlankLineGroup,
117    /// and header nodes like Session (title line), Definition (subject line).
118    fn is_source_line_node(&self) -> bool {
119        false
120    }
121
122    /// Whether this node has a visual header line separate from its content
123    ///
124    /// Returns true for Session (has title), Definition (has subject),
125    /// Annotation (has data line), VerbatimBlock (has subject line).
126    fn has_visual_header(&self) -> bool {
127        false
128    }
129
130    /// Whether this is a homogeneous container whose children can collapse with parent icon
131    ///
132    /// Returns true for Paragraph (contains only TextLines) and List (contains only ListItems).
133    /// These containers don't have their own visual line, so in line-oriented views,
134    /// we show the parent icon alongside the child's icon (¶ ↵ for Paragraph/TextLine).
135    fn collapses_with_children(&self) -> bool {
136        false
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::super::elements::{Paragraph, Session};
143    use super::*;
144
145    #[test]
146    fn test_visitor_traversal() {
147        // Create a simple structure: Session with a Paragraph
148        let para = Paragraph::from_line("Hello, World!".to_string());
149        let session = Session::with_title("Test Session".to_string());
150
151        // Create a visitor that counts nodes
152        struct CountingVisitor {
153            session_count: usize,
154            paragraph_count: usize,
155            text_line_count: usize,
156        }
157
158        impl Visitor for CountingVisitor {
159            fn visit_session(&mut self, _: &super::super::Session) {
160                self.session_count += 1;
161            }
162            fn visit_paragraph(&mut self, _: &super::super::Paragraph) {
163                self.paragraph_count += 1;
164            }
165            fn visit_text_line(&mut self, _: &super::super::elements::paragraph::TextLine) {
166                self.text_line_count += 1;
167            }
168        }
169
170        let mut visitor = CountingVisitor {
171            session_count: 0,
172            paragraph_count: 0,
173            text_line_count: 0,
174        };
175
176        // Visit the paragraph
177        para.accept(&mut visitor);
178        assert_eq!(visitor.paragraph_count, 1);
179        assert_eq!(visitor.text_line_count, 1); // Paragraph contains one TextLine
180        assert_eq!(visitor.session_count, 0);
181
182        // Reset and visit the session
183        visitor.session_count = 0;
184        visitor.paragraph_count = 0;
185        visitor.text_line_count = 0;
186        session.accept(&mut visitor);
187        assert_eq!(visitor.session_count, 1);
188        assert_eq!(visitor.paragraph_count, 0); // Session has no children yet
189    }
190}