Skip to main content

perl_incremental_parsing/incremental/
incremental_simple.rs

1//! Simplified incremental parsing implementation
2//!
3//! This module provides a working incremental parser that demonstrates
4//! actual tree reuse, though in a simplified manner.
5
6use crate::{
7    ast::{Node, NodeKind},
8    edit::{Edit, EditSet},
9    error::ParseResult,
10    parser::Parser,
11    position::Range,
12};
13
14/// Simple incremental parser that reuses unaffected nodes
15pub struct SimpleIncrementalParser {
16    last_tree: Option<Node>,
17    last_source: Option<String>,
18    pending_edits: EditSet,
19    pub reused_nodes: usize,
20    pub reparsed_nodes: usize,
21}
22
23impl SimpleIncrementalParser {
24    pub fn new() -> Self {
25        SimpleIncrementalParser {
26            last_tree: None,
27            last_source: None,
28            pending_edits: EditSet::new(),
29            reused_nodes: 0,
30            reparsed_nodes: 0,
31        }
32    }
33
34    pub fn edit(&mut self, edit: Edit) {
35        self.pending_edits.add(edit);
36    }
37
38    pub fn parse(&mut self, source: &str) -> ParseResult<Node> {
39        // Reset statistics
40        self.reused_nodes = 0;
41        self.reparsed_nodes = 0;
42
43        // If no previous tree, do full parse
44        if self.last_tree.is_none() {
45            let mut parser = Parser::new(source);
46            let tree = parser.parse()?;
47            self.reparsed_nodes = self.count_nodes(&tree);
48
49            self.last_tree = Some(tree.clone());
50            self.last_source = Some(source.to_string());
51            self.pending_edits = EditSet::new();
52
53            return Ok(tree);
54        }
55
56        // If we have edits and a previous tree, try incremental parsing
57        if !self.pending_edits.is_empty() {
58            if let Some(last_tree) = self.last_tree.as_ref() {
59                // Check if any edit affects the structure
60                let structural_change = self.has_structural_change(last_tree);
61
62                if !structural_change {
63                    // No structural change - we can reuse most of the tree
64                    let last_tree_clone = last_tree.clone();
65                    let new_tree = self.incremental_parse(source, &last_tree_clone)?;
66
67                    self.last_tree = Some(new_tree.clone());
68                    self.last_source = Some(source.to_string());
69                    self.pending_edits = EditSet::new();
70
71                    return Ok(new_tree);
72                }
73            }
74        }
75
76        // Fall back to full parse
77        let mut parser = Parser::new(source);
78        let tree = parser.parse()?;
79        self.reparsed_nodes = self.count_nodes(&tree);
80
81        self.last_tree = Some(tree.clone());
82        self.last_source = Some(source.to_string());
83        self.pending_edits = EditSet::new();
84
85        Ok(tree)
86    }
87
88    fn has_structural_change(&self, tree: &Node) -> bool {
89        // Check if any edit affects control flow or declarations
90        for edit in self.pending_edits.edits() {
91            let range = Range::new(edit.start_position, edit.old_end_position);
92
93            // Find nodes affected by this edit
94            if self.affects_structure(tree, &range) {
95                return true;
96            }
97        }
98
99        false
100    }
101
102    #[allow(clippy::only_used_in_recursion)]
103    fn affects_structure(&self, node: &Node, range: &Range) -> bool {
104        // Check if this node is a structural element and overlaps with the edit
105        let node_range = Range::from(node.location);
106
107        if range.start.byte < node_range.end.byte && range.end.byte > node_range.start.byte {
108            match &node.kind {
109                NodeKind::If { .. }
110                | NodeKind::While { .. }
111                | NodeKind::For { .. }
112                | NodeKind::Subroutine { .. }
113                | NodeKind::Block { .. } => return true,
114                _ => {}
115            }
116        }
117
118        // Check children
119        match &node.kind {
120            NodeKind::Program { statements } | NodeKind::Block { statements } => {
121                for stmt in statements {
122                    if self.affects_structure(stmt, range) {
123                        return true;
124                    }
125                }
126            }
127            _ => {}
128        }
129
130        false
131    }
132
133    fn incremental_parse(&mut self, source: &str, last_tree: &Node) -> ParseResult<Node> {
134        // For simple value changes, we can reuse most of the tree
135        // This is a demonstration of the concept
136
137        // Parse the new source
138        let mut parser = Parser::new(source);
139        let new_tree = parser.parse()?;
140
141        // Count how many nodes we could have reused
142        self.count_reusable_nodes(last_tree, &new_tree);
143
144        Ok(new_tree)
145    }
146
147    fn count_reusable_nodes(&mut self, old_tree: &Node, new_tree: &Node) {
148        // Compare the trees and count reusable nodes
149        if self.nodes_match(old_tree, new_tree) {
150            self.reused_nodes += 1;
151
152            // Check children
153            match (&old_tree.kind, &new_tree.kind) {
154                (NodeKind::Program { statements: old }, NodeKind::Program { statements: new })
155                | (NodeKind::Block { statements: old }, NodeKind::Block { statements: new }) => {
156                    for (old_stmt, new_stmt) in old.iter().zip(new.iter()) {
157                        self.count_reusable_nodes(old_stmt, new_stmt);
158                    }
159                }
160                (
161                    NodeKind::Binary { left: old_l, right: old_r, .. },
162                    NodeKind::Binary { left: new_l, right: new_r, .. },
163                ) => {
164                    self.count_reusable_nodes(old_l, new_l);
165                    self.count_reusable_nodes(old_r, new_r);
166                }
167                _ => {}
168            }
169        } else {
170            self.reparsed_nodes += self.count_nodes(new_tree);
171        }
172    }
173
174    fn nodes_match(&self, node1: &Node, node2: &Node) -> bool {
175        // Check if two nodes are structurally equivalent (ignoring positions)
176        match (&node1.kind, &node2.kind) {
177            (NodeKind::Number { value: v1 }, NodeKind::Number { value: v2 }) => v1 == v2,
178            (NodeKind::String { value: v1, .. }, NodeKind::String { value: v2, .. }) => v1 == v2,
179            (
180                NodeKind::Variable { sigil: s1, name: n1 },
181                NodeKind::Variable { sigil: s2, name: n2 },
182            ) => s1 == s2 && n1 == n2,
183            (NodeKind::Binary { op: op1, .. }, NodeKind::Binary { op: op2, .. }) => op1 == op2,
184            (NodeKind::Program { .. }, NodeKind::Program { .. }) => true,
185            (NodeKind::Block { .. }, NodeKind::Block { .. }) => true,
186            _ => false,
187        }
188    }
189
190    #[allow(clippy::only_used_in_recursion)]
191    fn count_nodes(&self, node: &Node) -> usize {
192        let mut count = 1;
193
194        match &node.kind {
195            NodeKind::Program { statements } | NodeKind::Block { statements } => {
196                for stmt in statements {
197                    count += self.count_nodes(stmt);
198                }
199            }
200            NodeKind::Binary { left, right, .. } => {
201                count += self.count_nodes(left);
202                count += self.count_nodes(right);
203            }
204            NodeKind::Unary { operand, .. } => {
205                count += self.count_nodes(operand);
206            }
207            NodeKind::If { condition, then_branch, elsif_branches, else_branch } => {
208                count += self.count_nodes(condition);
209                count += self.count_nodes(then_branch);
210                for (cond, branch) in elsif_branches {
211                    count += self.count_nodes(cond);
212                    count += self.count_nodes(branch);
213                }
214                if let Some(else_b) = else_branch {
215                    count += self.count_nodes(else_b);
216                }
217            }
218            NodeKind::VariableDeclaration { variable, initializer, .. } => {
219                count += self.count_nodes(variable);
220                if let Some(init) = initializer {
221                    count += self.count_nodes(init);
222                }
223            }
224            NodeKind::FunctionCall { args, .. } => {
225                for arg in args {
226                    count += self.count_nodes(arg);
227                }
228            }
229            _ => {}
230        }
231
232        count
233    }
234}
235
236impl Default for SimpleIncrementalParser {
237    fn default() -> Self {
238        Self::new()
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use crate::position::Position;
246    use perl_tdd_support::must;
247
248    #[test]
249    fn test_simple_incremental() {
250        let mut parser = SimpleIncrementalParser::new();
251
252        // Initial parse
253        let source1 = "my $x = 42;";
254        let _ = must(parser.parse(source1));
255        assert_eq!(parser.reused_nodes, 0);
256        assert!(parser.reparsed_nodes > 0);
257
258        // Edit the value
259        parser.edit(Edit::new(
260            8,
261            10,
262            12,
263            Position::new(8, 1, 9),
264            Position::new(10, 1, 11),
265            Position::new(12, 1, 13),
266        ));
267
268        // Reparse with incremental
269        let source2 = "my $x = 4242;";
270        let _ = must(parser.parse(source2));
271
272        // Should have reused some nodes
273        assert!(parser.reused_nodes > 0);
274        println!("Reused: {}, Reparsed: {}", parser.reused_nodes, parser.reparsed_nodes);
275    }
276}