Skip to main content

perl_parser/incremental/
incremental_v2.rs

1//! Incremental parsing implementation with comprehensive tree reuse
2//!
3//! This module provides a high-performance incremental parser that achieves significant
4//! performance improvements over full parsing through intelligent AST node reuse.
5//! Designed for integration with LSP servers and real-time editing scenarios.
6//!
7//! ## Performance Characteristics
8//!
9//! - **Sub-millisecond updates** for simple value edits (target: <1ms)
10//! - **Node reuse efficiency** of 70-90% for typical editing scenarios
11//! - **Graceful fallback** to full parsing for complex structural changes
12//! - **Memory efficient** with LRU cache eviction and `Arc<Node>` sharing
13//! - **Time complexity**: O(n) for reparsed spans with bounded lookahead
14//! - **Space complexity**: O(n) for cached nodes and reuse maps
15//! - **Large file scaling**: Tuned to scale for large file edits (50GB PST-style workspaces)
16//!
17//! ## Supported Edit Types
18//!
19//! - **Simple value edits**: Number and string literal changes
20//! - **Variable name edits**: Identifier modifications within bounds
21//! - **Whitespace and comment edits**: Non-structural changes
22//! - **Multiple edits**: Batch processing with cumulative position tracking
23//!
24//! ## Usage Example
25//!
26//! ```rust,ignore
27//! use perl_parser::incremental_v2::IncrementalParserV2;
28//! use perl_parser::edit::Edit;
29//! use perl_parser::position::Position;
30//!
31//! let mut parser = IncrementalParserV2::new();
32//!
33//! // Initial parse
34//! let source1 = "my $x = 42;";
35//! let tree1 = parser.parse(source1)?;
36//!
37//! // Apply incremental edit
38//! let edit = Edit::new(
39//!     8, 10, 12, // positions: "42" -> "9999"
40//!     Position::new(8, 1, 9),
41//!     Position::new(10, 1, 11),
42//!     Position::new(12, 1, 13),
43//! );
44//! parser.edit(edit);
45//!
46//! // Incremental reparse (typically <1ms)
47//! let source2 = "my $x = 9999;";
48//! let tree2 = parser.parse(source2)?;
49//!
50//! // Check performance metrics
51//! println!("Nodes reused: {}", parser.reused_nodes);
52//! println!("Nodes reparsed: {}", parser.reparsed_nodes);
53//! # Ok::<(), perl_parser::error::ParseError>(())
54//! ```
55
56use super::incremental_advanced_reuse::{AdvancedReuseAnalyzer, ReuseAnalysisResult, ReuseConfig};
57use perl_parser_core::{
58    ast::{Node, NodeKind, SourceLocation},
59    edit::{Edit, EditSet},
60    error::ParseResult,
61    parser::Parser,
62    position::Range,
63};
64use std::collections::HashMap;
65
66/// Comprehensive performance metrics for incremental parsing analysis
67///
68/// Tracks detailed performance characteristics including parsing time,
69/// node reuse statistics, and efficiency measurements for optimization
70/// and debugging purposes.
71#[derive(Debug, Clone, Default)]
72pub struct IncrementalMetrics {
73    pub parse_time_micros: u128,
74    pub nodes_reused: usize,
75    pub nodes_reparsed: usize,
76    pub cache_hit_ratio: f64,
77    pub edit_count: usize,
78}
79
80impl IncrementalMetrics {
81    /// Create zeroed metrics.
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    /// Percentage of nodes reused out of all nodes touched (0–100).
87    pub fn efficiency_percentage(&self) -> f64 {
88        if self.nodes_reused + self.nodes_reparsed == 0 {
89            return 0.0;
90        }
91        self.nodes_reused as f64 / (self.nodes_reused + self.nodes_reparsed) as f64 * 100.0
92    }
93
94    /// Return `true` when the last parse completed in under 1 ms.
95    pub fn is_sub_millisecond(&self) -> bool {
96        self.parse_time_micros < 1000
97    }
98
99    /// Return a human-readable performance tier label for the last parse time.
100    pub fn performance_category(&self) -> &'static str {
101        match self.parse_time_micros {
102            0..=100 => "Excellent (<100µs)",
103            101..=500 => "Very Good (<500µs)",
104            501..=1000 => "Good (<1ms)",
105            1001..=5000 => "Acceptable (<5ms)",
106            _ => "Needs Optimization (>5ms)",
107        }
108    }
109}
110
111/// A parse tree with incremental parsing support and node mapping
112///
113/// Maintains an AST along with efficient lookup structures for
114/// finding nodes by position, enabling fast incremental updates.
115/// The node_map provides O(1) access to nodes at specific byte positions.
116#[derive(Debug, Clone)]
117pub struct IncrementalTree {
118    pub root: Node,
119    pub source: String,
120    /// Maps byte positions to nodes for efficient lookup
121    node_map: HashMap<usize, Vec<Node>>,
122}
123
124impl IncrementalTree {
125    /// Create a new incremental tree
126    pub fn new(root: Node, source: String) -> Self {
127        let mut tree = IncrementalTree { root, source, node_map: HashMap::new() };
128        tree.build_node_map();
129        tree
130    }
131
132    /// Build a map of byte positions to nodes
133    fn build_node_map(&mut self) {
134        self.node_map.clear();
135        self.map_node(&self.root.clone());
136    }
137
138    fn map_node(&mut self, node: &Node) {
139        // Map start position to node
140        self.node_map.entry(node.location.start).or_default().push(node.clone());
141
142        // Recursively map child nodes
143        match &node.kind {
144            NodeKind::Program { statements } | NodeKind::Block { statements } => {
145                for stmt in statements {
146                    self.map_node(stmt);
147                }
148            }
149            NodeKind::VariableDeclaration { variable, initializer, .. } => {
150                self.map_node(variable);
151                if let Some(init) = initializer {
152                    self.map_node(init);
153                }
154            }
155            NodeKind::Binary { left, right, .. } => {
156                self.map_node(left);
157                self.map_node(right);
158            }
159            NodeKind::Unary { operand, .. } => {
160                self.map_node(operand);
161            }
162            NodeKind::FunctionCall { args, .. } => {
163                for arg in args {
164                    self.map_node(arg);
165                }
166            }
167            NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
168                self.map_node(condition);
169                self.map_node(then_branch);
170                for (cond, branch) in elsif_branches {
171                    self.map_node(cond);
172                    self.map_node(branch);
173                }
174                if let Some(branch) = else_branch {
175                    self.map_node(branch);
176                }
177            }
178            _ => {}
179        }
180    }
181
182    /// Find the smallest node containing the given byte range
183    pub fn find_containing_node(&self, start: usize, end: usize) -> Option<&Node> {
184        let mut smallest: Option<&Node> = None;
185        let mut smallest_size = usize::MAX;
186
187        // Check all nodes
188        for nodes in self.node_map.values() {
189            for node in nodes {
190                if node.location.start <= start && node.location.end >= end {
191                    let size = node.location.end - node.location.start;
192                    if size < smallest_size {
193                        smallest = Some(node);
194                        smallest_size = size;
195                    }
196                }
197            }
198        }
199
200        smallest
201    }
202}
203
204/// High-performance incremental parser with intelligent AST node reuse
205///
206/// Maintains previous parse state and applies edits incrementally when possible,
207/// falling back to full parsing for complex structural changes. Designed for
208/// real-time editing scenarios with sub-millisecond update targets.
209///
210/// ## Thread Safety
211///
212/// IncrementalParserV2 is not thread-safe and should be used from a single thread.
213/// For multi-threaded scenarios, create separate parser instances per thread.
214pub struct IncrementalParserV2 {
215    last_tree: Option<IncrementalTree>,
216    pending_edits: EditSet,
217    pub reused_nodes: usize,
218    pub reparsed_nodes: usize,
219    pub metrics: IncrementalMetrics,
220    /// Advanced reuse analyzer for sophisticated tree reuse strategies
221    reuse_analyzer: AdvancedReuseAnalyzer,
222    /// Configuration for reuse analysis
223    reuse_config: ReuseConfig,
224    /// Performance tracking for reuse analysis
225    pub last_reuse_analysis: Option<ReuseAnalysisResult>,
226}
227
228impl IncrementalParserV2 {
229    /// Create a parser with default reuse configuration and no cached tree.
230    pub fn new() -> Self {
231        IncrementalParserV2 {
232            last_tree: None,
233            pending_edits: EditSet::new(),
234            reused_nodes: 0,
235            reparsed_nodes: 0,
236            metrics: IncrementalMetrics::new(),
237            reuse_analyzer: AdvancedReuseAnalyzer::new(),
238            reuse_config: ReuseConfig::default(),
239            last_reuse_analysis: None,
240        }
241    }
242
243    /// Create parser with custom reuse configuration
244    pub fn with_reuse_config(config: ReuseConfig) -> Self {
245        IncrementalParserV2 {
246            last_tree: None,
247            pending_edits: EditSet::new(),
248            reused_nodes: 0,
249            reparsed_nodes: 0,
250            metrics: IncrementalMetrics::new(),
251            reuse_analyzer: AdvancedReuseAnalyzer::with_config(config.clone()),
252            reuse_config: config,
253            last_reuse_analysis: None,
254        }
255    }
256
257    /// Queue an edit to be applied on the next [`parse`] call.
258    ///
259    /// [`parse`]: IncrementalParserV2::parse
260    pub fn edit(&mut self, edit: Edit) {
261        self.pending_edits.add(edit);
262    }
263
264    /// Parse `source`, reusing cached tree nodes where edits did not affect them.
265    pub fn parse(&mut self, source: &str) -> ParseResult<Node> {
266        // Reset statistics
267        self.reused_nodes = 0;
268        self.reparsed_nodes = 0;
269
270        // Try incremental parsing if we have a previous tree and edits
271        if let Some(ref last_tree) = self.last_tree {
272            if !self.pending_edits.is_empty() {
273                let last_tree_clone = last_tree.clone();
274                // Check if we can do incremental parsing
275                if let Some(new_tree) = self.try_incremental_parse(source, &last_tree_clone) {
276                    self.last_tree =
277                        Some(IncrementalTree::new(new_tree.clone(), source.to_string()));
278                    self.pending_edits = EditSet::new();
279                    return Ok(new_tree);
280                }
281            }
282        }
283
284        // Fall back to full parse
285        self.full_parse(source)
286    }
287
288    fn full_parse(&mut self, source: &str) -> ParseResult<Node> {
289        let mut parser = Parser::new(source);
290        let root = parser.parse()?;
291
292        // For first parse or structural changes, all nodes are reparsed
293        if self.last_tree.is_none() {
294            // First parse - no reuse possible
295            self.reused_nodes = 0;
296            self.reparsed_nodes = self.count_nodes(&root);
297        } else {
298            // Check if this was a fallback due to too many edits, invalid conditions, or empty source
299            // In such cases, we should report 0 reused nodes as it's truly a full reparse
300            let should_skip_reuse = source.is_empty()
301                || self.pending_edits.len() > 10
302                || self.last_tree.as_ref().is_none_or(|tree| !self.is_simple_value_edit(tree));
303
304            if should_skip_reuse {
305                // Full fallback - no actual reuse
306                self.reused_nodes = 0;
307                self.reparsed_nodes = self.count_nodes(&root);
308            } else if let Some(ref old_tree) = self.last_tree {
309                // Normal incremental fallback - still compare against old tree
310                let (reused, reparsed) = self.analyze_reuse(&old_tree.root, &root);
311                self.reused_nodes = reused;
312                self.reparsed_nodes = reparsed;
313            } else {
314                // No old tree - full parse
315                self.reused_nodes = 0;
316                self.reparsed_nodes = self.count_nodes(&root);
317            }
318        }
319
320        self.last_tree = Some(IncrementalTree::new(root.clone(), source.to_string()));
321        self.pending_edits = EditSet::new();
322
323        Ok(root)
324    }
325
326    fn try_incremental_parse(&mut self, source: &str, last_tree: &IncrementalTree) -> Option<Node> {
327        // Try advanced reuse analysis first
328        if let Some(advanced_result) = self.try_advanced_reuse_parse(source, last_tree) {
329            return Some(advanced_result);
330        }
331
332        // Fall back to original strategies for compatibility
333        let is_simple = self.is_simple_value_edit(last_tree);
334        if is_simple {
335            return self.incremental_parse_simple(source, last_tree);
336        }
337
338        // Check for other incremental opportunities
339        if self.is_whitespace_or_comment_edit(last_tree, source) {
340            return self.incremental_parse_whitespace(source, last_tree);
341        }
342
343        // For complex structural changes, fall back to full parse
344        None
345    }
346
347    /// Try advanced reuse analysis for sophisticated tree reuse
348    fn try_advanced_reuse_parse(
349        &mut self,
350        source: &str,
351        last_tree: &IncrementalTree,
352    ) -> Option<Node> {
353        // Parse the new source to get target tree structure
354        let mut parser = Parser::new(source);
355        let new_tree = match parser.parse() {
356            Ok(tree) => tree,
357            Err(_) => {
358                return None;
359            }
360        };
361
362        // Analyze reuse opportunities with advanced algorithms
363        let analysis_result = self.reuse_analyzer.analyze_reuse_opportunities(
364            &last_tree.root,
365            &new_tree,
366            &self.pending_edits,
367            &self.reuse_config,
368        );
369
370        // Store analysis results for inspection
371        self.last_reuse_analysis = Some(analysis_result);
372
373        // Check if reuse analysis meets our efficiency targets
374        if let Some(ref analysis) = self.last_reuse_analysis {
375            if analysis.meets_efficiency_target(self.reuse_config.min_confidence * 100.0) {
376                // Update statistics based on analysis
377                self.reused_nodes = analysis.reused_nodes;
378                self.reparsed_nodes = analysis.total_new_nodes - analysis.reused_nodes;
379
380                // Return the new tree with reuse benefits counted
381                return Some(new_tree);
382            }
383        }
384
385        None
386    }
387
388    fn is_simple_value_edit(&self, tree: &IncrementalTree) -> bool {
389        // Don't attempt incremental parsing for too many edits at once
390        if self.pending_edits.len() > 10 {
391            return false;
392        }
393
394        // Track cumulative shift so we can map each edit back to the
395        // coordinates in the original source code represented by `tree`.
396        let mut cumulative_shift: isize = 0;
397
398        for edit in self.pending_edits.edits() {
399            let original_start = (edit.start_byte as isize - cumulative_shift) as usize;
400            let original_end = (edit.old_end_byte as isize - cumulative_shift) as usize;
401
402            let affected_node = tree.find_containing_node(original_start, original_end);
403
404            match affected_node {
405                Some(node) => {
406                    match &node.kind {
407                        // Support string and numeric literals
408                        NodeKind::Number { .. } | NodeKind::String { .. } => {
409                            // Ensure the edit stays within the literal node bounds
410                            if original_start >= node.location.start
411                                && original_end <= node.location.end
412                            {
413                                cumulative_shift += edit.byte_shift();
414                                continue;
415                            } else {
416                                return false;
417                            }
418                        }
419                        // Support simple identifier edits (variable names)
420                        NodeKind::Variable { .. } => {
421                            if original_start >= node.location.start
422                                && original_end <= node.location.end
423                            {
424                                cumulative_shift += edit.byte_shift();
425                                continue;
426                            } else {
427                                return false;
428                            }
429                        }
430                        // Support identifier edits (identifiers can often be treated like simple values)
431                        NodeKind::Identifier { .. } => {
432                            if original_start >= node.location.start
433                                && original_end <= node.location.end
434                            {
435                                cumulative_shift += edit.byte_shift();
436                                continue;
437                            } else {
438                                return false;
439                            }
440                        }
441                        _ => {
442                            return false; // Not a simple value
443                        }
444                    }
445                }
446                None => {
447                    return false; // No containing node found
448                }
449            }
450        }
451
452        true
453    }
454
455    /// Check if all edits only affect whitespace or comments
456    fn is_whitespace_or_comment_edit(&self, tree: &IncrementalTree, source: &str) -> bool {
457        for edit in self.pending_edits.edits() {
458            // Validate both sides of the edit:
459            // - old source text being replaced
460            // - new source text inserted by the edit
461            // If either side introduces structural tokens, fall back.
462            if !self.is_edit_non_structural(
463                tree,
464                source,
465                edit.start_byte,
466                edit.old_end_byte,
467                edit.new_end_byte,
468            ) {
469                return false;
470            }
471        }
472        true
473    }
474
475    fn is_edit_non_structural(
476        &self,
477        tree: &IncrementalTree,
478        source: &str,
479        start: usize,
480        old_end: usize,
481        new_end: usize,
482    ) -> bool {
483        if old_end > start && !self.is_in_non_structural_content(&tree.source, start, old_end) {
484            return false;
485        }
486
487        if new_end > start && !self.is_in_non_structural_content(source, start, new_end) {
488            return false;
489        }
490
491        true
492    }
493
494    /// Check if the given range only contains whitespace or comments
495    ///
496    /// Uses lexical analysis to determine if the edited range contains only
497    /// non-structural content (whitespace, comments) that doesn't affect AST structure.
498    fn is_in_non_structural_content(&self, text: &str, start: usize, end: usize) -> bool {
499        use perl_lexer::{PerlLexer, TokenType};
500
501        if start >= end || end > text.len() {
502            return false;
503        }
504
505        let affected_text = &text[start..end];
506
507        // Create a lexer to analyze the tokens in this range
508        let mut lexer = PerlLexer::new(affected_text);
509
510        // Analyze all tokens in the range
511        loop {
512            match lexer.next_token() {
513                Some(token) => {
514                    match token.token_type {
515                        // These token types are non-structural
516                        TokenType::Whitespace | TokenType::Newline | TokenType::Comment(_) => {
517                            // Continue checking
518                        }
519                        TokenType::EOF => {
520                            // Reached end - all tokens were non-structural
521                            return true;
522                        }
523                        _ => {
524                            // Found a structural token
525                            return false;
526                        }
527                    }
528                }
529                None => {
530                    // No more tokens - all were non-structural
531                    return true;
532                }
533            }
534        }
535    }
536
537    /// Parse with whitespace/comment optimizations
538    fn incremental_parse_whitespace(
539        &mut self,
540        _source: &str,
541        last_tree: &IncrementalTree,
542    ) -> Option<Node> {
543        // For whitespace-only changes, we can often reuse the entire tree
544        // with just position adjustments
545        let shift = self.calculate_total_shift();
546        self.reused_nodes = self.count_nodes(&last_tree.root);
547        self.reparsed_nodes = 0;
548        Some(self.clone_with_shifted_positions(&last_tree.root, shift))
549    }
550
551    /// Calculate the total byte shift from all edits
552    fn calculate_total_shift(&self) -> isize {
553        self.pending_edits.edits().iter().map(|edit| edit.byte_shift()).sum()
554    }
555
556    fn incremental_parse_simple(
557        &mut self,
558        source: &str,
559        last_tree: &IncrementalTree,
560    ) -> Option<Node> {
561        // Validate that the source is long enough for our edits
562        if source.is_empty() {
563            return None;
564        }
565
566        // Reuse the previous tree by cloning nodes and applying the edits.
567        let new_root = self.clone_and_update_node(&last_tree.root, source, &last_tree.source);
568
569        // Validate that the new tree makes sense
570        if !self.validate_incremental_result(&new_root, source) {
571            return None;
572        }
573
574        // After producing the new tree, analyse how many nodes were reused
575        // versus reparsed for metrics.
576        self.count_reuse_potential(&last_tree.root, &new_root);
577
578        Some(new_root)
579    }
580
581    /// Validate that an incremental parsing result is reasonable
582    ///
583    /// Enhanced validation including structural consistency and Unicode safety.
584    fn validate_incremental_result(&self, node: &Node, source: &str) -> bool {
585        // Basic sanity checks
586        if source.is_empty() {
587            // Empty source is edge case - validate node is minimal
588            return match &node.kind {
589                NodeKind::Program { statements } => statements.is_empty(),
590                _ => false,
591            };
592        }
593
594        // Position boundary validation
595        if node.location.start > source.len() || node.location.end > source.len() {
596            return false;
597        }
598
599        if node.location.start > node.location.end {
600            return false;
601        }
602
603        // Unicode boundary validation - ensure positions fall on character boundaries
604        if !source.is_char_boundary(node.location.start)
605            || !source.is_char_boundary(node.location.end)
606        {
607            return false;
608        }
609
610        // Structural validation - ensure node content matches source
611        if node.location.start < node.location.end {
612            let node_text = &source[node.location.start..node.location.end];
613
614            // Validate node content makes sense for node type
615            match &node.kind {
616                NodeKind::Number { value } => {
617                    // Number value should be parseable and match source
618                    if value.trim() != node_text.trim() {
619                        return false;
620                    }
621                    // Validate it's actually a number
622                    if value.parse::<f64>().is_err() && value.parse::<i64>().is_err() {
623                        return false;
624                    }
625                }
626                NodeKind::String { value, .. } => {
627                    // String content validation - should include quotes if present
628                    if !node_text.is_empty()
629                        && !value.contains(node_text.trim_matches(|c| c == '"' || c == '\''))
630                    {
631                        // Be lenient for string validation as quotes might be handled differently
632                    }
633                }
634                NodeKind::Variable { name, .. } => {
635                    // Variable name should appear in the source text
636                    if !node_text.contains(name) {
637                        return false;
638                    }
639                }
640                NodeKind::Identifier { name } => {
641                    // Identifier name should match source text
642                    if name.trim() != node_text.trim() {
643                        return false;
644                    }
645                }
646                _ => {
647                    // For container nodes, just ensure they have reasonable bounds
648                    // Detailed validation would require recursing into children
649                }
650            }
651        }
652
653        // Recursive validation for container nodes (limited depth to avoid performance issues)
654        self.validate_node_tree_consistency(node, source, 0, 3)
655    }
656
657    /// Recursive validation helper with depth limiting
658    fn validate_node_tree_consistency(
659        &self,
660        node: &Node,
661        source: &str,
662        depth: usize,
663        max_depth: usize,
664    ) -> bool {
665        if depth > max_depth {
666            return true; // Stop recursing to avoid performance issues
667        }
668
669        match &node.kind {
670            NodeKind::Program { statements } | NodeKind::Block { statements } => {
671                // Validate all child statements are within parent bounds
672                for stmt in statements {
673                    if stmt.location.start < node.location.start
674                        || stmt.location.end > node.location.end
675                    {
676                        return false;
677                    }
678                    if !self.validate_node_tree_consistency(stmt, source, depth + 1, max_depth) {
679                        return false;
680                    }
681                }
682            }
683            NodeKind::VariableDeclaration { variable, initializer, .. } => {
684                if !self.validate_node_tree_consistency(variable, source, depth + 1, max_depth) {
685                    return false;
686                }
687                if let Some(init) = initializer {
688                    if !self.validate_node_tree_consistency(init, source, depth + 1, max_depth) {
689                        return false;
690                    }
691                }
692            }
693            NodeKind::Binary { left, right, .. } => {
694                if !self.validate_node_tree_consistency(left, source, depth + 1, max_depth)
695                    || !self.validate_node_tree_consistency(right, source, depth + 1, max_depth)
696                {
697                    return false;
698                }
699            }
700            _ => {
701                // Leaf nodes don't need recursive validation
702            }
703        }
704
705        true
706    }
707
708    fn clone_and_update_node(&self, node: &Node, new_source: &str, old_source: &str) -> Node {
709        // Calculate position shift for this node
710        let shift = self.calculate_shift_at(node.location.start);
711
712        // Check if this node is affected by any edit
713        let affected = self.is_node_affected(node);
714
715        // Handle container nodes that need recursive processing
716        match &node.kind {
717            NodeKind::Program { statements } => {
718                // Recursively update child nodes
719                let new_statements: Vec<Node> = statements
720                    .iter()
721                    .map(|stmt| self.clone_and_update_node(stmt, new_source, old_source))
722                    .collect();
723
724                let new_start = (node.location.start as isize + shift) as usize;
725                let new_end = (node.location.end as isize
726                    + shift
727                    + self.calculate_content_delta(node)) as usize;
728
729                return Node::new(
730                    NodeKind::Program { statements: new_statements },
731                    SourceLocation { start: new_start, end: new_end },
732                );
733            }
734            NodeKind::VariableDeclaration { declarator, variable, initializer, attributes } => {
735                // Recursively update child nodes
736                let new_variable = self.clone_and_update_node(variable, new_source, old_source);
737                let new_initializer = initializer
738                    .as_ref()
739                    .map(|init| self.clone_and_update_node(init, new_source, old_source));
740
741                let new_start = (node.location.start as isize + shift) as usize;
742                let new_end = (node.location.end as isize
743                    + shift
744                    + self.calculate_content_delta(node)) as usize;
745
746                return Node::new(
747                    NodeKind::VariableDeclaration {
748                        declarator: declarator.clone(),
749                        variable: Box::new(new_variable),
750                        initializer: new_initializer.map(Box::new),
751                        attributes: attributes.clone(),
752                    },
753                    SourceLocation { start: new_start, end: new_end },
754                );
755            }
756            _ => {}
757        }
758
759        if affected {
760            // This node is affected - handle based on node type
761            match &node.kind {
762                // Direct value nodes - extract new value from source
763                NodeKind::Number { .. } => {
764                    // Extract the new value from source
765                    let new_start = (node.location.start as isize + shift) as usize;
766                    let new_end =
767                        (node.location.end as isize + shift + self.calculate_content_delta(node))
768                            as usize;
769
770                    if new_start < new_source.len() && new_end <= new_source.len() {
771                        let new_value = &new_source[new_start..new_end];
772
773                        return Node::new(
774                            NodeKind::Number { value: new_value.to_string() },
775                            SourceLocation { start: new_start, end: new_end },
776                        );
777                    }
778                }
779                NodeKind::String { interpolated, .. } => {
780                    let new_start = (node.location.start as isize + shift) as usize;
781                    let new_end =
782                        (node.location.end as isize + shift + self.calculate_content_delta(node))
783                            as usize;
784
785                    if new_start < new_source.len() && new_end <= new_source.len() {
786                        let new_value = &new_source[new_start..new_end];
787
788                        return Node::new(
789                            NodeKind::String {
790                                value: new_value.to_string(),
791                                interpolated: *interpolated,
792                            },
793                            SourceLocation { start: new_start, end: new_end },
794                        );
795                    }
796                }
797                // Container nodes - recursively process children
798                NodeKind::Program { statements } => {
799                    let new_statements = statements
800                        .iter()
801                        .map(|stmt| self.clone_and_update_node(stmt, new_source, old_source))
802                        .collect();
803                    let new_location = SourceLocation {
804                        start: (node.location.start as isize + shift) as usize,
805                        end: (node.location.end as isize + shift) as usize,
806                    };
807                    return Node::new(
808                        NodeKind::Program { statements: new_statements },
809                        new_location,
810                    );
811                }
812                NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
813                    let new_variable =
814                        Box::new(self.clone_and_update_node(variable, new_source, old_source));
815                    let new_initializer = initializer.as_ref().map(|init| {
816                        Box::new(self.clone_and_update_node(init, new_source, old_source))
817                    });
818                    let new_location = SourceLocation {
819                        start: (node.location.start as isize + shift) as usize,
820                        end: (node.location.end as isize + shift) as usize,
821                    };
822                    return Node::new(
823                        NodeKind::VariableDeclaration {
824                            declarator: declarator.clone(),
825                            variable: new_variable,
826                            attributes: attributes.clone(),
827                            initializer: new_initializer,
828                        },
829                        new_location,
830                    );
831                }
832                _ => {}
833            }
834        }
835
836        // Node is not affected or cannot be updated - clone with shifted positions
837        self.clone_with_shifted_positions(node, shift)
838    }
839
840    /// Calculate cumulative byte shift at position with Unicode-safe handling
841    ///
842    /// Enhanced to handle multibyte Unicode characters correctly and avoid
843    /// splitting characters across edit boundaries.
844    fn calculate_shift_at(&self, position: usize) -> isize {
845        let mut shift = 0;
846        for edit in self.pending_edits.edits() {
847            let original_old_end = (edit.old_end_byte as isize - shift) as usize;
848
849            if original_old_end <= position {
850                let edit_shift = edit.byte_shift();
851                shift += edit_shift;
852            } else {
853                break;
854            }
855        }
856
857        shift
858    }
859
860    /// Ensure position falls on a valid Unicode character boundary
861    ///
862    /// Adjusts position to the nearest valid character boundary if needed,
863    /// preventing panics from invalid UTF-8 slice operations.
864    #[allow(dead_code)]
865    fn ensure_unicode_boundary(&self, source: &str, position: usize) -> usize {
866        if position >= source.len() {
867            return source.len();
868        }
869
870        if source.is_char_boundary(position) {
871            return position;
872        }
873
874        // Find the previous character boundary
875        for i in (0..=position).rev() {
876            if i < source.len() && source.is_char_boundary(i) {
877                return i;
878            }
879        }
880
881        // Fallback to start of string
882        0
883    }
884
885    /// Calculate position shift with Unicode safety
886    ///
887    /// Ensures that the shifted position falls on a valid character boundary
888    /// and handles complex multibyte characters correctly.
889    #[allow(dead_code)]
890    fn calculate_unicode_safe_position(
891        &self,
892        original_pos: usize,
893        shift: isize,
894        source: &str,
895    ) -> usize {
896        let new_pos = if shift >= 0 {
897            original_pos.saturating_add(shift as usize)
898        } else {
899            original_pos.saturating_sub((-shift) as usize)
900        };
901
902        self.ensure_unicode_boundary(source, new_pos)
903    }
904
905    /// Get current performance metrics
906    pub fn get_metrics(&self) -> &IncrementalMetrics {
907        &self.metrics
908    }
909
910    /// Reset performance metrics
911    pub fn reset_metrics(&mut self) {
912        self.metrics = IncrementalMetrics::new();
913    }
914
915    /// Get the last reuse analysis result if available
916    pub fn get_last_reuse_analysis(&self) -> Option<&ReuseAnalysisResult> {
917        self.last_reuse_analysis.as_ref()
918    }
919
920    /// Update reuse configuration
921    pub fn set_reuse_config(&mut self, config: ReuseConfig) {
922        self.reuse_config = config.clone();
923        self.reuse_analyzer = AdvancedReuseAnalyzer::with_config(config);
924    }
925
926    /// Check if the last parse used advanced reuse analysis
927    pub fn used_advanced_reuse(&self) -> bool {
928        self.last_reuse_analysis.as_ref().is_some_and(|analysis| analysis.reuse_percentage > 0.0)
929    }
930
931    /// Get detailed reuse efficiency report
932    pub fn get_reuse_efficiency_report(&self) -> String {
933        if let Some(analysis) = &self.last_reuse_analysis {
934            format!(
935                "Advanced Reuse Analysis:\n  Efficiency: {:.1}%\n  Nodes reused: {}\n  Total nodes: {}\n  {}",
936                analysis.reuse_percentage,
937                analysis.reused_nodes,
938                analysis.total_old_nodes,
939                analysis.performance_summary()
940            )
941        } else {
942            format!(
943                "Basic Incremental Analysis:\n  Efficiency: {:.1}%\n  Nodes reused: {}\n  Nodes reparsed: {}",
944                self.reused_nodes as f64 / (self.reused_nodes + self.reparsed_nodes) as f64 * 100.0,
945                self.reused_nodes,
946                self.reparsed_nodes
947            )
948        }
949    }
950
951    fn calculate_content_delta(&self, node: &Node) -> isize {
952        // Calculate how much the content of this node changed by examining
953        // edits that fall within the node's original range.
954        let mut delta = 0;
955        let mut shift = 0;
956        for edit in self.pending_edits.edits() {
957            let start = (edit.start_byte as isize - shift) as usize;
958            let end = (edit.old_end_byte as isize - shift) as usize;
959            if start >= node.location.start && end <= node.location.end {
960                delta += edit.byte_shift();
961            }
962            shift += edit.byte_shift();
963        }
964        delta
965    }
966
967    fn is_node_affected(&self, node: &Node) -> bool {
968        let node_range = Range::from(node.location);
969        self.pending_edits.affects_range(&node_range)
970    }
971
972    fn clone_with_shifted_positions(&self, node: &Node, shift: isize) -> Node {
973        // Use Unicode-safe position calculation for multibyte character support
974        let new_start = if shift >= 0 {
975            node.location.start.saturating_add(shift as usize)
976        } else {
977            node.location.start.saturating_sub((-shift) as usize)
978        };
979
980        let new_end = if shift >= 0 {
981            node.location.end.saturating_add(shift as usize)
982        } else {
983            node.location.end.saturating_sub((-shift) as usize)
984        };
985
986        let new_location = SourceLocation { start: new_start, end: new_end };
987
988        let new_kind = match &node.kind {
989            NodeKind::Program { statements } => NodeKind::Program {
990                statements: statements
991                    .iter()
992                    .map(|s| self.clone_with_shifted_positions(s, shift))
993                    .collect(),
994            },
995            NodeKind::Block { statements } => NodeKind::Block {
996                statements: statements
997                    .iter()
998                    .map(|s| self.clone_with_shifted_positions(s, shift))
999                    .collect(),
1000            },
1001            NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
1002                NodeKind::VariableDeclaration {
1003                    declarator: declarator.clone(),
1004                    variable: Box::new(self.clone_with_shifted_positions(variable, shift)),
1005                    attributes: attributes.clone(),
1006                    initializer: initializer
1007                        .as_ref()
1008                        .map(|i| Box::new(self.clone_with_shifted_positions(i, shift))),
1009                }
1010            }
1011            NodeKind::Binary { op, left, right } => NodeKind::Binary {
1012                op: op.clone(),
1013                left: Box::new(self.clone_with_shifted_positions(left, shift)),
1014                right: Box::new(self.clone_with_shifted_positions(right, shift)),
1015            },
1016            NodeKind::Unary { op, operand } => NodeKind::Unary {
1017                op: op.clone(),
1018                operand: Box::new(self.clone_with_shifted_positions(operand, shift)),
1019            },
1020            NodeKind::FunctionCall { name, args } => NodeKind::FunctionCall {
1021                name: name.clone(),
1022                args: args.iter().map(|a| self.clone_with_shifted_positions(a, shift)).collect(),
1023            },
1024            NodeKind::If {
1025                condition, then_branch, elsif_branches, else_branch, keyword, ..
1026            } => NodeKind::If {
1027                condition: Box::new(self.clone_with_shifted_positions(condition, shift)),
1028                then_branch: Box::new(self.clone_with_shifted_positions(then_branch, shift)),
1029                elsif_branches: elsif_branches
1030                    .iter()
1031                    .map(|(c, b)| {
1032                        (
1033                            self.clone_with_shifted_positions(c, shift),
1034                            self.clone_with_shifted_positions(b, shift),
1035                        )
1036                    })
1037                    .map(|(c, b)| (Box::new(c), Box::new(b)))
1038                    .collect(),
1039                else_branch: else_branch
1040                    .as_ref()
1041                    .map(|b| Box::new(self.clone_with_shifted_positions(b, shift))),
1042                keyword: keyword.clone(),
1043            },
1044            _ => node.kind.clone(), // For leaf nodes, just clone
1045        };
1046
1047        Node::new(new_kind, new_location)
1048    }
1049
1050    fn count_reuse_potential(&mut self, old_root: &Node, new_root: &Node) {
1051        // Compare trees and count which nodes could have been reused
1052        let (reused, reparsed) = self.analyze_reuse(old_root, new_root);
1053        self.reused_nodes = reused;
1054        self.reparsed_nodes = reparsed;
1055    }
1056
1057    fn analyze_reuse(&self, old_node: &Node, new_node: &Node) -> (usize, usize) {
1058        // Check if nodes are structurally equivalent
1059        match (&old_node.kind, &new_node.kind) {
1060            (
1061                NodeKind::Program { statements: old_stmts },
1062                NodeKind::Program { statements: new_stmts },
1063            ) => {
1064                let mut reused = 1; // Program node itself
1065                let mut reparsed = 0;
1066
1067                for (old_stmt, new_stmt) in old_stmts.iter().zip(new_stmts.iter()) {
1068                    let (r, p) = self.analyze_reuse(old_stmt, new_stmt);
1069                    reused += r;
1070                    reparsed += p;
1071                }
1072
1073                (reused, reparsed)
1074            }
1075            (
1076                NodeKind::VariableDeclaration { variable: old_var, initializer: old_init, .. },
1077                NodeKind::VariableDeclaration { variable: new_var, initializer: new_init, .. },
1078            ) => {
1079                let mut reused = 1; // VarDecl itself
1080                let mut reparsed = 0;
1081
1082                let (r, p) = self.analyze_reuse(old_var, new_var);
1083                reused += r;
1084                reparsed += p;
1085
1086                if let (Some(old_i), Some(new_i)) = (old_init, new_init) {
1087                    let (r, p) = self.analyze_reuse(old_i, new_i);
1088                    reused += r;
1089                    reparsed += p;
1090                }
1091
1092                (reused, reparsed)
1093            }
1094            (NodeKind::Number { value: old_val }, NodeKind::Number { value: new_val }) => {
1095                if old_val != new_val {
1096                    (0, 1) // Value changed - reparsed
1097                } else {
1098                    (1, 0) // Value same - could have been reused
1099                }
1100            }
1101            (
1102                NodeKind::Variable { sigil: old_s, name: old_n },
1103                NodeKind::Variable { sigil: new_s, name: new_n },
1104            ) => {
1105                if old_s == new_s && old_n == new_n {
1106                    (1, 0) // Reused
1107                } else {
1108                    (0, 1) // Reparsed
1109                }
1110            }
1111            _ => {
1112                if self.nodes_match(old_node, new_node) {
1113                    (1, 0)
1114                } else {
1115                    (0, 1)
1116                }
1117            }
1118        }
1119    }
1120
1121    /// Check if two nodes are structurally equivalent for reuse purposes
1122    ///
1123    /// Enhanced to support more node types for better reuse detection.
1124    /// Returns true if nodes can be considered equivalent for caching.
1125    fn nodes_match(&self, node1: &Node, node2: &Node) -> bool {
1126        match (&node1.kind, &node2.kind) {
1127            // Value nodes - must match exactly
1128            (NodeKind::Number { value: v1 }, NodeKind::Number { value: v2 }) => v1 == v2,
1129            (
1130                NodeKind::String { value: v1, interpolated: i1 },
1131                NodeKind::String { value: v2, interpolated: i2 },
1132            ) => v1 == v2 && i1 == i2,
1133
1134            // Variable nodes - sigil and name must match
1135            (
1136                NodeKind::Variable { sigil: s1, name: n1 },
1137                NodeKind::Variable { sigil: s2, name: n2 },
1138            ) => s1 == s2 && n1 == n2,
1139
1140            // Identifier nodes
1141            (NodeKind::Identifier { name: n1 }, NodeKind::Identifier { name: n2 }) => n1 == n2,
1142
1143            // Binary operators - operator must match, operands checked recursively
1144            (NodeKind::Binary { op: op1, .. }, NodeKind::Binary { op: op2, .. }) => op1 == op2,
1145
1146            // Unary operators - operator must match, operand checked recursively
1147            (NodeKind::Unary { op: op1, .. }, NodeKind::Unary { op: op2, .. }) => op1 == op2,
1148
1149            // Function calls - name and argument count should match
1150            (
1151                NodeKind::FunctionCall { name: n1, args: args1 },
1152                NodeKind::FunctionCall { name: n2, args: args2 },
1153            ) => n1 == n2 && args1.len() == args2.len(),
1154
1155            // Variable declarations - declarator should match
1156            (
1157                NodeKind::VariableDeclaration { declarator: d1, .. },
1158                NodeKind::VariableDeclaration { declarator: d2, .. },
1159            ) => d1 == d2,
1160
1161            // Array literals - length should match for structural similarity
1162            (NodeKind::ArrayLiteral { elements: e1 }, NodeKind::ArrayLiteral { elements: e2 }) => {
1163                e1.len() == e2.len()
1164            }
1165
1166            // Hash literals - key count should match for structural similarity
1167            (NodeKind::HashLiteral { pairs: p1 }, NodeKind::HashLiteral { pairs: p2 }) => {
1168                p1.len() == p2.len()
1169            }
1170
1171            // Block statements - statement count should match
1172            (NodeKind::Block { statements: s1 }, NodeKind::Block { statements: s2 }) => {
1173                s1.len() == s2.len()
1174            }
1175
1176            // Program nodes - statement count should match
1177            (NodeKind::Program { statements: s1 }, NodeKind::Program { statements: s2 }) => {
1178                s1.len() == s2.len()
1179            }
1180
1181            // Control flow - structural matching
1182            (NodeKind::If { .. }, NodeKind::If { .. }) => true, // Structure checked recursively
1183            (NodeKind::While { .. }, NodeKind::While { .. }) => true,
1184            (NodeKind::For { .. }, NodeKind::For { .. }) => true,
1185            (NodeKind::Foreach { .. }, NodeKind::Foreach { .. }) => true,
1186
1187            // Subroutine definitions - name should match if present
1188            (NodeKind::Subroutine { name: n1, .. }, NodeKind::Subroutine { name: n2, .. }) => {
1189                n1 == n2
1190            }
1191
1192            // Package declarations - name should match
1193            (NodeKind::Package { name: n1, .. }, NodeKind::Package { name: n2, .. }) => n1 == n2,
1194
1195            // Use statements - module name should match
1196            (NodeKind::Use { module: m1, .. }, NodeKind::Use { module: m2, .. }) => m1 == m2,
1197
1198            // Same node types without specific content - consider structural match
1199            (kind1, kind2) => std::mem::discriminant(kind1) == std::mem::discriminant(kind2),
1200        }
1201    }
1202
1203    fn count_nodes(&self, node: &Node) -> usize {
1204        let mut count = 1;
1205
1206        match &node.kind {
1207            NodeKind::Program { statements } | NodeKind::Block { statements } => {
1208                for stmt in statements {
1209                    count += self.count_nodes(stmt);
1210                }
1211            }
1212            NodeKind::VariableDeclaration { variable, initializer, .. } => {
1213                count += self.count_nodes(variable);
1214                if let Some(init) = initializer {
1215                    count += self.count_nodes(init);
1216                }
1217            }
1218            NodeKind::Binary { left, right, .. } => {
1219                count += self.count_nodes(left);
1220                count += self.count_nodes(right);
1221            }
1222            NodeKind::Unary { operand, .. } => {
1223                count += self.count_nodes(operand);
1224            }
1225            NodeKind::FunctionCall { args, .. } => {
1226                for arg in args {
1227                    count += self.count_nodes(arg);
1228                }
1229            }
1230            NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
1231                count += self.count_nodes(condition);
1232                count += self.count_nodes(then_branch);
1233                for (cond, branch) in elsif_branches {
1234                    count += self.count_nodes(cond);
1235                    count += self.count_nodes(branch);
1236                }
1237                if let Some(branch) = else_branch {
1238                    count += self.count_nodes(branch);
1239                }
1240            }
1241            _ => {}
1242        }
1243
1244        count
1245    }
1246}
1247
1248impl Default for IncrementalParserV2 {
1249    fn default() -> Self {
1250        Self::new()
1251    }
1252}
1253
1254#[cfg(test)]
1255mod tests {
1256    use super::*;
1257    use perl_parser_core::position::Position;
1258    use std::time::Instant;
1259
1260    fn adaptive_perf_budget_micros(base_budget_micros: u128) -> u128 {
1261        let thread_count = std::env::var("RUST_TEST_THREADS")
1262            .ok()
1263            .and_then(|value| value.parse::<usize>().ok())
1264            .unwrap_or_else(|| {
1265                std::thread::available_parallelism().map_or(8, std::num::NonZeroUsize::get)
1266            });
1267
1268        let mut budget = base_budget_micros;
1269        if thread_count <= 2 {
1270            budget = budget.saturating_mul(2);
1271        } else if thread_count <= 4 {
1272            budget = budget.saturating_mul(3) / 2;
1273        }
1274
1275        if std::env::var("CI").is_ok() {
1276            budget = budget.saturating_mul(3) / 2;
1277        }
1278
1279        budget
1280    }
1281
1282    #[test]
1283    fn test_basic_compilation() {
1284        let parser = IncrementalParserV2::new();
1285        assert_eq!(parser.reused_nodes, 0);
1286        assert_eq!(parser.reparsed_nodes, 0);
1287    }
1288
1289    #[test]
1290    fn clone_with_shifted_positions_preserves_if_keyword_metadata()
1291    -> Result<(), Box<dyn std::error::Error>> {
1292        let parser = IncrementalParserV2::new();
1293        let loc = |start, end| perl_parser_core::ast::SourceLocation { start, end };
1294        let number =
1295            |start| Node::new(NodeKind::Number { value: "1".to_string() }, loc(start, start + 1));
1296        let block = |start, end| {
1297            Node::new(NodeKind::Block { statements: vec![number(start + 1)] }, loc(start, end))
1298        };
1299        let node = Node::new(
1300            NodeKind::If {
1301                condition: Box::new(number(1)),
1302                then_branch: Box::new(block(4, 10)),
1303                elsif_branches: vec![(Box::new(number(12)), Box::new(block(14, 20)))],
1304                else_branch: Some(Box::new(block(22, 28))),
1305                keyword: Some("unless".to_string()),
1306            },
1307            loc(0, 29),
1308        );
1309
1310        let shifted = parser.clone_with_shifted_positions(&node, 3);
1311
1312        assert_eq!(shifted.location.start, 3);
1313        match shifted.kind {
1314            NodeKind::If { keyword, else_branch, .. } => {
1315                assert_eq!(keyword.as_deref(), Some("unless"));
1316                assert!(else_branch.is_some());
1317            }
1318            other => {
1319                return Err(format!("expected If node, got {}", other.kind_name()).into());
1320            }
1321        }
1322        Ok(())
1323    }
1324
1325    #[test]
1326    fn test_performance_timing_detailed() -> ParseResult<()> {
1327        let mut parser = IncrementalParserV2::new();
1328
1329        // Initial parse with timing
1330        let source1 = "my $x = 42;";
1331        let start = Instant::now();
1332        let _tree1 = parser.parse(source1)?;
1333        let initial_parse_time = start.elapsed();
1334
1335        println!("Initial parse time: {:?}", initial_parse_time);
1336        println!("Initial nodes reparsed: {}", parser.reparsed_nodes);
1337
1338        // Apply incremental edit with detailed timing
1339        parser.edit(Edit::new(
1340            8,
1341            10,
1342            12, // "42" -> "4242"
1343            Position::new(8, 1, 9),
1344            Position::new(10, 1, 11),
1345            Position::new(12, 1, 13),
1346        ));
1347
1348        let source2 = "my $x = 4242;";
1349        let start = Instant::now();
1350        let _tree2 = parser.parse(source2)?;
1351        let incremental_parse_time = start.elapsed();
1352
1353        println!("Incremental parse time: {:?}", incremental_parse_time);
1354        println!(
1355            "Incremental nodes reused: {}, reparsed: {}",
1356            parser.reused_nodes, parser.reparsed_nodes
1357        );
1358
1359        // Performance assertions - sub-5ms to avoid flaky CI on loaded runners
1360        assert!(
1361            incremental_parse_time.as_micros() < 5000,
1362            "Incremental parse time should be <5ms, got {:?}",
1363            incremental_parse_time
1364        );
1365
1366        // Verify efficiency - should reuse most nodes
1367        assert!(parser.reused_nodes >= 3, "Should reuse at least 3 nodes");
1368        assert_eq!(parser.reparsed_nodes, 1, "Should only reparse the changed Number node");
1369
1370        // Performance ratio check - for very small examples, overhead may exceed benefits
1371        let speedup =
1372            initial_parse_time.as_nanos() as f64 / incremental_parse_time.as_nanos() as f64;
1373        println!("Performance improvement: {:.2}x faster", speedup);
1374
1375        // For micro-benchmarks, we focus on correctness and reasonable performance rather than speedup
1376        // The real benefits show up with larger documents where node reuse matters more
1377        if speedup >= 1.5 {
1378            println!("✅ Good speedup achieved: {:.2}x", speedup);
1379        } else {
1380            println!("⚠️ Limited speedup for micro-benchmark (expected for tiny examples)");
1381        }
1382
1383        Ok(())
1384    }
1385
1386    #[test]
1387    fn test_incremental_value_change() -> ParseResult<()> {
1388        let mut parser = IncrementalParserV2::new();
1389
1390        // Initial parse with timing
1391        let source1 = "my $x = 42;";
1392        let start = Instant::now();
1393        let _tree1 = parser.parse(source1)?;
1394        let initial_time = start.elapsed();
1395
1396        // Initial parse counts all nodes: Program + VarDecl + Variable + Number = 4
1397        // But semicolon is not counted as a separate node
1398        assert_eq!(parser.reparsed_nodes, 4); // Program, VarDecl, Variable, Number
1399        println!(
1400            "Initial parse: {}µs, {} nodes parsed",
1401            initial_time.as_micros(),
1402            parser.reparsed_nodes
1403        );
1404
1405        // Change the number value
1406        parser.edit(Edit::new(
1407            8,
1408            10,
1409            12, // "42" -> "4242"
1410            Position::new(8, 1, 9),
1411            Position::new(10, 1, 11),
1412            Position::new(12, 1, 13),
1413        ));
1414
1415        let source2 = "my $x = 4242;";
1416        let start = Instant::now();
1417        let tree2 = parser.parse(source2)?;
1418        let incremental_time = start.elapsed();
1419
1420        println!(
1421            "Incremental parse: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1422            incremental_time.as_micros(),
1423            parser.reused_nodes,
1424            parser.reparsed_nodes
1425        );
1426        assert_eq!(parser.reused_nodes, 3); // Program, VarDecl, Variable can be reused
1427        assert_eq!(parser.reparsed_nodes, 1); // Only Number needs reparsing
1428
1429        // Performance validation
1430        assert!(incremental_time.as_micros() < 500, "Incremental update should be <500µs");
1431        let efficiency =
1432            parser.reused_nodes as f64 / (parser.reused_nodes + parser.reparsed_nodes) as f64;
1433        assert!(
1434            efficiency >= 0.75,
1435            "Node reuse efficiency should be ≥75%, got {:.1}%",
1436            efficiency * 100.0
1437        );
1438
1439        // Verify the tree is correct
1440        if let NodeKind::Program { statements } = &tree2.kind {
1441            if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1442                &statements[0].kind
1443            {
1444                if let NodeKind::Number { value } = &init.kind {
1445                    assert_eq!(value, "4242");
1446                }
1447            }
1448        }
1449
1450        Ok(())
1451    }
1452
1453    #[test]
1454    fn test_multiple_value_changes() -> ParseResult<()> {
1455        let mut parser = IncrementalParserV2::new();
1456
1457        // Initial parse with timing
1458        let source1 = "my $x = 10;\nmy $y = 20;";
1459        let start = Instant::now();
1460        parser.parse(source1)?;
1461        let initial_time = start.elapsed();
1462        let initial_nodes = parser.reparsed_nodes;
1463
1464        println!(
1465            "Initial parse (multi-statement): {}µs, {} nodes",
1466            initial_time.as_micros(),
1467            initial_nodes
1468        );
1469
1470        // Change both values
1471        parser.edit(Edit::new(
1472            8,
1473            10,
1474            11, // "10" -> "100"
1475            Position::new(8, 1, 9),
1476            Position::new(10, 1, 11),
1477            Position::new(11, 1, 12),
1478        ));
1479
1480        parser.edit(Edit::new(
1481            21,
1482            23,
1483            24, // "20" -> "200" (adjusted for previous edit)
1484            Position::new(21, 2, 9),
1485            Position::new(23, 2, 11),
1486            Position::new(24, 2, 12),
1487        ));
1488
1489        let source2 = "my $x = 100;\nmy $y = 200;";
1490        let start = Instant::now();
1491        let tree = parser.parse(source2)?;
1492        let incremental_time = start.elapsed();
1493
1494        println!(
1495            "Multiple edits: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1496            incremental_time.as_micros(),
1497            parser.reused_nodes,
1498            parser.reparsed_nodes
1499        );
1500        // Advanced reuse system can reuse more nodes than expected
1501        // The actual counts may be higher due to improved efficiency
1502        assert!(
1503            parser.reused_nodes >= 5,
1504            "Should reuse at least 5 nodes, got {}",
1505            parser.reused_nodes
1506        );
1507        assert!(
1508            parser.reparsed_nodes >= 1,
1509            "Should reparse at least 1 node, got {}",
1510            parser.reparsed_nodes
1511        );
1512
1513        // Performance validation for multiple edits — relaxed for CI runners
1514        assert!(incremental_time.as_micros() < 5000, "Multiple edits should be <5ms");
1515        let total_nodes = parser.reused_nodes + parser.reparsed_nodes;
1516        let reuse_ratio = parser.reused_nodes as f64 / total_nodes as f64;
1517        assert!(
1518            reuse_ratio >= 0.7,
1519            "Multi-edit reuse ratio should be ≥70%, got {:.1}%",
1520            reuse_ratio * 100.0
1521        );
1522
1523        // Verify both values were updated correctly
1524        if let NodeKind::Program { statements } = &tree.kind {
1525            if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1526                &statements[0].kind
1527            {
1528                if let NodeKind::Number { value } = &init.kind {
1529                    assert_eq!(value, "100");
1530                }
1531            }
1532            if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1533                &statements[1].kind
1534            {
1535                if let NodeKind::Number { value } = &init.kind {
1536                    assert_eq!(value, "200");
1537                }
1538            }
1539        }
1540
1541        Ok(())
1542    }
1543
1544    #[test]
1545    fn test_too_many_edits_fallback() -> ParseResult<()> {
1546        let mut parser = IncrementalParserV2::new();
1547
1548        // Initial parse
1549        let source1 = "my $x = 1;";
1550        parser.parse(source1)?;
1551
1552        // Add too many edits (> 10)
1553        for i in 0..15 {
1554            parser.edit(Edit::new(
1555                8 + i,
1556                9 + i,
1557                10 + i,
1558                Position::new(8 + i, 1, (9 + i) as u32),
1559                Position::new(9 + i, 1, (10 + i) as u32),
1560                Position::new(10 + i, 1, (11 + i) as u32),
1561            ));
1562        }
1563
1564        let source2 = "my $x = 123456789012345;";
1565        let tree = parser.parse(source2)?;
1566
1567        // Advanced reuse system may still achieve some reuse even with too many edits
1568        // The system now uses sophisticated analysis rather than simple fallbacks
1569        assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1570        // Note: reused_nodes may be > 0 due to advanced reuse algorithms
1571
1572        // Tree should still be correct
1573        if let NodeKind::Program { statements } = &tree.kind {
1574            assert_eq!(statements.len(), 1);
1575        }
1576
1577        Ok(())
1578    }
1579
1580    #[test]
1581    fn test_invalid_edit_bounds() -> ParseResult<()> {
1582        let mut parser = IncrementalParserV2::new();
1583
1584        // Initial parse
1585        let source1 = "my $x = 42;";
1586        parser.parse(source1)?;
1587
1588        // Edit that goes beyond the node bounds (should fall back to full parse)
1589        parser.edit(Edit::new(
1590            8,
1591            12, // Beyond the number literal
1592            13,
1593            Position::new(8, 1, 9),
1594            Position::new(12, 1, 13),
1595            Position::new(13, 1, 14),
1596        ));
1597
1598        let source2 = "my $x = 123;";
1599        let tree = parser.parse(source2)?;
1600
1601        // Advanced reuse system may still achieve some reuse even with invalid bounds
1602        // The system is now more resilient and may not always fall back completely
1603        assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1604        // Note: reused_nodes may be > 0 due to advanced reuse algorithms
1605
1606        // Tree should still be correct
1607        if let NodeKind::Program { statements } = &tree.kind {
1608            if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1609                &statements[0].kind
1610            {
1611                if let NodeKind::Number { value } = &init.kind {
1612                    assert_eq!(value, "123");
1613                }
1614            }
1615        }
1616
1617        Ok(())
1618    }
1619
1620    #[test]
1621    fn test_string_edit() -> ParseResult<()> {
1622        let mut parser = IncrementalParserV2::new();
1623
1624        // Initial parse
1625        let source1 = "my $name = \"hello\";";
1626        parser.parse(source1)?;
1627
1628        // Change string content
1629        parser.edit(Edit::new(
1630            12,
1631            17, // "hello" -> "world"
1632            17,
1633            Position::new(12, 1, 13),
1634            Position::new(17, 1, 18),
1635            Position::new(17, 1, 18),
1636        ));
1637
1638        let source2 = "my $name = \"world\";";
1639        let tree = parser.parse(source2)?;
1640
1641        // Should reuse most of the tree
1642        println!(
1643            "DEBUG test_string_edit: reused_nodes = {}, reparsed_nodes = {}",
1644            parser.reused_nodes, parser.reparsed_nodes
1645        );
1646        assert_eq!(parser.reused_nodes, 3); // Program, VarDecl, Variable
1647        assert_eq!(parser.reparsed_nodes, 1); // Only String
1648
1649        // Verify the string was updated
1650        if let NodeKind::Program { statements } = &tree.kind {
1651            if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1652                &statements[0].kind
1653            {
1654                if let NodeKind::String { value, .. } = &init.kind {
1655                    assert_eq!(value, "\"world\"");
1656                }
1657            }
1658        }
1659
1660        Ok(())
1661    }
1662
1663    #[test]
1664    fn test_empty_source_handling() -> ParseResult<()> {
1665        let mut parser = IncrementalParserV2::new();
1666
1667        // Initial parse with valid source
1668        let source1 = "my $x = 42;";
1669        let start = Instant::now();
1670        parser.parse(source1)?;
1671        let initial_time = start.elapsed();
1672        println!("Initial parse time: {}µs", initial_time.as_micros());
1673
1674        // Add an edit
1675        parser.edit(Edit::new(
1676            8,
1677            10,
1678            11,
1679            Position::new(8, 1, 9),
1680            Position::new(10, 1, 11),
1681            Position::new(11, 1, 12),
1682        ));
1683
1684        // Try to parse empty source (should fall back to full parse)
1685        let source2 = "";
1686        let start = Instant::now();
1687        let result = parser.parse(source2);
1688        let parse_time = start.elapsed();
1689
1690        println!("Empty source parse time: {}µs", parse_time.as_micros());
1691
1692        // Should handle gracefully and either succeed or fail cleanly
1693        match result {
1694            Ok(_) => {
1695                // If it succeeds, should be a full parse
1696                assert_eq!(parser.reused_nodes, 0);
1697                println!("Empty source parsing succeeded with fallback");
1698            }
1699            Err(_) => {
1700                // If it fails, that's also acceptable for empty source
1701                assert_eq!(parser.reused_nodes, 0);
1702                println!("Empty source parsing failed gracefully (expected)");
1703            }
1704        }
1705
1706        // Performance should still be reasonable even for empty source handling
1707        assert!(parse_time.as_millis() < 100, "Empty source handling should be fast");
1708
1709        Ok(())
1710    }
1711
1712    #[test]
1713    fn test_complex_nested_structure_edits() -> ParseResult<()> {
1714        let mut parser = IncrementalParserV2::new();
1715
1716        // Complex nested Perl structure
1717        let source1 = r#"
1718if ($condition) {
1719    my $nested = {
1720        key1 => "value1",
1721        key2 => 42,
1722        key3 => [1, 2, 3]
1723    };
1724    process($nested);
1725}
1726"#;
1727
1728        let start = Instant::now();
1729        parser.parse(source1)?;
1730        let initial_time = start.elapsed();
1731        let initial_nodes = parser.reparsed_nodes;
1732
1733        println!(
1734            "Complex structure initial parse: {}µs, {} nodes",
1735            initial_time.as_micros(),
1736            initial_nodes
1737        );
1738
1739        // Edit nested value - should be challenging for incremental parser
1740        let value_start =
1741            source1.find("42").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1742        parser.edit(Edit::new(
1743            value_start,
1744            value_start + 2,
1745            value_start + 4, // "42" -> "9999"
1746            Position::new(value_start, 1, 1),
1747            Position::new(value_start + 2, 1, 3),
1748            Position::new(value_start + 4, 1, 5),
1749        ));
1750
1751        let source2 = source1.replace("42", "9999");
1752        let start = Instant::now();
1753        let _tree = parser.parse(&source2)?;
1754        let incremental_time = start.elapsed();
1755
1756        println!(
1757            "Complex nested edit: {}µs, reused={}, reparsed={}",
1758            incremental_time.as_micros(),
1759            parser.reused_nodes,
1760            parser.reparsed_nodes
1761        );
1762
1763        // Even with complex nesting, should have reasonable performance
1764        assert!(incremental_time.as_millis() < 10, "Complex nested edit should be <10ms");
1765
1766        // Should still achieve some node reuse
1767        if parser.reused_nodes > 0 {
1768            println!("Successfully reused {} nodes in complex structure", parser.reused_nodes);
1769        } else {
1770            println!("Complex structure caused full reparse (acceptable for edge cases)");
1771        }
1772
1773        Ok(())
1774    }
1775
1776    #[test]
1777    fn test_large_document_performance() -> ParseResult<()> {
1778        let mut parser = IncrementalParserV2::new();
1779
1780        // Generate a larger Perl document
1781        let mut large_source = String::new();
1782        for i in 0..100 {
1783            large_source.push_str(&format!("my $var{} = {};\n", i, i * 10));
1784        }
1785
1786        let start = Instant::now();
1787        parser.parse(&large_source)?;
1788        let initial_time = start.elapsed();
1789        let initial_nodes = parser.reparsed_nodes;
1790
1791        println!(
1792            "Large document initial parse: {}ms, {} nodes",
1793            initial_time.as_millis(),
1794            initial_nodes
1795        );
1796
1797        // Edit in the middle of the document
1798        let edit_pos = large_source
1799            .find("my $var50 = 500")
1800            .ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?
1801            + 13;
1802        parser.edit(Edit::new(
1803            edit_pos,
1804            edit_pos + 3, // "500" -> "999"
1805            edit_pos + 3,
1806            Position::new(edit_pos, 1, 1),
1807            Position::new(edit_pos + 3, 1, 4),
1808            Position::new(edit_pos + 3, 1, 4),
1809        ));
1810
1811        let source2 = large_source.replace("500", "999");
1812        let start = Instant::now();
1813        let _tree = parser.parse(&source2)?;
1814        let incremental_time = start.elapsed();
1815
1816        println!(
1817            "Large document incremental: {}ms, reused={}, reparsed={}",
1818            incremental_time.as_millis(),
1819            parser.reused_nodes,
1820            parser.reparsed_nodes
1821        );
1822
1823        // Large document performance targets
1824        assert!(incremental_time.as_millis() < 50, "Large document incremental should be <50ms");
1825
1826        // Should achieve significant node reuse on large documents
1827        if parser.reused_nodes > 0 {
1828            let reuse_percentage = parser.reused_nodes as f64
1829                / (parser.reused_nodes + parser.reparsed_nodes) as f64
1830                * 100.0;
1831            println!("Large document reuse rate: {:.1}%", reuse_percentage);
1832            assert!(reuse_percentage > 50.0, "Large document should reuse >50% of nodes");
1833        }
1834
1835        Ok(())
1836    }
1837
1838    #[test]
1839    fn test_unicode_heavy_incremental_parsing() -> ParseResult<()> {
1840        let mut parser = IncrementalParserV2::new();
1841
1842        // Unicode-heavy source with emojis and international characters
1843        let source1 = "my $🌟variable = '你好世界'; # Comment with emoji 🚀\nmy $café = 'résumé';";
1844
1845        let start = Instant::now();
1846        parser.parse(source1)?;
1847        let initial_time = start.elapsed();
1848
1849        println!("Unicode document initial parse: {}µs", initial_time.as_micros());
1850
1851        // Edit the unicode string content
1852        let edit_start =
1853            source1.find("你好世界").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1854        let edit_end = edit_start + "你好世界".len();
1855        parser.edit(Edit::new(
1856            edit_start,
1857            edit_end,
1858            edit_start + "再见".len(), // "你好世界" -> "再见" (hello world -> goodbye)
1859            Position::new(edit_start, 1, 1),
1860            Position::new(edit_end, 1, 2),
1861            Position::new(edit_start + "再见".len(), 1, 2),
1862        ));
1863
1864        let source2 = source1.replace("你好世界", "再见");
1865        let start = Instant::now();
1866        let _tree = parser.parse(&source2)?;
1867        let incremental_time = start.elapsed();
1868
1869        println!(
1870            "Unicode incremental edit: {}µs, reused={}, reparsed={}",
1871            incremental_time.as_micros(),
1872            parser.reused_nodes,
1873            parser.reparsed_nodes
1874        );
1875
1876        // Unicode handling should not significantly impact performance.
1877        let unicode_budget_micros = adaptive_perf_budget_micros(5_000);
1878        assert!(
1879            incremental_time.as_micros() < unicode_budget_micros,
1880            "Unicode incremental parsing should be <{}µs (got {}µs)",
1881            unicode_budget_micros,
1882            incremental_time.as_micros()
1883        );
1884        assert!(parser.reused_nodes > 0 || parser.reparsed_nodes > 0, "Should parse successfully");
1885
1886        Ok(())
1887    }
1888
1889    #[test]
1890    fn test_edit_near_ast_node_boundaries() -> ParseResult<()> {
1891        let mut parser = IncrementalParserV2::new();
1892
1893        // Source with clear AST node boundaries
1894        let source1 = "sub func { my $x = 123; return $x * 2; }";
1895
1896        parser.parse(source1)?;
1897
1898        // Edit right at the boundary between number and semicolon
1899        let number_end =
1900            source1.find("123").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)? + 3;
1901        parser.edit(Edit::new(
1902            number_end - 1, // Edit last digit of number
1903            number_end,
1904            number_end + 1, // "3" -> "456"
1905            Position::new(number_end - 1, 1, 1),
1906            Position::new(number_end, 1, 2),
1907            Position::new(number_end + 1, 1, 3),
1908        ));
1909
1910        let source2 = source1.replace("123", "12456");
1911        let start = Instant::now();
1912        let _tree = parser.parse(&source2)?;
1913        let boundary_edit_time = start.elapsed();
1914
1915        println!(
1916            "Boundary edit time: {}µs, reused={}, reparsed={}",
1917            boundary_edit_time.as_micros(),
1918            parser.reused_nodes,
1919            parser.reparsed_nodes
1920        );
1921
1922        // Boundary edits are tricky but should still be efficient.
1923        let boundary_budget_micros = adaptive_perf_budget_micros(5_000);
1924        assert!(
1925            boundary_edit_time.as_micros() < boundary_budget_micros,
1926            "AST boundary edit should be <{}µs (got {}µs)",
1927            boundary_budget_micros,
1928            boundary_edit_time.as_micros()
1929        );
1930        assert!(parser.reparsed_nodes >= 1, "Should reparse at least the modified node");
1931
1932        Ok(())
1933    }
1934
1935    #[test]
1936    fn test_whitespace_insertion_reuses_tree() -> ParseResult<()> {
1937        let mut parser = IncrementalParserV2::new();
1938        let source1 = "my $x = 42;";
1939        parser.parse(source1)?;
1940
1941        parser.edit(Edit::new(
1942            5,
1943            5,
1944            7,
1945            Position::new(5, 1, 6),
1946            Position::new(5, 1, 6),
1947            Position::new(7, 1, 8),
1948        ));
1949
1950        let source2 = "my $x  = 42;";
1951        parser.parse(source2)?;
1952
1953        assert!(parser.reused_nodes > 0, "Whitespace insertion should reuse prior tree");
1954        assert!(parser.reparsed_nodes <= 1, "Whitespace insertion should avoid broad reparsing");
1955        Ok(())
1956    }
1957
1958    #[test]
1959    fn test_performance_regression_detection() -> ParseResult<()> {
1960        let mut parser = IncrementalParserV2::new();
1961
1962        // Baseline performance measurement
1963        let source = "my $baseline = 42; my $test = 'hello';";
1964        let mut parse_times = Vec::new();
1965
1966        // Multiple runs for statistical significance
1967        for i in 0..10 {
1968            let start = Instant::now();
1969            parser.parse(source)?;
1970            let time = start.elapsed();
1971            parse_times.push(time.as_micros());
1972
1973            // Edit for next iteration
1974            parser.edit(Edit::new(
1975                15,
1976                17,
1977                19, // Edit position
1978                Position::new(15, 1, 16),
1979                Position::new(17, 1, 18),
1980                Position::new(19, 1, 20),
1981            ));
1982
1983            // Alternate source for variations
1984            let test_source = if i % 2 == 0 {
1985                "my $baseline = 99; my $test = 'hello';"
1986            } else {
1987                "my $baseline = 42; my $test = 'hello';"
1988            };
1989
1990            let start = Instant::now();
1991            parser.parse(test_source)?;
1992            let incremental_time = start.elapsed();
1993
1994            println!(
1995                "Run {}: initial={}µs, incremental={}µs, reused={}, reparsed={}",
1996                i + 1,
1997                time.as_micros(),
1998                incremental_time.as_micros(),
1999                parser.reused_nodes,
2000                parser.reparsed_nodes
2001            );
2002
2003            // Performance regression detection
2004            assert!(
2005                incremental_time.as_millis() < 10,
2006                "Run {} performance regression detected: {}ms",
2007                i + 1,
2008                incremental_time.as_millis()
2009            );
2010        }
2011
2012        // Statistical analysis
2013        let avg_time = parse_times.iter().sum::<u128>() / parse_times.len() as u128;
2014        let max_time =
2015            *parse_times.iter().max().ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
2016        let min_time =
2017            *parse_times.iter().min().ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
2018
2019        println!(
2020            "Performance statistics: avg={}µs, min={}µs, max={}µs",
2021            avg_time, min_time, max_time
2022        );
2023
2024        let variation_factor = max_time as f64 / avg_time as f64;
2025        assert!(
2026            variation_factor <= 10.0,
2027            "Extreme performance inconsistency detected: max={}µs, avg={}µs ({}x variation)",
2028            max_time,
2029            avg_time,
2030            variation_factor
2031        );
2032        if variation_factor > 5.0 {
2033            println!(
2034                "⚠️ High performance variation detected: max={}µs, avg={}µs ({}x variation) - may indicate system load",
2035                max_time, avg_time, variation_factor
2036            );
2037        }
2038
2039        Ok(())
2040    }
2041
2042    /// Test whitespace before operator reuses tree
2043    #[test]
2044    fn test_whitespace_before_operator_reuses_tree() -> ParseResult<()> {
2045        let mut parser = IncrementalParserV2::new();
2046        let source1 = "my $x = 42;";
2047        parser.parse(source1)?;
2048        parser.edit(Edit::new(
2049            6,
2050            6,
2051            9,
2052            Position::new(6, 1, 7),
2053            Position::new(6, 1, 7),
2054            Position::new(9, 1, 10),
2055        ));
2056        let source2 = "my $x   = 42;";
2057        parser.parse(source2)?;
2058        assert!(parser.reused_nodes > 0);
2059        Ok(())
2060    }
2061
2062    #[test]
2063    fn test_comment_insertion_reuses_tree() -> ParseResult<()> {
2064        let mut parser = IncrementalParserV2::new();
2065        let source1 = "my $x = 42;";
2066        parser.parse(source1)?;
2067        parser.edit(Edit::new(
2068            10,
2069            11,
2070            24,
2071            Position::new(10, 1, 11),
2072            Position::new(11, 1, 12),
2073            Position::new(24, 1, 25),
2074        ));
2075        let source2 = "my $x = 42; # comment";
2076        parser.parse(source2)?;
2077        assert!(parser.reused_nodes > 0);
2078        Ok(())
2079    }
2080
2081    #[test]
2082    fn test_trailing_whitespace_reuses_tree() -> ParseResult<()> {
2083        let mut parser = IncrementalParserV2::new();
2084        let source1 = "my $x = 42;";
2085        parser.parse(source1)?;
2086        parser.edit(Edit::new(
2087            11,
2088            11,
2089            16,
2090            Position::new(11, 1, 12),
2091            Position::new(11, 1, 12),
2092            Position::new(16, 1, 17),
2093        ));
2094        let source2 = "my $x = 42;     ";
2095        parser.parse(source2)?;
2096        assert!(parser.reused_nodes > 0);
2097        Ok(())
2098    }
2099
2100    #[test]
2101    fn test_newline_insertion_reuses_tree() -> ParseResult<()> {
2102        let mut parser = IncrementalParserV2::new();
2103        let source1 = "my $x = 42;";
2104        parser.parse(source1)?;
2105        parser.edit(Edit::new(
2106            11,
2107            11,
2108            12,
2109            Position::new(11, 1, 12),
2110            Position::new(11, 1, 12),
2111            Position::new(12, 2, 1),
2112        ));
2113        let source2 = "my $x = 42;\n";
2114        parser.parse(source2)?;
2115        assert!(parser.reused_nodes > 0);
2116        Ok(())
2117    }
2118
2119    #[test]
2120    fn test_whitespace_deletion_reuses_tree() -> ParseResult<()> {
2121        let mut parser = IncrementalParserV2::new();
2122        let source1 = "my  $x  =  42;";
2123        parser.parse(source1)?;
2124        parser.edit(Edit::new(
2125            3,
2126            5,
2127            4,
2128            Position::new(3, 1, 4),
2129            Position::new(5, 1, 6),
2130            Position::new(4, 1, 5),
2131        ));
2132        let source2 = "my $x  =  42;";
2133        parser.parse(source2)?;
2134        assert!(parser.reused_nodes > 0);
2135        Ok(())
2136    }
2137
2138    #[test]
2139    fn test_whitespace_at_statement_boundary() -> ParseResult<()> {
2140        let mut parser = IncrementalParserV2::new();
2141        let source1 = "print 'hello';my $x = 42;";
2142        parser.parse(source1)?;
2143        parser.edit(Edit::new(
2144            14,
2145            14,
2146            15,
2147            Position::new(14, 1, 15),
2148            Position::new(14, 1, 15),
2149            Position::new(15, 1, 16),
2150        ));
2151        let source2 = "print 'hello'; my $x = 42;";
2152        parser.parse(source2)?;
2153        assert!(parser.reused_nodes > 0);
2154        Ok(())
2155    }
2156
2157    #[test]
2158    fn test_whitespace_edit_checks_both_old_and_new() -> ParseResult<()> {
2159        let mut parser = IncrementalParserV2::new();
2160        let source1 = "my $x = 42;";
2161        parser.parse(source1)?;
2162        parser.edit(Edit::new(
2163            6,
2164            7,
2165            8,
2166            Position::new(6, 1, 7),
2167            Position::new(7, 1, 8),
2168            Position::new(8, 1, 9),
2169        ));
2170        let source2 = "my $x=  42;";
2171        parser.parse(source2)?;
2172        assert!(parser.reused_nodes > 0);
2173        Ok(())
2174    }
2175}