Skip to main content

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