1use crate::{
57 ast::{Node, NodeKind, SourceLocation},
58 edit::{Edit, EditSet},
59 error::ParseResult,
60 incremental_advanced_reuse::{AdvancedReuseAnalyzer, ReuseAnalysisResult, ReuseConfig},
61 parser::Parser,
62 position::Range,
63};
64use std::collections::HashMap;
65
66#[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 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn efficiency_percentage(&self) -> f64 {
86 if self.nodes_reused + self.nodes_reparsed == 0 {
87 return 0.0;
88 }
89 self.nodes_reused as f64 / (self.nodes_reused + self.nodes_reparsed) as f64 * 100.0
90 }
91
92 pub fn is_sub_millisecond(&self) -> bool {
93 self.parse_time_micros < 1000
94 }
95
96 pub fn performance_category(&self) -> &'static str {
97 match self.parse_time_micros {
98 0..=100 => "Excellent (<100µs)",
99 101..=500 => "Very Good (<500µs)",
100 501..=1000 => "Good (<1ms)",
101 1001..=5000 => "Acceptable (<5ms)",
102 _ => "Needs Optimization (>5ms)",
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
113pub struct IncrementalTree {
114 pub root: Node,
115 pub source: String,
116 node_map: HashMap<usize, Vec<Node>>,
118}
119
120impl IncrementalTree {
121 pub fn new(root: Node, source: String) -> Self {
123 let mut tree = IncrementalTree { root, source, node_map: HashMap::new() };
124 tree.build_node_map();
125 tree
126 }
127
128 fn build_node_map(&mut self) {
130 self.node_map.clear();
131 self.map_node(&self.root.clone());
132 }
133
134 fn map_node(&mut self, node: &Node) {
135 self.node_map.entry(node.location.start).or_default().push(node.clone());
137
138 match &node.kind {
140 NodeKind::Program { statements } | NodeKind::Block { statements } => {
141 for stmt in statements {
142 self.map_node(stmt);
143 }
144 }
145 NodeKind::VariableDeclaration { variable, initializer, .. } => {
146 self.map_node(variable);
147 if let Some(init) = initializer {
148 self.map_node(init);
149 }
150 }
151 NodeKind::Binary { left, right, .. } => {
152 self.map_node(left);
153 self.map_node(right);
154 }
155 NodeKind::Unary { operand, .. } => {
156 self.map_node(operand);
157 }
158 NodeKind::FunctionCall { args, .. } => {
159 for arg in args {
160 self.map_node(arg);
161 }
162 }
163 NodeKind::If { condition, then_branch, elsif_branches, else_branch } => {
164 self.map_node(condition);
165 self.map_node(then_branch);
166 for (cond, branch) in elsif_branches {
167 self.map_node(cond);
168 self.map_node(branch);
169 }
170 if let Some(branch) = else_branch {
171 self.map_node(branch);
172 }
173 }
174 _ => {}
175 }
176 }
177
178 pub fn find_containing_node(&self, start: usize, end: usize) -> Option<&Node> {
180 let mut smallest: Option<&Node> = None;
181 let mut smallest_size = usize::MAX;
182
183 for nodes in self.node_map.values() {
185 for node in nodes {
186 if node.location.start <= start && node.location.end >= end {
187 let size = node.location.end - node.location.start;
188 if size < smallest_size {
189 smallest = Some(node);
190 smallest_size = size;
191 }
192 }
193 }
194 }
195
196 smallest
197 }
198}
199
200pub struct IncrementalParserV2 {
211 last_tree: Option<IncrementalTree>,
212 pending_edits: EditSet,
213 pub reused_nodes: usize,
214 pub reparsed_nodes: usize,
215 pub metrics: IncrementalMetrics,
216 reuse_analyzer: AdvancedReuseAnalyzer,
218 reuse_config: ReuseConfig,
220 pub last_reuse_analysis: Option<ReuseAnalysisResult>,
222}
223
224impl IncrementalParserV2 {
225 pub fn new() -> Self {
226 IncrementalParserV2 {
227 last_tree: None,
228 pending_edits: EditSet::new(),
229 reused_nodes: 0,
230 reparsed_nodes: 0,
231 metrics: IncrementalMetrics::new(),
232 reuse_analyzer: AdvancedReuseAnalyzer::new(),
233 reuse_config: ReuseConfig::default(),
234 last_reuse_analysis: None,
235 }
236 }
237
238 pub fn with_reuse_config(config: ReuseConfig) -> Self {
240 IncrementalParserV2 {
241 last_tree: None,
242 pending_edits: EditSet::new(),
243 reused_nodes: 0,
244 reparsed_nodes: 0,
245 metrics: IncrementalMetrics::new(),
246 reuse_analyzer: AdvancedReuseAnalyzer::with_config(config.clone()),
247 reuse_config: config,
248 last_reuse_analysis: None,
249 }
250 }
251
252 pub fn edit(&mut self, edit: Edit) {
253 self.pending_edits.add(edit);
254 }
255
256 pub fn parse(&mut self, source: &str) -> ParseResult<Node> {
257 self.reused_nodes = 0;
259 self.reparsed_nodes = 0;
260
261 if let Some(ref last_tree) = self.last_tree {
263 if !self.pending_edits.is_empty() {
264 let last_tree_clone = last_tree.clone();
265 if let Some(new_tree) = self.try_incremental_parse(source, &last_tree_clone) {
267 self.last_tree =
268 Some(IncrementalTree::new(new_tree.clone(), source.to_string()));
269 self.pending_edits = EditSet::new();
270 return Ok(new_tree);
271 }
272 }
273 }
274
275 self.full_parse(source)
277 }
278
279 fn full_parse(&mut self, source: &str) -> ParseResult<Node> {
280 let mut parser = Parser::new(source);
281 let root = parser.parse()?;
282
283 if self.last_tree.is_none() {
285 self.reused_nodes = 0;
287 self.reparsed_nodes = self.count_nodes(&root);
288 } else {
289 let should_skip_reuse = source.is_empty()
292 || self.pending_edits.len() > 10
293 || self.last_tree.as_ref().is_none_or(|tree| !self.is_simple_value_edit(tree));
294
295 if should_skip_reuse {
296 self.reused_nodes = 0;
298 self.reparsed_nodes = self.count_nodes(&root);
299 } else if let Some(ref old_tree) = self.last_tree {
300 let (reused, reparsed) = self.analyze_reuse(&old_tree.root, &root);
302 self.reused_nodes = reused;
303 self.reparsed_nodes = reparsed;
304 } else {
305 self.reused_nodes = 0;
307 self.reparsed_nodes = self.count_nodes(&root);
308 }
309 }
310
311 self.last_tree = Some(IncrementalTree::new(root.clone(), source.to_string()));
312 self.pending_edits = EditSet::new();
313
314 Ok(root)
315 }
316
317 fn try_incremental_parse(&mut self, source: &str, last_tree: &IncrementalTree) -> Option<Node> {
318 if let Some(advanced_result) = self.try_advanced_reuse_parse(source, last_tree) {
320 return Some(advanced_result);
321 }
322
323 let is_simple = self.is_simple_value_edit(last_tree);
325 if is_simple {
326 return self.incremental_parse_simple(source, last_tree);
327 }
328
329 if self.is_whitespace_or_comment_edit(last_tree) {
331 return self.incremental_parse_whitespace(source, last_tree);
332 }
333
334 None
336 }
337
338 fn try_advanced_reuse_parse(
340 &mut self,
341 source: &str,
342 last_tree: &IncrementalTree,
343 ) -> Option<Node> {
344 let mut parser = Parser::new(source);
346 let new_tree = match parser.parse() {
347 Ok(tree) => tree,
348 Err(_) => {
349 return None;
350 }
351 };
352
353 let analysis_result = self.reuse_analyzer.analyze_reuse_opportunities(
355 &last_tree.root,
356 &new_tree,
357 &self.pending_edits,
358 &self.reuse_config,
359 );
360
361 self.last_reuse_analysis = Some(analysis_result);
363
364 if let Some(ref analysis) = self.last_reuse_analysis {
366 if analysis.meets_efficiency_target(self.reuse_config.min_confidence * 100.0) {
367 self.reused_nodes = analysis.reused_nodes;
369 self.reparsed_nodes = analysis.total_new_nodes - analysis.reused_nodes;
370
371 return Some(new_tree);
373 }
374 }
375
376 None
377 }
378
379 fn is_simple_value_edit(&self, tree: &IncrementalTree) -> bool {
380 if self.pending_edits.len() > 10 {
382 return false;
383 }
384
385 let mut cumulative_shift: isize = 0;
388
389 for edit in self.pending_edits.edits() {
390 let original_start = (edit.start_byte as isize - cumulative_shift) as usize;
391 let original_end = (edit.old_end_byte as isize - cumulative_shift) as usize;
392
393 let affected_node = tree.find_containing_node(original_start, original_end);
394
395 match affected_node {
396 Some(node) => {
397 match &node.kind {
398 NodeKind::Number { .. } | NodeKind::String { .. } => {
400 if original_start >= node.location.start
402 && original_end <= node.location.end
403 {
404 cumulative_shift += edit.byte_shift();
405 continue;
406 } else {
407 return false;
408 }
409 }
410 NodeKind::Variable { .. } => {
412 if original_start >= node.location.start
413 && original_end <= node.location.end
414 {
415 cumulative_shift += edit.byte_shift();
416 continue;
417 } else {
418 return false;
419 }
420 }
421 NodeKind::Identifier { .. } => {
423 if original_start >= node.location.start
424 && original_end <= node.location.end
425 {
426 cumulative_shift += edit.byte_shift();
427 continue;
428 } else {
429 return false;
430 }
431 }
432 _ => {
433 return false; }
435 }
436 }
437 None => {
438 return false; }
440 }
441 }
442
443 true
444 }
445
446 fn is_whitespace_or_comment_edit(&self, tree: &IncrementalTree) -> bool {
448 for edit in self.pending_edits.edits() {
449 let start = edit.start_byte;
452 let end = edit.old_end_byte;
453
454 if !self.is_in_non_structural_content(tree, start, end) {
456 return false;
457 }
458 }
459 true
460 }
461
462 fn is_in_non_structural_content(
467 &self,
468 tree: &IncrementalTree,
469 start: usize,
470 end: usize,
471 ) -> bool {
472 use perl_lexer::{PerlLexer, TokenType};
473
474 if start >= end || end > tree.source.len() {
476 return false;
477 }
478
479 let affected_text = &tree.source[start..end];
481
482 let mut lexer = PerlLexer::new(affected_text);
484
485 loop {
487 match lexer.next_token() {
488 Some(token) => {
489 match token.token_type {
490 TokenType::Whitespace | TokenType::Newline | TokenType::Comment(_) => {
492 }
494 TokenType::EOF => {
495 return true;
497 }
498 _ => {
499 return false;
501 }
502 }
503 }
504 None => {
505 return true;
507 }
508 }
509 }
510 }
511
512 fn incremental_parse_whitespace(
514 &mut self,
515 _source: &str,
516 last_tree: &IncrementalTree,
517 ) -> Option<Node> {
518 let shift = self.calculate_total_shift();
521 Some(self.clone_with_shifted_positions(&last_tree.root, shift))
522 }
523
524 fn calculate_total_shift(&self) -> isize {
526 self.pending_edits.edits().iter().map(|edit| edit.byte_shift()).sum()
527 }
528
529 fn incremental_parse_simple(
530 &mut self,
531 source: &str,
532 last_tree: &IncrementalTree,
533 ) -> Option<Node> {
534 if source.is_empty() {
536 return None;
537 }
538
539 let new_root = self.clone_and_update_node(&last_tree.root, source, &last_tree.source);
541
542 if !self.validate_incremental_result(&new_root, source) {
544 return None;
545 }
546
547 self.count_reuse_potential(&last_tree.root, &new_root);
550
551 Some(new_root)
552 }
553
554 fn validate_incremental_result(&self, node: &Node, source: &str) -> bool {
558 if source.is_empty() {
560 return match &node.kind {
562 NodeKind::Program { statements } => statements.is_empty(),
563 _ => false,
564 };
565 }
566
567 if node.location.start > source.len() || node.location.end > source.len() {
569 return false;
570 }
571
572 if node.location.start > node.location.end {
573 return false;
574 }
575
576 if !source.is_char_boundary(node.location.start)
578 || !source.is_char_boundary(node.location.end)
579 {
580 return false;
581 }
582
583 if node.location.start < node.location.end {
585 let node_text = &source[node.location.start..node.location.end];
586
587 match &node.kind {
589 NodeKind::Number { value } => {
590 if value.trim() != node_text.trim() {
592 return false;
593 }
594 if value.parse::<f64>().is_err() && value.parse::<i64>().is_err() {
596 return false;
597 }
598 }
599 NodeKind::String { value, .. } => {
600 if !node_text.is_empty()
602 && !value.contains(node_text.trim_matches(|c| c == '"' || c == '\''))
603 {
604 }
606 }
607 NodeKind::Variable { name, .. } => {
608 if !node_text.contains(name) {
610 return false;
611 }
612 }
613 NodeKind::Identifier { name } => {
614 if name.trim() != node_text.trim() {
616 return false;
617 }
618 }
619 _ => {
620 }
623 }
624 }
625
626 self.validate_node_tree_consistency(node, source, 0, 3)
628 }
629
630 fn validate_node_tree_consistency(
632 &self,
633 node: &Node,
634 source: &str,
635 depth: usize,
636 max_depth: usize,
637 ) -> bool {
638 if depth > max_depth {
639 return true; }
641
642 match &node.kind {
643 NodeKind::Program { statements } | NodeKind::Block { statements } => {
644 for stmt in statements {
646 if stmt.location.start < node.location.start
647 || stmt.location.end > node.location.end
648 {
649 return false;
650 }
651 if !self.validate_node_tree_consistency(stmt, source, depth + 1, max_depth) {
652 return false;
653 }
654 }
655 }
656 NodeKind::VariableDeclaration { variable, initializer, .. } => {
657 if !self.validate_node_tree_consistency(variable, source, depth + 1, max_depth) {
658 return false;
659 }
660 if let Some(init) = initializer {
661 if !self.validate_node_tree_consistency(init, source, depth + 1, max_depth) {
662 return false;
663 }
664 }
665 }
666 NodeKind::Binary { left, right, .. } => {
667 if !self.validate_node_tree_consistency(left, source, depth + 1, max_depth)
668 || !self.validate_node_tree_consistency(right, source, depth + 1, max_depth)
669 {
670 return false;
671 }
672 }
673 _ => {
674 }
676 }
677
678 true
679 }
680
681 fn clone_and_update_node(&self, node: &Node, new_source: &str, old_source: &str) -> Node {
682 let shift = self.calculate_shift_at(node.location.start);
684
685 let affected = self.is_node_affected(node);
687
688 match &node.kind {
690 NodeKind::Program { statements } => {
691 let new_statements: Vec<Node> = statements
693 .iter()
694 .map(|stmt| self.clone_and_update_node(stmt, new_source, old_source))
695 .collect();
696
697 let new_start = (node.location.start as isize + shift) as usize;
698 let new_end = (node.location.end as isize
699 + shift
700 + self.calculate_content_delta(node)) as usize;
701
702 return Node::new(
703 NodeKind::Program { statements: new_statements },
704 SourceLocation { start: new_start, end: new_end },
705 );
706 }
707 NodeKind::VariableDeclaration { declarator, variable, initializer, attributes } => {
708 let new_variable = self.clone_and_update_node(variable, new_source, old_source);
710 let new_initializer = initializer
711 .as_ref()
712 .map(|init| self.clone_and_update_node(init, new_source, old_source));
713
714 let new_start = (node.location.start as isize + shift) as usize;
715 let new_end = (node.location.end as isize
716 + shift
717 + self.calculate_content_delta(node)) as usize;
718
719 return Node::new(
720 NodeKind::VariableDeclaration {
721 declarator: declarator.clone(),
722 variable: Box::new(new_variable),
723 initializer: new_initializer.map(Box::new),
724 attributes: attributes.clone(),
725 },
726 SourceLocation { start: new_start, end: new_end },
727 );
728 }
729 _ => {}
730 }
731
732 if affected {
733 match &node.kind {
735 NodeKind::Number { .. } => {
737 let new_start = (node.location.start as isize + shift) as usize;
739 let new_end =
740 (node.location.end as isize + shift + self.calculate_content_delta(node))
741 as usize;
742
743 if new_start < new_source.len() && new_end <= new_source.len() {
744 let new_value = &new_source[new_start..new_end];
745
746 return Node::new(
747 NodeKind::Number { value: new_value.to_string() },
748 SourceLocation { start: new_start, end: new_end },
749 );
750 }
751 }
752 NodeKind::String { interpolated, .. } => {
753 let new_start = (node.location.start as isize + shift) as usize;
754 let new_end =
755 (node.location.end as isize + shift + self.calculate_content_delta(node))
756 as usize;
757
758 if new_start < new_source.len() && new_end <= new_source.len() {
759 let new_value = &new_source[new_start..new_end];
760
761 return Node::new(
762 NodeKind::String {
763 value: new_value.to_string(),
764 interpolated: *interpolated,
765 },
766 SourceLocation { start: new_start, end: new_end },
767 );
768 }
769 }
770 NodeKind::Program { statements } => {
772 let new_statements = statements
773 .iter()
774 .map(|stmt| self.clone_and_update_node(stmt, new_source, old_source))
775 .collect();
776 let new_location = SourceLocation {
777 start: (node.location.start as isize + shift) as usize,
778 end: (node.location.end as isize + shift) as usize,
779 };
780 return Node::new(
781 NodeKind::Program { statements: new_statements },
782 new_location,
783 );
784 }
785 NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
786 let new_variable =
787 Box::new(self.clone_and_update_node(variable, new_source, old_source));
788 let new_initializer = initializer.as_ref().map(|init| {
789 Box::new(self.clone_and_update_node(init, new_source, old_source))
790 });
791 let new_location = SourceLocation {
792 start: (node.location.start as isize + shift) as usize,
793 end: (node.location.end as isize + shift) as usize,
794 };
795 return Node::new(
796 NodeKind::VariableDeclaration {
797 declarator: declarator.clone(),
798 variable: new_variable,
799 attributes: attributes.clone(),
800 initializer: new_initializer,
801 },
802 new_location,
803 );
804 }
805 _ => {}
806 }
807 }
808
809 self.clone_with_shifted_positions(node, shift)
811 }
812
813 fn calculate_shift_at(&self, position: usize) -> isize {
818 let mut shift = 0;
819 for edit in self.pending_edits.edits() {
820 let original_old_end = (edit.old_end_byte as isize - shift) as usize;
821
822 if original_old_end <= position {
823 let edit_shift = edit.byte_shift();
824 shift += edit_shift;
825 } else {
826 break;
827 }
828 }
829
830 shift
831 }
832
833 #[allow(dead_code)]
838 fn ensure_unicode_boundary(&self, source: &str, position: usize) -> usize {
839 if position >= source.len() {
840 return source.len();
841 }
842
843 if source.is_char_boundary(position) {
844 return position;
845 }
846
847 for i in (0..=position).rev() {
849 if i < source.len() && source.is_char_boundary(i) {
850 return i;
851 }
852 }
853
854 0
856 }
857
858 #[allow(dead_code)]
863 fn calculate_unicode_safe_position(
864 &self,
865 original_pos: usize,
866 shift: isize,
867 source: &str,
868 ) -> usize {
869 let new_pos = if shift >= 0 {
870 original_pos.saturating_add(shift as usize)
871 } else {
872 original_pos.saturating_sub((-shift) as usize)
873 };
874
875 self.ensure_unicode_boundary(source, new_pos)
876 }
877
878 pub fn get_metrics(&self) -> &IncrementalMetrics {
880 &self.metrics
881 }
882
883 pub fn reset_metrics(&mut self) {
885 self.metrics = IncrementalMetrics::new();
886 }
887
888 pub fn get_last_reuse_analysis(&self) -> Option<&ReuseAnalysisResult> {
890 self.last_reuse_analysis.as_ref()
891 }
892
893 pub fn set_reuse_config(&mut self, config: ReuseConfig) {
895 self.reuse_config = config.clone();
896 self.reuse_analyzer = AdvancedReuseAnalyzer::with_config(config);
897 }
898
899 pub fn used_advanced_reuse(&self) -> bool {
901 self.last_reuse_analysis.as_ref().is_some_and(|analysis| analysis.reuse_percentage > 0.0)
902 }
903
904 pub fn get_reuse_efficiency_report(&self) -> String {
906 if let Some(analysis) = &self.last_reuse_analysis {
907 format!(
908 "Advanced Reuse Analysis:\n Efficiency: {:.1}%\n Nodes reused: {}\n Total nodes: {}\n {}",
909 analysis.reuse_percentage,
910 analysis.reused_nodes,
911 analysis.total_old_nodes,
912 analysis.performance_summary()
913 )
914 } else {
915 format!(
916 "Basic Incremental Analysis:\n Efficiency: {:.1}%\n Nodes reused: {}\n Nodes reparsed: {}",
917 self.reused_nodes as f64 / (self.reused_nodes + self.reparsed_nodes) as f64 * 100.0,
918 self.reused_nodes,
919 self.reparsed_nodes
920 )
921 }
922 }
923
924 fn calculate_content_delta(&self, node: &Node) -> isize {
925 let mut delta = 0;
928 let mut shift = 0;
929 for edit in self.pending_edits.edits() {
930 let start = (edit.start_byte as isize - shift) as usize;
931 let end = (edit.old_end_byte as isize - shift) as usize;
932 if start >= node.location.start && end <= node.location.end {
933 delta += edit.byte_shift();
934 }
935 shift += edit.byte_shift();
936 }
937 delta
938 }
939
940 fn is_node_affected(&self, node: &Node) -> bool {
941 let node_range = Range::from(node.location);
942 self.pending_edits.affects_range(&node_range)
943 }
944
945 fn clone_with_shifted_positions(&self, node: &Node, shift: isize) -> Node {
946 let new_start = if shift >= 0 {
948 node.location.start.saturating_add(shift as usize)
949 } else {
950 node.location.start.saturating_sub((-shift) as usize)
951 };
952
953 let new_end = if shift >= 0 {
954 node.location.end.saturating_add(shift as usize)
955 } else {
956 node.location.end.saturating_sub((-shift) as usize)
957 };
958
959 let new_location = SourceLocation { start: new_start, end: new_end };
960
961 let new_kind = match &node.kind {
962 NodeKind::Program { statements } => NodeKind::Program {
963 statements: statements
964 .iter()
965 .map(|s| self.clone_with_shifted_positions(s, shift))
966 .collect(),
967 },
968 NodeKind::Block { statements } => NodeKind::Block {
969 statements: statements
970 .iter()
971 .map(|s| self.clone_with_shifted_positions(s, shift))
972 .collect(),
973 },
974 NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
975 NodeKind::VariableDeclaration {
976 declarator: declarator.clone(),
977 variable: Box::new(self.clone_with_shifted_positions(variable, shift)),
978 attributes: attributes.clone(),
979 initializer: initializer
980 .as_ref()
981 .map(|i| Box::new(self.clone_with_shifted_positions(i, shift))),
982 }
983 }
984 NodeKind::Binary { op, left, right } => NodeKind::Binary {
985 op: op.clone(),
986 left: Box::new(self.clone_with_shifted_positions(left, shift)),
987 right: Box::new(self.clone_with_shifted_positions(right, shift)),
988 },
989 NodeKind::Unary { op, operand } => NodeKind::Unary {
990 op: op.clone(),
991 operand: Box::new(self.clone_with_shifted_positions(operand, shift)),
992 },
993 NodeKind::FunctionCall { name, args } => NodeKind::FunctionCall {
994 name: name.clone(),
995 args: args.iter().map(|a| self.clone_with_shifted_positions(a, shift)).collect(),
996 },
997 NodeKind::If { condition, then_branch, elsif_branches, else_branch } => NodeKind::If {
998 condition: Box::new(self.clone_with_shifted_positions(condition, shift)),
999 then_branch: Box::new(self.clone_with_shifted_positions(then_branch, shift)),
1000 elsif_branches: elsif_branches
1001 .iter()
1002 .map(|(c, b)| {
1003 (
1004 self.clone_with_shifted_positions(c, shift),
1005 self.clone_with_shifted_positions(b, shift),
1006 )
1007 })
1008 .map(|(c, b)| (Box::new(c), Box::new(b)))
1009 .collect(),
1010 else_branch: else_branch
1011 .as_ref()
1012 .map(|b| Box::new(self.clone_with_shifted_positions(b, shift))),
1013 },
1014 _ => node.kind.clone(), };
1016
1017 Node::new(new_kind, new_location)
1018 }
1019
1020 fn count_reuse_potential(&mut self, old_root: &Node, new_root: &Node) {
1021 let (reused, reparsed) = self.analyze_reuse(old_root, new_root);
1023 self.reused_nodes = reused;
1024 self.reparsed_nodes = reparsed;
1025 }
1026
1027 fn analyze_reuse(&self, old_node: &Node, new_node: &Node) -> (usize, usize) {
1028 match (&old_node.kind, &new_node.kind) {
1030 (
1031 NodeKind::Program { statements: old_stmts },
1032 NodeKind::Program { statements: new_stmts },
1033 ) => {
1034 let mut reused = 1; let mut reparsed = 0;
1036
1037 for (old_stmt, new_stmt) in old_stmts.iter().zip(new_stmts.iter()) {
1038 let (r, p) = self.analyze_reuse(old_stmt, new_stmt);
1039 reused += r;
1040 reparsed += p;
1041 }
1042
1043 (reused, reparsed)
1044 }
1045 (
1046 NodeKind::VariableDeclaration { variable: old_var, initializer: old_init, .. },
1047 NodeKind::VariableDeclaration { variable: new_var, initializer: new_init, .. },
1048 ) => {
1049 let mut reused = 1; let mut reparsed = 0;
1051
1052 let (r, p) = self.analyze_reuse(old_var, new_var);
1053 reused += r;
1054 reparsed += p;
1055
1056 if let (Some(old_i), Some(new_i)) = (old_init, new_init) {
1057 let (r, p) = self.analyze_reuse(old_i, new_i);
1058 reused += r;
1059 reparsed += p;
1060 }
1061
1062 (reused, reparsed)
1063 }
1064 (NodeKind::Number { value: old_val }, NodeKind::Number { value: new_val }) => {
1065 if old_val != new_val {
1066 (0, 1) } else {
1068 (1, 0) }
1070 }
1071 (
1072 NodeKind::Variable { sigil: old_s, name: old_n },
1073 NodeKind::Variable { sigil: new_s, name: new_n },
1074 ) => {
1075 if old_s == new_s && old_n == new_n {
1076 (1, 0) } else {
1078 (0, 1) }
1080 }
1081 _ => {
1082 if self.nodes_match(old_node, new_node) {
1083 (1, 0)
1084 } else {
1085 (0, 1)
1086 }
1087 }
1088 }
1089 }
1090
1091 fn nodes_match(&self, node1: &Node, node2: &Node) -> bool {
1096 match (&node1.kind, &node2.kind) {
1097 (NodeKind::Number { value: v1 }, NodeKind::Number { value: v2 }) => v1 == v2,
1099 (
1100 NodeKind::String { value: v1, interpolated: i1 },
1101 NodeKind::String { value: v2, interpolated: i2 },
1102 ) => v1 == v2 && i1 == i2,
1103
1104 (
1106 NodeKind::Variable { sigil: s1, name: n1 },
1107 NodeKind::Variable { sigil: s2, name: n2 },
1108 ) => s1 == s2 && n1 == n2,
1109
1110 (NodeKind::Identifier { name: n1 }, NodeKind::Identifier { name: n2 }) => n1 == n2,
1112
1113 (NodeKind::Binary { op: op1, .. }, NodeKind::Binary { op: op2, .. }) => op1 == op2,
1115
1116 (NodeKind::Unary { op: op1, .. }, NodeKind::Unary { op: op2, .. }) => op1 == op2,
1118
1119 (
1121 NodeKind::FunctionCall { name: n1, args: args1 },
1122 NodeKind::FunctionCall { name: n2, args: args2 },
1123 ) => n1 == n2 && args1.len() == args2.len(),
1124
1125 (
1127 NodeKind::VariableDeclaration { declarator: d1, .. },
1128 NodeKind::VariableDeclaration { declarator: d2, .. },
1129 ) => d1 == d2,
1130
1131 (NodeKind::ArrayLiteral { elements: e1 }, NodeKind::ArrayLiteral { elements: e2 }) => {
1133 e1.len() == e2.len()
1134 }
1135
1136 (NodeKind::HashLiteral { pairs: p1 }, NodeKind::HashLiteral { pairs: p2 }) => {
1138 p1.len() == p2.len()
1139 }
1140
1141 (NodeKind::Block { statements: s1 }, NodeKind::Block { statements: s2 }) => {
1143 s1.len() == s2.len()
1144 }
1145
1146 (NodeKind::Program { statements: s1 }, NodeKind::Program { statements: s2 }) => {
1148 s1.len() == s2.len()
1149 }
1150
1151 (NodeKind::If { .. }, NodeKind::If { .. }) => true, (NodeKind::While { .. }, NodeKind::While { .. }) => true,
1154 (NodeKind::For { .. }, NodeKind::For { .. }) => true,
1155 (NodeKind::Foreach { .. }, NodeKind::Foreach { .. }) => true,
1156
1157 (NodeKind::Subroutine { name: n1, .. }, NodeKind::Subroutine { name: n2, .. }) => {
1159 n1 == n2
1160 }
1161
1162 (NodeKind::Package { name: n1, .. }, NodeKind::Package { name: n2, .. }) => n1 == n2,
1164
1165 (NodeKind::Use { module: m1, .. }, NodeKind::Use { module: m2, .. }) => m1 == m2,
1167
1168 (kind1, kind2) => std::mem::discriminant(kind1) == std::mem::discriminant(kind2),
1170 }
1171 }
1172
1173 fn count_nodes(&self, node: &Node) -> usize {
1174 let mut count = 1;
1175
1176 match &node.kind {
1177 NodeKind::Program { statements } | NodeKind::Block { statements } => {
1178 for stmt in statements {
1179 count += self.count_nodes(stmt);
1180 }
1181 }
1182 NodeKind::VariableDeclaration { variable, initializer, .. } => {
1183 count += self.count_nodes(variable);
1184 if let Some(init) = initializer {
1185 count += self.count_nodes(init);
1186 }
1187 }
1188 NodeKind::Binary { left, right, .. } => {
1189 count += self.count_nodes(left);
1190 count += self.count_nodes(right);
1191 }
1192 NodeKind::Unary { operand, .. } => {
1193 count += self.count_nodes(operand);
1194 }
1195 NodeKind::FunctionCall { args, .. } => {
1196 for arg in args {
1197 count += self.count_nodes(arg);
1198 }
1199 }
1200 NodeKind::If { condition, then_branch, elsif_branches, else_branch } => {
1201 count += self.count_nodes(condition);
1202 count += self.count_nodes(then_branch);
1203 for (cond, branch) in elsif_branches {
1204 count += self.count_nodes(cond);
1205 count += self.count_nodes(branch);
1206 }
1207 if let Some(branch) = else_branch {
1208 count += self.count_nodes(branch);
1209 }
1210 }
1211 _ => {}
1212 }
1213
1214 count
1215 }
1216}
1217
1218impl Default for IncrementalParserV2 {
1219 fn default() -> Self {
1220 Self::new()
1221 }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226 use super::*;
1227 use crate::position::Position;
1228 use std::time::Instant;
1229
1230 fn adaptive_perf_budget_micros(base_budget_micros: u128) -> u128 {
1231 let thread_count = std::env::var("RUST_TEST_THREADS")
1232 .ok()
1233 .and_then(|value| value.parse::<usize>().ok())
1234 .unwrap_or_else(|| {
1235 std::thread::available_parallelism().map_or(8, std::num::NonZeroUsize::get)
1236 });
1237
1238 let mut budget = base_budget_micros;
1239 if thread_count <= 2 {
1240 budget = budget.saturating_mul(2);
1241 } else if thread_count <= 4 {
1242 budget = budget.saturating_mul(3) / 2;
1243 }
1244
1245 if std::env::var("CI").is_ok() {
1246 budget = budget.saturating_mul(3) / 2;
1247 }
1248
1249 budget
1250 }
1251
1252 #[test]
1253 fn test_basic_compilation() {
1254 let parser = IncrementalParserV2::new();
1255 assert_eq!(parser.reused_nodes, 0);
1256 assert_eq!(parser.reparsed_nodes, 0);
1257 }
1258
1259 #[test]
1260 fn test_performance_timing_detailed() -> ParseResult<()> {
1261 let mut parser = IncrementalParserV2::new();
1262
1263 let source1 = "my $x = 42;";
1265 let start = Instant::now();
1266 let _tree1 = parser.parse(source1)?;
1267 let initial_parse_time = start.elapsed();
1268
1269 println!("Initial parse time: {:?}", initial_parse_time);
1270 println!("Initial nodes reparsed: {}", parser.reparsed_nodes);
1271
1272 parser.edit(Edit::new(
1274 8,
1275 10,
1276 12, Position::new(8, 1, 9),
1278 Position::new(10, 1, 11),
1279 Position::new(12, 1, 13),
1280 ));
1281
1282 let source2 = "my $x = 4242;";
1283 let start = Instant::now();
1284 let _tree2 = parser.parse(source2)?;
1285 let incremental_parse_time = start.elapsed();
1286
1287 println!("Incremental parse time: {:?}", incremental_parse_time);
1288 println!(
1289 "Incremental nodes reused: {}, reparsed: {}",
1290 parser.reused_nodes, parser.reparsed_nodes
1291 );
1292
1293 assert!(
1295 incremental_parse_time.as_micros() < 5000,
1296 "Incremental parse time should be <5ms, got {:?}",
1297 incremental_parse_time
1298 );
1299
1300 assert!(parser.reused_nodes >= 3, "Should reuse at least 3 nodes");
1302 assert_eq!(parser.reparsed_nodes, 1, "Should only reparse the changed Number node");
1303
1304 let speedup =
1306 initial_parse_time.as_nanos() as f64 / incremental_parse_time.as_nanos() as f64;
1307 println!("Performance improvement: {:.2}x faster", speedup);
1308
1309 if speedup >= 1.5 {
1312 println!("✅ Good speedup achieved: {:.2}x", speedup);
1313 } else {
1314 println!("⚠️ Limited speedup for micro-benchmark (expected for tiny examples)");
1315 }
1316
1317 Ok(())
1318 }
1319
1320 #[test]
1321 fn test_incremental_value_change() -> ParseResult<()> {
1322 let mut parser = IncrementalParserV2::new();
1323
1324 let source1 = "my $x = 42;";
1326 let start = Instant::now();
1327 let _tree1 = parser.parse(source1)?;
1328 let initial_time = start.elapsed();
1329
1330 assert_eq!(parser.reparsed_nodes, 4); println!(
1334 "Initial parse: {}µs, {} nodes parsed",
1335 initial_time.as_micros(),
1336 parser.reparsed_nodes
1337 );
1338
1339 parser.edit(Edit::new(
1341 8,
1342 10,
1343 12, Position::new(8, 1, 9),
1345 Position::new(10, 1, 11),
1346 Position::new(12, 1, 13),
1347 ));
1348
1349 let source2 = "my $x = 4242;";
1350 let start = Instant::now();
1351 let tree2 = parser.parse(source2)?;
1352 let incremental_time = start.elapsed();
1353
1354 println!(
1355 "Incremental parse: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1356 incremental_time.as_micros(),
1357 parser.reused_nodes,
1358 parser.reparsed_nodes
1359 );
1360 assert_eq!(parser.reused_nodes, 3); assert_eq!(parser.reparsed_nodes, 1); assert!(incremental_time.as_micros() < 500, "Incremental update should be <500µs");
1365 let efficiency =
1366 parser.reused_nodes as f64 / (parser.reused_nodes + parser.reparsed_nodes) as f64;
1367 assert!(
1368 efficiency >= 0.75,
1369 "Node reuse efficiency should be ≥75%, got {:.1}%",
1370 efficiency * 100.0
1371 );
1372
1373 if let NodeKind::Program { statements } = &tree2.kind {
1375 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1376 &statements[0].kind
1377 {
1378 if let NodeKind::Number { value } = &init.kind {
1379 assert_eq!(value, "4242");
1380 }
1381 }
1382 }
1383
1384 Ok(())
1385 }
1386
1387 #[test]
1388 fn test_multiple_value_changes() -> ParseResult<()> {
1389 let mut parser = IncrementalParserV2::new();
1390
1391 let source1 = "my $x = 10;\nmy $y = 20;";
1393 let start = Instant::now();
1394 parser.parse(source1)?;
1395 let initial_time = start.elapsed();
1396 let initial_nodes = parser.reparsed_nodes;
1397
1398 println!(
1399 "Initial parse (multi-statement): {}µs, {} nodes",
1400 initial_time.as_micros(),
1401 initial_nodes
1402 );
1403
1404 parser.edit(Edit::new(
1406 8,
1407 10,
1408 11, Position::new(8, 1, 9),
1410 Position::new(10, 1, 11),
1411 Position::new(11, 1, 12),
1412 ));
1413
1414 parser.edit(Edit::new(
1415 21,
1416 23,
1417 24, Position::new(21, 2, 9),
1419 Position::new(23, 2, 11),
1420 Position::new(24, 2, 12),
1421 ));
1422
1423 let source2 = "my $x = 100;\nmy $y = 200;";
1424 let start = Instant::now();
1425 let tree = parser.parse(source2)?;
1426 let incremental_time = start.elapsed();
1427
1428 println!(
1429 "Multiple edits: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1430 incremental_time.as_micros(),
1431 parser.reused_nodes,
1432 parser.reparsed_nodes
1433 );
1434 assert!(
1437 parser.reused_nodes >= 5,
1438 "Should reuse at least 5 nodes, got {}",
1439 parser.reused_nodes
1440 );
1441 assert!(
1442 parser.reparsed_nodes >= 1,
1443 "Should reparse at least 1 node, got {}",
1444 parser.reparsed_nodes
1445 );
1446
1447 assert!(incremental_time.as_micros() < 5000, "Multiple edits should be <5ms");
1449 let total_nodes = parser.reused_nodes + parser.reparsed_nodes;
1450 let reuse_ratio = parser.reused_nodes as f64 / total_nodes as f64;
1451 assert!(
1452 reuse_ratio >= 0.7,
1453 "Multi-edit reuse ratio should be ≥70%, got {:.1}%",
1454 reuse_ratio * 100.0
1455 );
1456
1457 if let NodeKind::Program { statements } = &tree.kind {
1459 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1460 &statements[0].kind
1461 {
1462 if let NodeKind::Number { value } = &init.kind {
1463 assert_eq!(value, "100");
1464 }
1465 }
1466 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1467 &statements[1].kind
1468 {
1469 if let NodeKind::Number { value } = &init.kind {
1470 assert_eq!(value, "200");
1471 }
1472 }
1473 }
1474
1475 Ok(())
1476 }
1477
1478 #[test]
1479 fn test_too_many_edits_fallback() -> ParseResult<()> {
1480 let mut parser = IncrementalParserV2::new();
1481
1482 let source1 = "my $x = 1;";
1484 parser.parse(source1)?;
1485
1486 for i in 0..15 {
1488 parser.edit(Edit::new(
1489 8 + i,
1490 9 + i,
1491 10 + i,
1492 Position::new(8 + i, 1, (9 + i) as u32),
1493 Position::new(9 + i, 1, (10 + i) as u32),
1494 Position::new(10 + i, 1, (11 + i) as u32),
1495 ));
1496 }
1497
1498 let source2 = "my $x = 123456789012345;";
1499 let tree = parser.parse(source2)?;
1500
1501 assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1504 if let NodeKind::Program { statements } = &tree.kind {
1508 assert_eq!(statements.len(), 1);
1509 }
1510
1511 Ok(())
1512 }
1513
1514 #[test]
1515 fn test_invalid_edit_bounds() -> ParseResult<()> {
1516 let mut parser = IncrementalParserV2::new();
1517
1518 let source1 = "my $x = 42;";
1520 parser.parse(source1)?;
1521
1522 parser.edit(Edit::new(
1524 8,
1525 12, 13,
1527 Position::new(8, 1, 9),
1528 Position::new(12, 1, 13),
1529 Position::new(13, 1, 14),
1530 ));
1531
1532 let source2 = "my $x = 123;";
1533 let tree = parser.parse(source2)?;
1534
1535 assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1538 if let NodeKind::Program { statements } = &tree.kind {
1542 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1543 &statements[0].kind
1544 {
1545 if let NodeKind::Number { value } = &init.kind {
1546 assert_eq!(value, "123");
1547 }
1548 }
1549 }
1550
1551 Ok(())
1552 }
1553
1554 #[test]
1555 fn test_string_edit() -> ParseResult<()> {
1556 let mut parser = IncrementalParserV2::new();
1557
1558 let source1 = "my $name = \"hello\";";
1560 parser.parse(source1)?;
1561
1562 parser.edit(Edit::new(
1564 12,
1565 17, 17,
1567 Position::new(12, 1, 13),
1568 Position::new(17, 1, 18),
1569 Position::new(17, 1, 18),
1570 ));
1571
1572 let source2 = "my $name = \"world\";";
1573 let tree = parser.parse(source2)?;
1574
1575 println!(
1577 "DEBUG test_string_edit: reused_nodes = {}, reparsed_nodes = {}",
1578 parser.reused_nodes, parser.reparsed_nodes
1579 );
1580 assert_eq!(parser.reused_nodes, 3); assert_eq!(parser.reparsed_nodes, 1); if let NodeKind::Program { statements } = &tree.kind {
1585 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1586 &statements[0].kind
1587 {
1588 if let NodeKind::String { value, .. } = &init.kind {
1589 assert_eq!(value, "\"world\"");
1590 }
1591 }
1592 }
1593
1594 Ok(())
1595 }
1596
1597 #[test]
1598 fn test_empty_source_handling() -> ParseResult<()> {
1599 let mut parser = IncrementalParserV2::new();
1600
1601 let source1 = "my $x = 42;";
1603 let start = Instant::now();
1604 parser.parse(source1)?;
1605 let initial_time = start.elapsed();
1606 println!("Initial parse time: {}µs", initial_time.as_micros());
1607
1608 parser.edit(Edit::new(
1610 8,
1611 10,
1612 11,
1613 Position::new(8, 1, 9),
1614 Position::new(10, 1, 11),
1615 Position::new(11, 1, 12),
1616 ));
1617
1618 let source2 = "";
1620 let start = Instant::now();
1621 let result = parser.parse(source2);
1622 let parse_time = start.elapsed();
1623
1624 println!("Empty source parse time: {}µs", parse_time.as_micros());
1625
1626 match result {
1628 Ok(_) => {
1629 assert_eq!(parser.reused_nodes, 0);
1631 println!("Empty source parsing succeeded with fallback");
1632 }
1633 Err(_) => {
1634 assert_eq!(parser.reused_nodes, 0);
1636 println!("Empty source parsing failed gracefully (expected)");
1637 }
1638 }
1639
1640 assert!(parse_time.as_millis() < 100, "Empty source handling should be fast");
1642
1643 Ok(())
1644 }
1645
1646 #[test]
1647 fn test_complex_nested_structure_edits() -> ParseResult<()> {
1648 let mut parser = IncrementalParserV2::new();
1649
1650 let source1 = r#"
1652if ($condition) {
1653 my $nested = {
1654 key1 => "value1",
1655 key2 => 42,
1656 key3 => [1, 2, 3]
1657 };
1658 process($nested);
1659}
1660"#;
1661
1662 let start = Instant::now();
1663 parser.parse(source1)?;
1664 let initial_time = start.elapsed();
1665 let initial_nodes = parser.reparsed_nodes;
1666
1667 println!(
1668 "Complex structure initial parse: {}µs, {} nodes",
1669 initial_time.as_micros(),
1670 initial_nodes
1671 );
1672
1673 let value_start = source1.find("42").ok_or(crate::error::ParseError::UnexpectedEof)?;
1675 parser.edit(Edit::new(
1676 value_start,
1677 value_start + 2,
1678 value_start + 4, Position::new(value_start, 1, 1),
1680 Position::new(value_start + 2, 1, 3),
1681 Position::new(value_start + 4, 1, 5),
1682 ));
1683
1684 let source2 = source1.replace("42", "9999");
1685 let start = Instant::now();
1686 let _tree = parser.parse(&source2)?;
1687 let incremental_time = start.elapsed();
1688
1689 println!(
1690 "Complex nested edit: {}µs, reused={}, reparsed={}",
1691 incremental_time.as_micros(),
1692 parser.reused_nodes,
1693 parser.reparsed_nodes
1694 );
1695
1696 assert!(incremental_time.as_millis() < 10, "Complex nested edit should be <10ms");
1698
1699 if parser.reused_nodes > 0 {
1701 println!("Successfully reused {} nodes in complex structure", parser.reused_nodes);
1702 } else {
1703 println!("Complex structure caused full reparse (acceptable for edge cases)");
1704 }
1705
1706 Ok(())
1707 }
1708
1709 #[test]
1710 fn test_large_document_performance() -> ParseResult<()> {
1711 let mut parser = IncrementalParserV2::new();
1712
1713 let mut large_source = String::new();
1715 for i in 0..100 {
1716 large_source.push_str(&format!("my $var{} = {};\n", i, i * 10));
1717 }
1718
1719 let start = Instant::now();
1720 parser.parse(&large_source)?;
1721 let initial_time = start.elapsed();
1722 let initial_nodes = parser.reparsed_nodes;
1723
1724 println!(
1725 "Large document initial parse: {}ms, {} nodes",
1726 initial_time.as_millis(),
1727 initial_nodes
1728 );
1729
1730 let edit_pos =
1732 large_source.find("my $var50 = 500").ok_or(crate::error::ParseError::UnexpectedEof)?
1733 + 13;
1734 parser.edit(Edit::new(
1735 edit_pos,
1736 edit_pos + 3, edit_pos + 3,
1738 Position::new(edit_pos, 1, 1),
1739 Position::new(edit_pos + 3, 1, 4),
1740 Position::new(edit_pos + 3, 1, 4),
1741 ));
1742
1743 let source2 = large_source.replace("500", "999");
1744 let start = Instant::now();
1745 let _tree = parser.parse(&source2)?;
1746 let incremental_time = start.elapsed();
1747
1748 println!(
1749 "Large document incremental: {}ms, reused={}, reparsed={}",
1750 incremental_time.as_millis(),
1751 parser.reused_nodes,
1752 parser.reparsed_nodes
1753 );
1754
1755 assert!(incremental_time.as_millis() < 50, "Large document incremental should be <50ms");
1757
1758 if parser.reused_nodes > 0 {
1760 let reuse_percentage = parser.reused_nodes as f64
1761 / (parser.reused_nodes + parser.reparsed_nodes) as f64
1762 * 100.0;
1763 println!("Large document reuse rate: {:.1}%", reuse_percentage);
1764 assert!(reuse_percentage > 50.0, "Large document should reuse >50% of nodes");
1765 }
1766
1767 Ok(())
1768 }
1769
1770 #[test]
1771 fn test_unicode_heavy_incremental_parsing() -> ParseResult<()> {
1772 let mut parser = IncrementalParserV2::new();
1773
1774 let source1 = "my $🌟variable = '你好世界'; # Comment with emoji 🚀\nmy $café = 'résumé';";
1776
1777 let start = Instant::now();
1778 parser.parse(source1)?;
1779 let initial_time = start.elapsed();
1780
1781 println!("Unicode document initial parse: {}µs", initial_time.as_micros());
1782
1783 let edit_start = source1.find("你好世界").ok_or(crate::error::ParseError::UnexpectedEof)?;
1785 let edit_end = edit_start + "你好世界".len();
1786 parser.edit(Edit::new(
1787 edit_start,
1788 edit_end,
1789 edit_start + "再见".len(), Position::new(edit_start, 1, 1),
1791 Position::new(edit_end, 1, 2),
1792 Position::new(edit_start + "再见".len(), 1, 2),
1793 ));
1794
1795 let source2 = source1.replace("你好世界", "再见");
1796 let start = Instant::now();
1797 let _tree = parser.parse(&source2)?;
1798 let incremental_time = start.elapsed();
1799
1800 println!(
1801 "Unicode incremental edit: {}µs, reused={}, reparsed={}",
1802 incremental_time.as_micros(),
1803 parser.reused_nodes,
1804 parser.reparsed_nodes
1805 );
1806
1807 let unicode_budget_micros = adaptive_perf_budget_micros(5_000);
1809 assert!(
1810 incremental_time.as_micros() < unicode_budget_micros,
1811 "Unicode incremental parsing should be <{}µs (got {}µs)",
1812 unicode_budget_micros,
1813 incremental_time.as_micros()
1814 );
1815 assert!(parser.reused_nodes > 0 || parser.reparsed_nodes > 0, "Should parse successfully");
1816
1817 Ok(())
1818 }
1819
1820 #[test]
1821 fn test_edit_near_ast_node_boundaries() -> ParseResult<()> {
1822 let mut parser = IncrementalParserV2::new();
1823
1824 let source1 = "sub func { my $x = 123; return $x * 2; }";
1826
1827 parser.parse(source1)?;
1828
1829 let number_end = source1.find("123").ok_or(crate::error::ParseError::UnexpectedEof)? + 3;
1831 parser.edit(Edit::new(
1832 number_end - 1, number_end,
1834 number_end + 1, Position::new(number_end - 1, 1, 1),
1836 Position::new(number_end, 1, 2),
1837 Position::new(number_end + 1, 1, 3),
1838 ));
1839
1840 let source2 = source1.replace("123", "12456");
1841 let start = Instant::now();
1842 let _tree = parser.parse(&source2)?;
1843 let boundary_edit_time = start.elapsed();
1844
1845 println!(
1846 "Boundary edit time: {}µs, reused={}, reparsed={}",
1847 boundary_edit_time.as_micros(),
1848 parser.reused_nodes,
1849 parser.reparsed_nodes
1850 );
1851
1852 let boundary_budget_micros = adaptive_perf_budget_micros(5_000);
1854 assert!(
1855 boundary_edit_time.as_micros() < boundary_budget_micros,
1856 "AST boundary edit should be <{}µs (got {}µs)",
1857 boundary_budget_micros,
1858 boundary_edit_time.as_micros()
1859 );
1860 assert!(parser.reparsed_nodes >= 1, "Should reparse at least the modified node");
1861
1862 Ok(())
1863 }
1864
1865 #[test]
1866 fn test_performance_regression_detection() -> ParseResult<()> {
1867 let mut parser = IncrementalParserV2::new();
1868
1869 let source = "my $baseline = 42; my $test = 'hello';";
1871 let mut parse_times = Vec::new();
1872
1873 for i in 0..10 {
1875 let start = Instant::now();
1876 parser.parse(source)?;
1877 let time = start.elapsed();
1878 parse_times.push(time.as_micros());
1879
1880 parser.edit(Edit::new(
1882 15,
1883 17,
1884 19, Position::new(15, 1, 16),
1886 Position::new(17, 1, 18),
1887 Position::new(19, 1, 20),
1888 ));
1889
1890 let test_source = if i % 2 == 0 {
1892 "my $baseline = 99; my $test = 'hello';"
1893 } else {
1894 "my $baseline = 42; my $test = 'hello';"
1895 };
1896
1897 let start = Instant::now();
1898 parser.parse(test_source)?;
1899 let incremental_time = start.elapsed();
1900
1901 println!(
1902 "Run {}: initial={}µs, incremental={}µs, reused={}, reparsed={}",
1903 i + 1,
1904 time.as_micros(),
1905 incremental_time.as_micros(),
1906 parser.reused_nodes,
1907 parser.reparsed_nodes
1908 );
1909
1910 assert!(
1912 incremental_time.as_millis() < 10,
1913 "Run {} performance regression detected: {}ms",
1914 i + 1,
1915 incremental_time.as_millis()
1916 );
1917 }
1918
1919 let avg_time = parse_times.iter().sum::<u128>() / parse_times.len() as u128;
1921 let max_time = *parse_times.iter().max().ok_or(crate::error::ParseError::UnexpectedEof)?;
1922 let min_time = *parse_times.iter().min().ok_or(crate::error::ParseError::UnexpectedEof)?;
1923
1924 println!(
1925 "Performance statistics: avg={}µs, min={}µs, max={}µs",
1926 avg_time, min_time, max_time
1927 );
1928
1929 let variation_factor = max_time as f64 / avg_time as f64;
1930 assert!(
1931 variation_factor <= 10.0,
1932 "Extreme performance inconsistency detected: max={}µs, avg={}µs ({}x variation)",
1933 max_time,
1934 avg_time,
1935 variation_factor
1936 );
1937 if variation_factor > 5.0 {
1938 println!(
1939 "⚠️ High performance variation detected: max={}µs, avg={}µs ({}x variation) - may indicate system load",
1940 max_time, avg_time, variation_factor
1941 );
1942 }
1943
1944 Ok(())
1945 }
1946}