perl_incremental_parsing/incremental/
incremental_simple.rs1use crate::{
7 ast::{Node, NodeKind},
8 edit::{Edit, EditSet},
9 error::ParseResult,
10 parser::Parser,
11 position::Range,
12};
13
14pub 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 self.reused_nodes = 0;
41 self.reparsed_nodes = 0;
42
43 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 !self.pending_edits.is_empty() {
58 if let Some(last_tree) = self.last_tree.as_ref() {
59 let structural_change = self.has_structural_change(last_tree);
61
62 if !structural_change {
63 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 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 for edit in self.pending_edits.edits() {
91 let range = Range::new(edit.start_position, edit.old_end_position);
92
93 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 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 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 let mut parser = Parser::new(source);
139 let new_tree = parser.parse()?;
140
141 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 if self.nodes_match(old_tree, new_tree) {
150 self.reused_nodes += 1;
151
152 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 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 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 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 let source2 = "my $x = 4242;";
270 let _ = must(parser.parse(source2));
271
272 assert!(parser.reused_nodes > 0);
274 println!("Reused: {}, Reparsed: {}", parser.reused_nodes, parser.reparsed_nodes);
275 }
276}