1use 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#[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 {
83 Self::default()
84 }
85
86 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 pub fn is_sub_millisecond(&self) -> bool {
96 self.parse_time_micros < 1000
97 }
98
99 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#[derive(Debug, Clone)]
117pub struct IncrementalTree {
118 pub root: Node,
119 pub source: String,
120 node_map: HashMap<usize, Vec<Node>>,
122}
123
124impl IncrementalTree {
125 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 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 self.node_map.entry(node.location.start).or_default().push(node.clone());
141
142 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 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 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
204pub 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 reuse_analyzer: AdvancedReuseAnalyzer,
222 reuse_config: ReuseConfig,
224 pub last_reuse_analysis: Option<ReuseAnalysisResult>,
226}
227
228impl IncrementalParserV2 {
229 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 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 pub fn edit(&mut self, edit: Edit) {
261 self.pending_edits.add(edit);
262 }
263
264 pub fn parse(&mut self, source: &str) -> ParseResult<Node> {
266 self.reused_nodes = 0;
268 self.reparsed_nodes = 0;
269
270 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 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 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 if self.last_tree.is_none() {
294 self.reused_nodes = 0;
296 self.reparsed_nodes = self.count_nodes(&root);
297 } else {
298 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 self.reused_nodes = 0;
307 self.reparsed_nodes = self.count_nodes(&root);
308 } else if let Some(ref old_tree) = self.last_tree {
309 let (reused, reparsed) = self.analyze_reuse(&old_tree.root, &root);
311 self.reused_nodes = reused;
312 self.reparsed_nodes = reparsed;
313 } else {
314 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 if let Some(advanced_result) = self.try_advanced_reuse_parse(source, last_tree) {
329 return Some(advanced_result);
330 }
331
332 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 if self.is_whitespace_or_comment_edit(last_tree, source) {
340 return self.incremental_parse_whitespace(source, last_tree);
341 }
342
343 None
345 }
346
347 fn try_advanced_reuse_parse(
349 &mut self,
350 source: &str,
351 last_tree: &IncrementalTree,
352 ) -> Option<Node> {
353 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 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 self.last_reuse_analysis = Some(analysis_result);
372
373 if let Some(ref analysis) = self.last_reuse_analysis {
375 if analysis.meets_efficiency_target(self.reuse_config.min_confidence * 100.0) {
376 self.reused_nodes = analysis.reused_nodes;
378 self.reparsed_nodes = analysis.total_new_nodes - analysis.reused_nodes;
379
380 return Some(new_tree);
382 }
383 }
384
385 None
386 }
387
388 fn is_simple_value_edit(&self, tree: &IncrementalTree) -> bool {
389 if self.pending_edits.len() > 10 {
391 return false;
392 }
393
394 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 NodeKind::Number { .. } | NodeKind::String { .. } => {
409 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 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 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; }
444 }
445 }
446 None => {
447 return false; }
449 }
450 }
451
452 true
453 }
454
455 fn is_whitespace_or_comment_edit(&self, tree: &IncrementalTree, source: &str) -> bool {
457 for edit in self.pending_edits.edits() {
458 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 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 let mut lexer = PerlLexer::new(affected_text);
509
510 loop {
512 match lexer.next_token() {
513 Some(token) => {
514 match token.token_type {
515 TokenType::Whitespace | TokenType::Newline | TokenType::Comment(_) => {
517 }
519 TokenType::EOF => {
520 return true;
522 }
523 _ => {
524 return false;
526 }
527 }
528 }
529 None => {
530 return true;
532 }
533 }
534 }
535 }
536
537 fn incremental_parse_whitespace(
539 &mut self,
540 _source: &str,
541 last_tree: &IncrementalTree,
542 ) -> Option<Node> {
543 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 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 if source.is_empty() {
563 return None;
564 }
565
566 let new_root = self.clone_and_update_node(&last_tree.root, source, &last_tree.source);
568
569 if !self.validate_incremental_result(&new_root, source) {
571 return None;
572 }
573
574 self.count_reuse_potential(&last_tree.root, &new_root);
577
578 Some(new_root)
579 }
580
581 fn validate_incremental_result(&self, node: &Node, source: &str) -> bool {
585 if source.is_empty() {
587 return match &node.kind {
589 NodeKind::Program { statements } => statements.is_empty(),
590 _ => false,
591 };
592 }
593
594 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 if !source.is_char_boundary(node.location.start)
605 || !source.is_char_boundary(node.location.end)
606 {
607 return false;
608 }
609
610 if node.location.start < node.location.end {
612 let node_text = &source[node.location.start..node.location.end];
613
614 match &node.kind {
616 NodeKind::Number { value } => {
617 if value.trim() != node_text.trim() {
619 return false;
620 }
621 if value.parse::<f64>().is_err() && value.parse::<i64>().is_err() {
623 return false;
624 }
625 }
626 NodeKind::String { value, .. } => {
627 if !node_text.is_empty()
629 && !value.contains(node_text.trim_matches(|c| c == '"' || c == '\''))
630 {
631 }
633 }
634 NodeKind::Variable { name, .. } => {
635 if !node_text.contains(name) {
637 return false;
638 }
639 }
640 NodeKind::Identifier { name } => {
641 if name.trim() != node_text.trim() {
643 return false;
644 }
645 }
646 _ => {
647 }
650 }
651 }
652
653 self.validate_node_tree_consistency(node, source, 0, 3)
655 }
656
657 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; }
668
669 match &node.kind {
670 NodeKind::Program { statements } | NodeKind::Block { statements } => {
671 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 }
703 }
704
705 true
706 }
707
708 fn clone_and_update_node(&self, node: &Node, new_source: &str, old_source: &str) -> Node {
709 let shift = self.calculate_shift_at(node.location.start);
711
712 let affected = self.is_node_affected(node);
714
715 match &node.kind {
717 NodeKind::Program { statements } => {
718 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 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 match &node.kind {
762 NodeKind::Number { .. } => {
764 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 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 self.clone_with_shifted_positions(node, shift)
838 }
839
840 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 #[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 for i in (0..=position).rev() {
876 if i < source.len() && source.is_char_boundary(i) {
877 return i;
878 }
879 }
880
881 0
883 }
884
885 #[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 pub fn get_metrics(&self) -> &IncrementalMetrics {
907 &self.metrics
908 }
909
910 pub fn reset_metrics(&mut self) {
912 self.metrics = IncrementalMetrics::new();
913 }
914
915 pub fn get_last_reuse_analysis(&self) -> Option<&ReuseAnalysisResult> {
917 self.last_reuse_analysis.as_ref()
918 }
919
920 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 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 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 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 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 { condition, then_branch, elsif_branches, else_branch } => NodeKind::If {
1025 condition: Box::new(self.clone_with_shifted_positions(condition, shift)),
1026 then_branch: Box::new(self.clone_with_shifted_positions(then_branch, shift)),
1027 elsif_branches: elsif_branches
1028 .iter()
1029 .map(|(c, b)| {
1030 (
1031 self.clone_with_shifted_positions(c, shift),
1032 self.clone_with_shifted_positions(b, shift),
1033 )
1034 })
1035 .map(|(c, b)| (Box::new(c), Box::new(b)))
1036 .collect(),
1037 else_branch: else_branch
1038 .as_ref()
1039 .map(|b| Box::new(self.clone_with_shifted_positions(b, shift))),
1040 },
1041 _ => node.kind.clone(), };
1043
1044 Node::new(new_kind, new_location)
1045 }
1046
1047 fn count_reuse_potential(&mut self, old_root: &Node, new_root: &Node) {
1048 let (reused, reparsed) = self.analyze_reuse(old_root, new_root);
1050 self.reused_nodes = reused;
1051 self.reparsed_nodes = reparsed;
1052 }
1053
1054 fn analyze_reuse(&self, old_node: &Node, new_node: &Node) -> (usize, usize) {
1055 match (&old_node.kind, &new_node.kind) {
1057 (
1058 NodeKind::Program { statements: old_stmts },
1059 NodeKind::Program { statements: new_stmts },
1060 ) => {
1061 let mut reused = 1; let mut reparsed = 0;
1063
1064 for (old_stmt, new_stmt) in old_stmts.iter().zip(new_stmts.iter()) {
1065 let (r, p) = self.analyze_reuse(old_stmt, new_stmt);
1066 reused += r;
1067 reparsed += p;
1068 }
1069
1070 (reused, reparsed)
1071 }
1072 (
1073 NodeKind::VariableDeclaration { variable: old_var, initializer: old_init, .. },
1074 NodeKind::VariableDeclaration { variable: new_var, initializer: new_init, .. },
1075 ) => {
1076 let mut reused = 1; let mut reparsed = 0;
1078
1079 let (r, p) = self.analyze_reuse(old_var, new_var);
1080 reused += r;
1081 reparsed += p;
1082
1083 if let (Some(old_i), Some(new_i)) = (old_init, new_init) {
1084 let (r, p) = self.analyze_reuse(old_i, new_i);
1085 reused += r;
1086 reparsed += p;
1087 }
1088
1089 (reused, reparsed)
1090 }
1091 (NodeKind::Number { value: old_val }, NodeKind::Number { value: new_val }) => {
1092 if old_val != new_val {
1093 (0, 1) } else {
1095 (1, 0) }
1097 }
1098 (
1099 NodeKind::Variable { sigil: old_s, name: old_n },
1100 NodeKind::Variable { sigil: new_s, name: new_n },
1101 ) => {
1102 if old_s == new_s && old_n == new_n {
1103 (1, 0) } else {
1105 (0, 1) }
1107 }
1108 _ => {
1109 if self.nodes_match(old_node, new_node) {
1110 (1, 0)
1111 } else {
1112 (0, 1)
1113 }
1114 }
1115 }
1116 }
1117
1118 fn nodes_match(&self, node1: &Node, node2: &Node) -> bool {
1123 match (&node1.kind, &node2.kind) {
1124 (NodeKind::Number { value: v1 }, NodeKind::Number { value: v2 }) => v1 == v2,
1126 (
1127 NodeKind::String { value: v1, interpolated: i1 },
1128 NodeKind::String { value: v2, interpolated: i2 },
1129 ) => v1 == v2 && i1 == i2,
1130
1131 (
1133 NodeKind::Variable { sigil: s1, name: n1 },
1134 NodeKind::Variable { sigil: s2, name: n2 },
1135 ) => s1 == s2 && n1 == n2,
1136
1137 (NodeKind::Identifier { name: n1 }, NodeKind::Identifier { name: n2 }) => n1 == n2,
1139
1140 (NodeKind::Binary { op: op1, .. }, NodeKind::Binary { op: op2, .. }) => op1 == op2,
1142
1143 (NodeKind::Unary { op: op1, .. }, NodeKind::Unary { op: op2, .. }) => op1 == op2,
1145
1146 (
1148 NodeKind::FunctionCall { name: n1, args: args1 },
1149 NodeKind::FunctionCall { name: n2, args: args2 },
1150 ) => n1 == n2 && args1.len() == args2.len(),
1151
1152 (
1154 NodeKind::VariableDeclaration { declarator: d1, .. },
1155 NodeKind::VariableDeclaration { declarator: d2, .. },
1156 ) => d1 == d2,
1157
1158 (NodeKind::ArrayLiteral { elements: e1 }, NodeKind::ArrayLiteral { elements: e2 }) => {
1160 e1.len() == e2.len()
1161 }
1162
1163 (NodeKind::HashLiteral { pairs: p1 }, NodeKind::HashLiteral { pairs: p2 }) => {
1165 p1.len() == p2.len()
1166 }
1167
1168 (NodeKind::Block { statements: s1 }, NodeKind::Block { statements: s2 }) => {
1170 s1.len() == s2.len()
1171 }
1172
1173 (NodeKind::Program { statements: s1 }, NodeKind::Program { statements: s2 }) => {
1175 s1.len() == s2.len()
1176 }
1177
1178 (NodeKind::If { .. }, NodeKind::If { .. }) => true, (NodeKind::While { .. }, NodeKind::While { .. }) => true,
1181 (NodeKind::For { .. }, NodeKind::For { .. }) => true,
1182 (NodeKind::Foreach { .. }, NodeKind::Foreach { .. }) => true,
1183
1184 (NodeKind::Subroutine { name: n1, .. }, NodeKind::Subroutine { name: n2, .. }) => {
1186 n1 == n2
1187 }
1188
1189 (NodeKind::Package { name: n1, .. }, NodeKind::Package { name: n2, .. }) => n1 == n2,
1191
1192 (NodeKind::Use { module: m1, .. }, NodeKind::Use { module: m2, .. }) => m1 == m2,
1194
1195 (kind1, kind2) => std::mem::discriminant(kind1) == std::mem::discriminant(kind2),
1197 }
1198 }
1199
1200 fn count_nodes(&self, node: &Node) -> usize {
1201 let mut count = 1;
1202
1203 match &node.kind {
1204 NodeKind::Program { statements } | NodeKind::Block { statements } => {
1205 for stmt in statements {
1206 count += self.count_nodes(stmt);
1207 }
1208 }
1209 NodeKind::VariableDeclaration { variable, initializer, .. } => {
1210 count += self.count_nodes(variable);
1211 if let Some(init) = initializer {
1212 count += self.count_nodes(init);
1213 }
1214 }
1215 NodeKind::Binary { left, right, .. } => {
1216 count += self.count_nodes(left);
1217 count += self.count_nodes(right);
1218 }
1219 NodeKind::Unary { operand, .. } => {
1220 count += self.count_nodes(operand);
1221 }
1222 NodeKind::FunctionCall { args, .. } => {
1223 for arg in args {
1224 count += self.count_nodes(arg);
1225 }
1226 }
1227 NodeKind::If { condition, then_branch, elsif_branches, else_branch } => {
1228 count += self.count_nodes(condition);
1229 count += self.count_nodes(then_branch);
1230 for (cond, branch) in elsif_branches {
1231 count += self.count_nodes(cond);
1232 count += self.count_nodes(branch);
1233 }
1234 if let Some(branch) = else_branch {
1235 count += self.count_nodes(branch);
1236 }
1237 }
1238 _ => {}
1239 }
1240
1241 count
1242 }
1243}
1244
1245impl Default for IncrementalParserV2 {
1246 fn default() -> Self {
1247 Self::new()
1248 }
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253 use super::*;
1254 use perl_parser_core::position::Position;
1255 use std::time::Instant;
1256
1257 fn adaptive_perf_budget_micros(base_budget_micros: u128) -> u128 {
1258 let thread_count = std::env::var("RUST_TEST_THREADS")
1259 .ok()
1260 .and_then(|value| value.parse::<usize>().ok())
1261 .unwrap_or_else(|| {
1262 std::thread::available_parallelism().map_or(8, std::num::NonZeroUsize::get)
1263 });
1264
1265 let mut budget = base_budget_micros;
1266 if thread_count <= 2 {
1267 budget = budget.saturating_mul(2);
1268 } else if thread_count <= 4 {
1269 budget = budget.saturating_mul(3) / 2;
1270 }
1271
1272 if std::env::var("CI").is_ok() {
1273 budget = budget.saturating_mul(3) / 2;
1274 }
1275
1276 budget
1277 }
1278
1279 #[test]
1280 fn test_basic_compilation() {
1281 let parser = IncrementalParserV2::new();
1282 assert_eq!(parser.reused_nodes, 0);
1283 assert_eq!(parser.reparsed_nodes, 0);
1284 }
1285
1286 #[test]
1287 fn test_performance_timing_detailed() -> ParseResult<()> {
1288 let mut parser = IncrementalParserV2::new();
1289
1290 let source1 = "my $x = 42;";
1292 let start = Instant::now();
1293 let _tree1 = parser.parse(source1)?;
1294 let initial_parse_time = start.elapsed();
1295
1296 println!("Initial parse time: {:?}", initial_parse_time);
1297 println!("Initial nodes reparsed: {}", parser.reparsed_nodes);
1298
1299 parser.edit(Edit::new(
1301 8,
1302 10,
1303 12, Position::new(8, 1, 9),
1305 Position::new(10, 1, 11),
1306 Position::new(12, 1, 13),
1307 ));
1308
1309 let source2 = "my $x = 4242;";
1310 let start = Instant::now();
1311 let _tree2 = parser.parse(source2)?;
1312 let incremental_parse_time = start.elapsed();
1313
1314 println!("Incremental parse time: {:?}", incremental_parse_time);
1315 println!(
1316 "Incremental nodes reused: {}, reparsed: {}",
1317 parser.reused_nodes, parser.reparsed_nodes
1318 );
1319
1320 assert!(
1322 incremental_parse_time.as_micros() < 5000,
1323 "Incremental parse time should be <5ms, got {:?}",
1324 incremental_parse_time
1325 );
1326
1327 assert!(parser.reused_nodes >= 3, "Should reuse at least 3 nodes");
1329 assert_eq!(parser.reparsed_nodes, 1, "Should only reparse the changed Number node");
1330
1331 let speedup =
1333 initial_parse_time.as_nanos() as f64 / incremental_parse_time.as_nanos() as f64;
1334 println!("Performance improvement: {:.2}x faster", speedup);
1335
1336 if speedup >= 1.5 {
1339 println!("✅ Good speedup achieved: {:.2}x", speedup);
1340 } else {
1341 println!("⚠️ Limited speedup for micro-benchmark (expected for tiny examples)");
1342 }
1343
1344 Ok(())
1345 }
1346
1347 #[test]
1348 fn test_incremental_value_change() -> ParseResult<()> {
1349 let mut parser = IncrementalParserV2::new();
1350
1351 let source1 = "my $x = 42;";
1353 let start = Instant::now();
1354 let _tree1 = parser.parse(source1)?;
1355 let initial_time = start.elapsed();
1356
1357 assert_eq!(parser.reparsed_nodes, 4); println!(
1361 "Initial parse: {}µs, {} nodes parsed",
1362 initial_time.as_micros(),
1363 parser.reparsed_nodes
1364 );
1365
1366 parser.edit(Edit::new(
1368 8,
1369 10,
1370 12, Position::new(8, 1, 9),
1372 Position::new(10, 1, 11),
1373 Position::new(12, 1, 13),
1374 ));
1375
1376 let source2 = "my $x = 4242;";
1377 let start = Instant::now();
1378 let tree2 = parser.parse(source2)?;
1379 let incremental_time = start.elapsed();
1380
1381 println!(
1382 "Incremental parse: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1383 incremental_time.as_micros(),
1384 parser.reused_nodes,
1385 parser.reparsed_nodes
1386 );
1387 assert_eq!(parser.reused_nodes, 3); assert_eq!(parser.reparsed_nodes, 1); assert!(incremental_time.as_micros() < 500, "Incremental update should be <500µs");
1392 let efficiency =
1393 parser.reused_nodes as f64 / (parser.reused_nodes + parser.reparsed_nodes) as f64;
1394 assert!(
1395 efficiency >= 0.75,
1396 "Node reuse efficiency should be ≥75%, got {:.1}%",
1397 efficiency * 100.0
1398 );
1399
1400 if let NodeKind::Program { statements } = &tree2.kind {
1402 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1403 &statements[0].kind
1404 {
1405 if let NodeKind::Number { value } = &init.kind {
1406 assert_eq!(value, "4242");
1407 }
1408 }
1409 }
1410
1411 Ok(())
1412 }
1413
1414 #[test]
1415 fn test_multiple_value_changes() -> ParseResult<()> {
1416 let mut parser = IncrementalParserV2::new();
1417
1418 let source1 = "my $x = 10;\nmy $y = 20;";
1420 let start = Instant::now();
1421 parser.parse(source1)?;
1422 let initial_time = start.elapsed();
1423 let initial_nodes = parser.reparsed_nodes;
1424
1425 println!(
1426 "Initial parse (multi-statement): {}µs, {} nodes",
1427 initial_time.as_micros(),
1428 initial_nodes
1429 );
1430
1431 parser.edit(Edit::new(
1433 8,
1434 10,
1435 11, Position::new(8, 1, 9),
1437 Position::new(10, 1, 11),
1438 Position::new(11, 1, 12),
1439 ));
1440
1441 parser.edit(Edit::new(
1442 21,
1443 23,
1444 24, Position::new(21, 2, 9),
1446 Position::new(23, 2, 11),
1447 Position::new(24, 2, 12),
1448 ));
1449
1450 let source2 = "my $x = 100;\nmy $y = 200;";
1451 let start = Instant::now();
1452 let tree = parser.parse(source2)?;
1453 let incremental_time = start.elapsed();
1454
1455 println!(
1456 "Multiple edits: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1457 incremental_time.as_micros(),
1458 parser.reused_nodes,
1459 parser.reparsed_nodes
1460 );
1461 assert!(
1464 parser.reused_nodes >= 5,
1465 "Should reuse at least 5 nodes, got {}",
1466 parser.reused_nodes
1467 );
1468 assert!(
1469 parser.reparsed_nodes >= 1,
1470 "Should reparse at least 1 node, got {}",
1471 parser.reparsed_nodes
1472 );
1473
1474 assert!(incremental_time.as_micros() < 5000, "Multiple edits should be <5ms");
1476 let total_nodes = parser.reused_nodes + parser.reparsed_nodes;
1477 let reuse_ratio = parser.reused_nodes as f64 / total_nodes as f64;
1478 assert!(
1479 reuse_ratio >= 0.7,
1480 "Multi-edit reuse ratio should be ≥70%, got {:.1}%",
1481 reuse_ratio * 100.0
1482 );
1483
1484 if let NodeKind::Program { statements } = &tree.kind {
1486 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1487 &statements[0].kind
1488 {
1489 if let NodeKind::Number { value } = &init.kind {
1490 assert_eq!(value, "100");
1491 }
1492 }
1493 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1494 &statements[1].kind
1495 {
1496 if let NodeKind::Number { value } = &init.kind {
1497 assert_eq!(value, "200");
1498 }
1499 }
1500 }
1501
1502 Ok(())
1503 }
1504
1505 #[test]
1506 fn test_too_many_edits_fallback() -> ParseResult<()> {
1507 let mut parser = IncrementalParserV2::new();
1508
1509 let source1 = "my $x = 1;";
1511 parser.parse(source1)?;
1512
1513 for i in 0..15 {
1515 parser.edit(Edit::new(
1516 8 + i,
1517 9 + i,
1518 10 + i,
1519 Position::new(8 + i, 1, (9 + i) as u32),
1520 Position::new(9 + i, 1, (10 + i) as u32),
1521 Position::new(10 + i, 1, (11 + i) as u32),
1522 ));
1523 }
1524
1525 let source2 = "my $x = 123456789012345;";
1526 let tree = parser.parse(source2)?;
1527
1528 assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1531 if let NodeKind::Program { statements } = &tree.kind {
1535 assert_eq!(statements.len(), 1);
1536 }
1537
1538 Ok(())
1539 }
1540
1541 #[test]
1542 fn test_invalid_edit_bounds() -> ParseResult<()> {
1543 let mut parser = IncrementalParserV2::new();
1544
1545 let source1 = "my $x = 42;";
1547 parser.parse(source1)?;
1548
1549 parser.edit(Edit::new(
1551 8,
1552 12, 13,
1554 Position::new(8, 1, 9),
1555 Position::new(12, 1, 13),
1556 Position::new(13, 1, 14),
1557 ));
1558
1559 let source2 = "my $x = 123;";
1560 let tree = parser.parse(source2)?;
1561
1562 assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1565 if let NodeKind::Program { statements } = &tree.kind {
1569 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1570 &statements[0].kind
1571 {
1572 if let NodeKind::Number { value } = &init.kind {
1573 assert_eq!(value, "123");
1574 }
1575 }
1576 }
1577
1578 Ok(())
1579 }
1580
1581 #[test]
1582 fn test_string_edit() -> ParseResult<()> {
1583 let mut parser = IncrementalParserV2::new();
1584
1585 let source1 = "my $name = \"hello\";";
1587 parser.parse(source1)?;
1588
1589 parser.edit(Edit::new(
1591 12,
1592 17, 17,
1594 Position::new(12, 1, 13),
1595 Position::new(17, 1, 18),
1596 Position::new(17, 1, 18),
1597 ));
1598
1599 let source2 = "my $name = \"world\";";
1600 let tree = parser.parse(source2)?;
1601
1602 println!(
1604 "DEBUG test_string_edit: reused_nodes = {}, reparsed_nodes = {}",
1605 parser.reused_nodes, parser.reparsed_nodes
1606 );
1607 assert_eq!(parser.reused_nodes, 3); assert_eq!(parser.reparsed_nodes, 1); if let NodeKind::Program { statements } = &tree.kind {
1612 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1613 &statements[0].kind
1614 {
1615 if let NodeKind::String { value, .. } = &init.kind {
1616 assert_eq!(value, "\"world\"");
1617 }
1618 }
1619 }
1620
1621 Ok(())
1622 }
1623
1624 #[test]
1625 fn test_empty_source_handling() -> ParseResult<()> {
1626 let mut parser = IncrementalParserV2::new();
1627
1628 let source1 = "my $x = 42;";
1630 let start = Instant::now();
1631 parser.parse(source1)?;
1632 let initial_time = start.elapsed();
1633 println!("Initial parse time: {}µs", initial_time.as_micros());
1634
1635 parser.edit(Edit::new(
1637 8,
1638 10,
1639 11,
1640 Position::new(8, 1, 9),
1641 Position::new(10, 1, 11),
1642 Position::new(11, 1, 12),
1643 ));
1644
1645 let source2 = "";
1647 let start = Instant::now();
1648 let result = parser.parse(source2);
1649 let parse_time = start.elapsed();
1650
1651 println!("Empty source parse time: {}µs", parse_time.as_micros());
1652
1653 match result {
1655 Ok(_) => {
1656 assert_eq!(parser.reused_nodes, 0);
1658 println!("Empty source parsing succeeded with fallback");
1659 }
1660 Err(_) => {
1661 assert_eq!(parser.reused_nodes, 0);
1663 println!("Empty source parsing failed gracefully (expected)");
1664 }
1665 }
1666
1667 assert!(parse_time.as_millis() < 100, "Empty source handling should be fast");
1669
1670 Ok(())
1671 }
1672
1673 #[test]
1674 fn test_complex_nested_structure_edits() -> ParseResult<()> {
1675 let mut parser = IncrementalParserV2::new();
1676
1677 let source1 = r#"
1679if ($condition) {
1680 my $nested = {
1681 key1 => "value1",
1682 key2 => 42,
1683 key3 => [1, 2, 3]
1684 };
1685 process($nested);
1686}
1687"#;
1688
1689 let start = Instant::now();
1690 parser.parse(source1)?;
1691 let initial_time = start.elapsed();
1692 let initial_nodes = parser.reparsed_nodes;
1693
1694 println!(
1695 "Complex structure initial parse: {}µs, {} nodes",
1696 initial_time.as_micros(),
1697 initial_nodes
1698 );
1699
1700 let value_start =
1702 source1.find("42").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1703 parser.edit(Edit::new(
1704 value_start,
1705 value_start + 2,
1706 value_start + 4, Position::new(value_start, 1, 1),
1708 Position::new(value_start + 2, 1, 3),
1709 Position::new(value_start + 4, 1, 5),
1710 ));
1711
1712 let source2 = source1.replace("42", "9999");
1713 let start = Instant::now();
1714 let _tree = parser.parse(&source2)?;
1715 let incremental_time = start.elapsed();
1716
1717 println!(
1718 "Complex nested edit: {}µs, reused={}, reparsed={}",
1719 incremental_time.as_micros(),
1720 parser.reused_nodes,
1721 parser.reparsed_nodes
1722 );
1723
1724 assert!(incremental_time.as_millis() < 10, "Complex nested edit should be <10ms");
1726
1727 if parser.reused_nodes > 0 {
1729 println!("Successfully reused {} nodes in complex structure", parser.reused_nodes);
1730 } else {
1731 println!("Complex structure caused full reparse (acceptable for edge cases)");
1732 }
1733
1734 Ok(())
1735 }
1736
1737 #[test]
1738 fn test_large_document_performance() -> ParseResult<()> {
1739 let mut parser = IncrementalParserV2::new();
1740
1741 let mut large_source = String::new();
1743 for i in 0..100 {
1744 large_source.push_str(&format!("my $var{} = {};\n", i, i * 10));
1745 }
1746
1747 let start = Instant::now();
1748 parser.parse(&large_source)?;
1749 let initial_time = start.elapsed();
1750 let initial_nodes = parser.reparsed_nodes;
1751
1752 println!(
1753 "Large document initial parse: {}ms, {} nodes",
1754 initial_time.as_millis(),
1755 initial_nodes
1756 );
1757
1758 let edit_pos = large_source
1760 .find("my $var50 = 500")
1761 .ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?
1762 + 13;
1763 parser.edit(Edit::new(
1764 edit_pos,
1765 edit_pos + 3, edit_pos + 3,
1767 Position::new(edit_pos, 1, 1),
1768 Position::new(edit_pos + 3, 1, 4),
1769 Position::new(edit_pos + 3, 1, 4),
1770 ));
1771
1772 let source2 = large_source.replace("500", "999");
1773 let start = Instant::now();
1774 let _tree = parser.parse(&source2)?;
1775 let incremental_time = start.elapsed();
1776
1777 println!(
1778 "Large document incremental: {}ms, reused={}, reparsed={}",
1779 incremental_time.as_millis(),
1780 parser.reused_nodes,
1781 parser.reparsed_nodes
1782 );
1783
1784 assert!(incremental_time.as_millis() < 50, "Large document incremental should be <50ms");
1786
1787 if parser.reused_nodes > 0 {
1789 let reuse_percentage = parser.reused_nodes as f64
1790 / (parser.reused_nodes + parser.reparsed_nodes) as f64
1791 * 100.0;
1792 println!("Large document reuse rate: {:.1}%", reuse_percentage);
1793 assert!(reuse_percentage > 50.0, "Large document should reuse >50% of nodes");
1794 }
1795
1796 Ok(())
1797 }
1798
1799 #[test]
1800 fn test_unicode_heavy_incremental_parsing() -> ParseResult<()> {
1801 let mut parser = IncrementalParserV2::new();
1802
1803 let source1 = "my $🌟variable = '你好世界'; # Comment with emoji 🚀\nmy $café = 'résumé';";
1805
1806 let start = Instant::now();
1807 parser.parse(source1)?;
1808 let initial_time = start.elapsed();
1809
1810 println!("Unicode document initial parse: {}µs", initial_time.as_micros());
1811
1812 let edit_start =
1814 source1.find("你好世界").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1815 let edit_end = edit_start + "你好世界".len();
1816 parser.edit(Edit::new(
1817 edit_start,
1818 edit_end,
1819 edit_start + "再见".len(), Position::new(edit_start, 1, 1),
1821 Position::new(edit_end, 1, 2),
1822 Position::new(edit_start + "再见".len(), 1, 2),
1823 ));
1824
1825 let source2 = source1.replace("你好世界", "再见");
1826 let start = Instant::now();
1827 let _tree = parser.parse(&source2)?;
1828 let incremental_time = start.elapsed();
1829
1830 println!(
1831 "Unicode incremental edit: {}µs, reused={}, reparsed={}",
1832 incremental_time.as_micros(),
1833 parser.reused_nodes,
1834 parser.reparsed_nodes
1835 );
1836
1837 let unicode_budget_micros = adaptive_perf_budget_micros(5_000);
1839 assert!(
1840 incremental_time.as_micros() < unicode_budget_micros,
1841 "Unicode incremental parsing should be <{}µs (got {}µs)",
1842 unicode_budget_micros,
1843 incremental_time.as_micros()
1844 );
1845 assert!(parser.reused_nodes > 0 || parser.reparsed_nodes > 0, "Should parse successfully");
1846
1847 Ok(())
1848 }
1849
1850 #[test]
1851 fn test_edit_near_ast_node_boundaries() -> ParseResult<()> {
1852 let mut parser = IncrementalParserV2::new();
1853
1854 let source1 = "sub func { my $x = 123; return $x * 2; }";
1856
1857 parser.parse(source1)?;
1858
1859 let number_end =
1861 source1.find("123").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)? + 3;
1862 parser.edit(Edit::new(
1863 number_end - 1, number_end,
1865 number_end + 1, Position::new(number_end - 1, 1, 1),
1867 Position::new(number_end, 1, 2),
1868 Position::new(number_end + 1, 1, 3),
1869 ));
1870
1871 let source2 = source1.replace("123", "12456");
1872 let start = Instant::now();
1873 let _tree = parser.parse(&source2)?;
1874 let boundary_edit_time = start.elapsed();
1875
1876 println!(
1877 "Boundary edit time: {}µs, reused={}, reparsed={}",
1878 boundary_edit_time.as_micros(),
1879 parser.reused_nodes,
1880 parser.reparsed_nodes
1881 );
1882
1883 let boundary_budget_micros = adaptive_perf_budget_micros(5_000);
1885 assert!(
1886 boundary_edit_time.as_micros() < boundary_budget_micros,
1887 "AST boundary edit should be <{}µs (got {}µs)",
1888 boundary_budget_micros,
1889 boundary_edit_time.as_micros()
1890 );
1891 assert!(parser.reparsed_nodes >= 1, "Should reparse at least the modified node");
1892
1893 Ok(())
1894 }
1895
1896 #[test]
1897 fn test_whitespace_insertion_reuses_tree() -> ParseResult<()> {
1898 let mut parser = IncrementalParserV2::new();
1899 let source1 = "my $x = 42;";
1900 parser.parse(source1)?;
1901
1902 parser.edit(Edit::new(
1903 5,
1904 5,
1905 7,
1906 Position::new(5, 1, 6),
1907 Position::new(5, 1, 6),
1908 Position::new(7, 1, 8),
1909 ));
1910
1911 let source2 = "my $x = 42;";
1912 parser.parse(source2)?;
1913
1914 assert!(parser.reused_nodes > 0, "Whitespace insertion should reuse prior tree");
1915 assert!(parser.reparsed_nodes <= 1, "Whitespace insertion should avoid broad reparsing");
1916 Ok(())
1917 }
1918
1919 #[test]
1920 fn test_performance_regression_detection() -> ParseResult<()> {
1921 let mut parser = IncrementalParserV2::new();
1922
1923 let source = "my $baseline = 42; my $test = 'hello';";
1925 let mut parse_times = Vec::new();
1926
1927 for i in 0..10 {
1929 let start = Instant::now();
1930 parser.parse(source)?;
1931 let time = start.elapsed();
1932 parse_times.push(time.as_micros());
1933
1934 parser.edit(Edit::new(
1936 15,
1937 17,
1938 19, Position::new(15, 1, 16),
1940 Position::new(17, 1, 18),
1941 Position::new(19, 1, 20),
1942 ));
1943
1944 let test_source = if i % 2 == 0 {
1946 "my $baseline = 99; my $test = 'hello';"
1947 } else {
1948 "my $baseline = 42; my $test = 'hello';"
1949 };
1950
1951 let start = Instant::now();
1952 parser.parse(test_source)?;
1953 let incremental_time = start.elapsed();
1954
1955 println!(
1956 "Run {}: initial={}µs, incremental={}µs, reused={}, reparsed={}",
1957 i + 1,
1958 time.as_micros(),
1959 incremental_time.as_micros(),
1960 parser.reused_nodes,
1961 parser.reparsed_nodes
1962 );
1963
1964 assert!(
1966 incremental_time.as_millis() < 10,
1967 "Run {} performance regression detected: {}ms",
1968 i + 1,
1969 incremental_time.as_millis()
1970 );
1971 }
1972
1973 let avg_time = parse_times.iter().sum::<u128>() / parse_times.len() as u128;
1975 let max_time =
1976 *parse_times.iter().max().ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1977 let min_time =
1978 *parse_times.iter().min().ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1979
1980 println!(
1981 "Performance statistics: avg={}µs, min={}µs, max={}µs",
1982 avg_time, min_time, max_time
1983 );
1984
1985 let variation_factor = max_time as f64 / avg_time as f64;
1986 assert!(
1987 variation_factor <= 10.0,
1988 "Extreme performance inconsistency detected: max={}µs, avg={}µs ({}x variation)",
1989 max_time,
1990 avg_time,
1991 variation_factor
1992 );
1993 if variation_factor > 5.0 {
1994 println!(
1995 "⚠️ High performance variation detected: max={}µs, avg={}µs ({}x variation) - may indicate system load",
1996 max_time, avg_time, variation_factor
1997 );
1998 }
1999
2000 Ok(())
2001 }
2002
2003 #[test]
2005 fn test_whitespace_before_operator_reuses_tree() -> ParseResult<()> {
2006 let mut parser = IncrementalParserV2::new();
2007 let source1 = "my $x = 42;";
2008 parser.parse(source1)?;
2009 parser.edit(Edit::new(
2010 6,
2011 6,
2012 9,
2013 Position::new(6, 1, 7),
2014 Position::new(6, 1, 7),
2015 Position::new(9, 1, 10),
2016 ));
2017 let source2 = "my $x = 42;";
2018 parser.parse(source2)?;
2019 assert!(parser.reused_nodes > 0);
2020 Ok(())
2021 }
2022
2023 #[test]
2024 fn test_comment_insertion_reuses_tree() -> ParseResult<()> {
2025 let mut parser = IncrementalParserV2::new();
2026 let source1 = "my $x = 42;";
2027 parser.parse(source1)?;
2028 parser.edit(Edit::new(
2029 10,
2030 11,
2031 24,
2032 Position::new(10, 1, 11),
2033 Position::new(11, 1, 12),
2034 Position::new(24, 1, 25),
2035 ));
2036 let source2 = "my $x = 42; # comment";
2037 parser.parse(source2)?;
2038 assert!(parser.reused_nodes > 0);
2039 Ok(())
2040 }
2041
2042 #[test]
2043 fn test_trailing_whitespace_reuses_tree() -> ParseResult<()> {
2044 let mut parser = IncrementalParserV2::new();
2045 let source1 = "my $x = 42;";
2046 parser.parse(source1)?;
2047 parser.edit(Edit::new(
2048 11,
2049 11,
2050 16,
2051 Position::new(11, 1, 12),
2052 Position::new(11, 1, 12),
2053 Position::new(16, 1, 17),
2054 ));
2055 let source2 = "my $x = 42; ";
2056 parser.parse(source2)?;
2057 assert!(parser.reused_nodes > 0);
2058 Ok(())
2059 }
2060
2061 #[test]
2062 fn test_newline_insertion_reuses_tree() -> ParseResult<()> {
2063 let mut parser = IncrementalParserV2::new();
2064 let source1 = "my $x = 42;";
2065 parser.parse(source1)?;
2066 parser.edit(Edit::new(
2067 11,
2068 11,
2069 12,
2070 Position::new(11, 1, 12),
2071 Position::new(11, 1, 12),
2072 Position::new(12, 2, 1),
2073 ));
2074 let source2 = "my $x = 42;\n";
2075 parser.parse(source2)?;
2076 assert!(parser.reused_nodes > 0);
2077 Ok(())
2078 }
2079
2080 #[test]
2081 fn test_whitespace_deletion_reuses_tree() -> ParseResult<()> {
2082 let mut parser = IncrementalParserV2::new();
2083 let source1 = "my $x = 42;";
2084 parser.parse(source1)?;
2085 parser.edit(Edit::new(
2086 3,
2087 5,
2088 4,
2089 Position::new(3, 1, 4),
2090 Position::new(5, 1, 6),
2091 Position::new(4, 1, 5),
2092 ));
2093 let source2 = "my $x = 42;";
2094 parser.parse(source2)?;
2095 assert!(parser.reused_nodes > 0);
2096 Ok(())
2097 }
2098
2099 #[test]
2100 fn test_whitespace_at_statement_boundary() -> ParseResult<()> {
2101 let mut parser = IncrementalParserV2::new();
2102 let source1 = "print 'hello';my $x = 42;";
2103 parser.parse(source1)?;
2104 parser.edit(Edit::new(
2105 14,
2106 14,
2107 15,
2108 Position::new(14, 1, 15),
2109 Position::new(14, 1, 15),
2110 Position::new(15, 1, 16),
2111 ));
2112 let source2 = "print 'hello'; my $x = 42;";
2113 parser.parse(source2)?;
2114 assert!(parser.reused_nodes > 0);
2115 Ok(())
2116 }
2117
2118 #[test]
2119 fn test_whitespace_edit_checks_both_old_and_new() -> ParseResult<()> {
2120 let mut parser = IncrementalParserV2::new();
2121 let source1 = "my $x = 42;";
2122 parser.parse(source1)?;
2123 parser.edit(Edit::new(
2124 6,
2125 7,
2126 8,
2127 Position::new(6, 1, 7),
2128 Position::new(7, 1, 8),
2129 Position::new(8, 1, 9),
2130 ));
2131 let source2 = "my $x= 42;";
2132 parser.parse(source2)?;
2133 assert!(parser.reused_nodes > 0);
2134 Ok(())
2135 }
2136}