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 {
1025 condition, then_branch, elsif_branches, else_branch, keyword, ..
1026 } => NodeKind::If {
1027 condition: Box::new(self.clone_with_shifted_positions(condition, shift)),
1028 then_branch: Box::new(self.clone_with_shifted_positions(then_branch, shift)),
1029 elsif_branches: elsif_branches
1030 .iter()
1031 .map(|(c, b)| {
1032 (
1033 self.clone_with_shifted_positions(c, shift),
1034 self.clone_with_shifted_positions(b, shift),
1035 )
1036 })
1037 .map(|(c, b)| (Box::new(c), Box::new(b)))
1038 .collect(),
1039 else_branch: else_branch
1040 .as_ref()
1041 .map(|b| Box::new(self.clone_with_shifted_positions(b, shift))),
1042 keyword: keyword.clone(),
1043 },
1044 _ => node.kind.clone(), };
1046
1047 Node::new(new_kind, new_location)
1048 }
1049
1050 fn count_reuse_potential(&mut self, old_root: &Node, new_root: &Node) {
1051 let (reused, reparsed) = self.analyze_reuse(old_root, new_root);
1053 self.reused_nodes = reused;
1054 self.reparsed_nodes = reparsed;
1055 }
1056
1057 fn analyze_reuse(&self, old_node: &Node, new_node: &Node) -> (usize, usize) {
1058 match (&old_node.kind, &new_node.kind) {
1060 (
1061 NodeKind::Program { statements: old_stmts },
1062 NodeKind::Program { statements: new_stmts },
1063 ) => {
1064 let mut reused = 1; let mut reparsed = 0;
1066
1067 for (old_stmt, new_stmt) in old_stmts.iter().zip(new_stmts.iter()) {
1068 let (r, p) = self.analyze_reuse(old_stmt, new_stmt);
1069 reused += r;
1070 reparsed += p;
1071 }
1072
1073 (reused, reparsed)
1074 }
1075 (
1076 NodeKind::VariableDeclaration { variable: old_var, initializer: old_init, .. },
1077 NodeKind::VariableDeclaration { variable: new_var, initializer: new_init, .. },
1078 ) => {
1079 let mut reused = 1; let mut reparsed = 0;
1081
1082 let (r, p) = self.analyze_reuse(old_var, new_var);
1083 reused += r;
1084 reparsed += p;
1085
1086 if let (Some(old_i), Some(new_i)) = (old_init, new_init) {
1087 let (r, p) = self.analyze_reuse(old_i, new_i);
1088 reused += r;
1089 reparsed += p;
1090 }
1091
1092 (reused, reparsed)
1093 }
1094 (NodeKind::Number { value: old_val }, NodeKind::Number { value: new_val }) => {
1095 if old_val != new_val {
1096 (0, 1) } else {
1098 (1, 0) }
1100 }
1101 (
1102 NodeKind::Variable { sigil: old_s, name: old_n },
1103 NodeKind::Variable { sigil: new_s, name: new_n },
1104 ) => {
1105 if old_s == new_s && old_n == new_n {
1106 (1, 0) } else {
1108 (0, 1) }
1110 }
1111 _ => {
1112 if self.nodes_match(old_node, new_node) {
1113 (1, 0)
1114 } else {
1115 (0, 1)
1116 }
1117 }
1118 }
1119 }
1120
1121 fn nodes_match(&self, node1: &Node, node2: &Node) -> bool {
1126 match (&node1.kind, &node2.kind) {
1127 (NodeKind::Number { value: v1 }, NodeKind::Number { value: v2 }) => v1 == v2,
1129 (
1130 NodeKind::String { value: v1, interpolated: i1 },
1131 NodeKind::String { value: v2, interpolated: i2 },
1132 ) => v1 == v2 && i1 == i2,
1133
1134 (
1136 NodeKind::Variable { sigil: s1, name: n1 },
1137 NodeKind::Variable { sigil: s2, name: n2 },
1138 ) => s1 == s2 && n1 == n2,
1139
1140 (NodeKind::Identifier { name: n1 }, NodeKind::Identifier { name: n2 }) => n1 == n2,
1142
1143 (NodeKind::Binary { op: op1, .. }, NodeKind::Binary { op: op2, .. }) => op1 == op2,
1145
1146 (NodeKind::Unary { op: op1, .. }, NodeKind::Unary { op: op2, .. }) => op1 == op2,
1148
1149 (
1151 NodeKind::FunctionCall { name: n1, args: args1 },
1152 NodeKind::FunctionCall { name: n2, args: args2 },
1153 ) => n1 == n2 && args1.len() == args2.len(),
1154
1155 (
1157 NodeKind::VariableDeclaration { declarator: d1, .. },
1158 NodeKind::VariableDeclaration { declarator: d2, .. },
1159 ) => d1 == d2,
1160
1161 (NodeKind::ArrayLiteral { elements: e1 }, NodeKind::ArrayLiteral { elements: e2 }) => {
1163 e1.len() == e2.len()
1164 }
1165
1166 (NodeKind::HashLiteral { pairs: p1 }, NodeKind::HashLiteral { pairs: p2 }) => {
1168 p1.len() == p2.len()
1169 }
1170
1171 (NodeKind::Block { statements: s1 }, NodeKind::Block { statements: s2 }) => {
1173 s1.len() == s2.len()
1174 }
1175
1176 (NodeKind::Program { statements: s1 }, NodeKind::Program { statements: s2 }) => {
1178 s1.len() == s2.len()
1179 }
1180
1181 (NodeKind::If { .. }, NodeKind::If { .. }) => true, (NodeKind::While { .. }, NodeKind::While { .. }) => true,
1184 (NodeKind::For { .. }, NodeKind::For { .. }) => true,
1185 (NodeKind::Foreach { .. }, NodeKind::Foreach { .. }) => true,
1186
1187 (NodeKind::Subroutine { name: n1, .. }, NodeKind::Subroutine { name: n2, .. }) => {
1189 n1 == n2
1190 }
1191
1192 (NodeKind::Package { name: n1, .. }, NodeKind::Package { name: n2, .. }) => n1 == n2,
1194
1195 (NodeKind::Use { module: m1, .. }, NodeKind::Use { module: m2, .. }) => m1 == m2,
1197
1198 (kind1, kind2) => std::mem::discriminant(kind1) == std::mem::discriminant(kind2),
1200 }
1201 }
1202
1203 fn count_nodes(&self, node: &Node) -> usize {
1204 let mut count = 1;
1205
1206 match &node.kind {
1207 NodeKind::Program { statements } | NodeKind::Block { statements } => {
1208 for stmt in statements {
1209 count += self.count_nodes(stmt);
1210 }
1211 }
1212 NodeKind::VariableDeclaration { variable, initializer, .. } => {
1213 count += self.count_nodes(variable);
1214 if let Some(init) = initializer {
1215 count += self.count_nodes(init);
1216 }
1217 }
1218 NodeKind::Binary { left, right, .. } => {
1219 count += self.count_nodes(left);
1220 count += self.count_nodes(right);
1221 }
1222 NodeKind::Unary { operand, .. } => {
1223 count += self.count_nodes(operand);
1224 }
1225 NodeKind::FunctionCall { args, .. } => {
1226 for arg in args {
1227 count += self.count_nodes(arg);
1228 }
1229 }
1230 NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
1231 count += self.count_nodes(condition);
1232 count += self.count_nodes(then_branch);
1233 for (cond, branch) in elsif_branches {
1234 count += self.count_nodes(cond);
1235 count += self.count_nodes(branch);
1236 }
1237 if let Some(branch) = else_branch {
1238 count += self.count_nodes(branch);
1239 }
1240 }
1241 _ => {}
1242 }
1243
1244 count
1245 }
1246}
1247
1248impl Default for IncrementalParserV2 {
1249 fn default() -> Self {
1250 Self::new()
1251 }
1252}
1253
1254#[cfg(test)]
1255mod tests {
1256 use super::*;
1257 use perl_parser_core::position::Position;
1258 use std::time::Instant;
1259
1260 fn adaptive_perf_budget_micros(base_budget_micros: u128) -> u128 {
1261 let thread_count = std::env::var("RUST_TEST_THREADS")
1262 .ok()
1263 .and_then(|value| value.parse::<usize>().ok())
1264 .unwrap_or_else(|| {
1265 std::thread::available_parallelism().map_or(8, std::num::NonZeroUsize::get)
1266 });
1267
1268 let mut budget = base_budget_micros;
1269 if thread_count <= 2 {
1270 budget = budget.saturating_mul(2);
1271 } else if thread_count <= 4 {
1272 budget = budget.saturating_mul(3) / 2;
1273 }
1274
1275 if std::env::var("CI").is_ok() {
1276 budget = budget.saturating_mul(3) / 2;
1277 }
1278
1279 budget
1280 }
1281
1282 #[test]
1283 fn test_basic_compilation() {
1284 let parser = IncrementalParserV2::new();
1285 assert_eq!(parser.reused_nodes, 0);
1286 assert_eq!(parser.reparsed_nodes, 0);
1287 }
1288
1289 #[test]
1290 fn clone_with_shifted_positions_preserves_if_keyword_metadata()
1291 -> Result<(), Box<dyn std::error::Error>> {
1292 let parser = IncrementalParserV2::new();
1293 let loc = |start, end| perl_parser_core::ast::SourceLocation { start, end };
1294 let number =
1295 |start| Node::new(NodeKind::Number { value: "1".to_string() }, loc(start, start + 1));
1296 let block = |start, end| {
1297 Node::new(NodeKind::Block { statements: vec![number(start + 1)] }, loc(start, end))
1298 };
1299 let node = Node::new(
1300 NodeKind::If {
1301 condition: Box::new(number(1)),
1302 then_branch: Box::new(block(4, 10)),
1303 elsif_branches: vec![(Box::new(number(12)), Box::new(block(14, 20)))],
1304 else_branch: Some(Box::new(block(22, 28))),
1305 keyword: Some("unless".to_string()),
1306 },
1307 loc(0, 29),
1308 );
1309
1310 let shifted = parser.clone_with_shifted_positions(&node, 3);
1311
1312 assert_eq!(shifted.location.start, 3);
1313 match shifted.kind {
1314 NodeKind::If { keyword, else_branch, .. } => {
1315 assert_eq!(keyword.as_deref(), Some("unless"));
1316 assert!(else_branch.is_some());
1317 }
1318 other => {
1319 return Err(format!("expected If node, got {}", other.kind_name()).into());
1320 }
1321 }
1322 Ok(())
1323 }
1324
1325 #[test]
1326 fn test_performance_timing_detailed() -> ParseResult<()> {
1327 let mut parser = IncrementalParserV2::new();
1328
1329 let source1 = "my $x = 42;";
1331 let start = Instant::now();
1332 let _tree1 = parser.parse(source1)?;
1333 let initial_parse_time = start.elapsed();
1334
1335 println!("Initial parse time: {:?}", initial_parse_time);
1336 println!("Initial nodes reparsed: {}", parser.reparsed_nodes);
1337
1338 parser.edit(Edit::new(
1340 8,
1341 10,
1342 12, Position::new(8, 1, 9),
1344 Position::new(10, 1, 11),
1345 Position::new(12, 1, 13),
1346 ));
1347
1348 let source2 = "my $x = 4242;";
1349 let start = Instant::now();
1350 let _tree2 = parser.parse(source2)?;
1351 let incremental_parse_time = start.elapsed();
1352
1353 println!("Incremental parse time: {:?}", incremental_parse_time);
1354 println!(
1355 "Incremental nodes reused: {}, reparsed: {}",
1356 parser.reused_nodes, parser.reparsed_nodes
1357 );
1358
1359 assert!(
1361 incremental_parse_time.as_micros() < 5000,
1362 "Incremental parse time should be <5ms, got {:?}",
1363 incremental_parse_time
1364 );
1365
1366 assert!(parser.reused_nodes >= 3, "Should reuse at least 3 nodes");
1368 assert_eq!(parser.reparsed_nodes, 1, "Should only reparse the changed Number node");
1369
1370 let speedup =
1372 initial_parse_time.as_nanos() as f64 / incremental_parse_time.as_nanos() as f64;
1373 println!("Performance improvement: {:.2}x faster", speedup);
1374
1375 if speedup >= 1.5 {
1378 println!("✅ Good speedup achieved: {:.2}x", speedup);
1379 } else {
1380 println!("⚠️ Limited speedup for micro-benchmark (expected for tiny examples)");
1381 }
1382
1383 Ok(())
1384 }
1385
1386 #[test]
1387 fn test_incremental_value_change() -> ParseResult<()> {
1388 let mut parser = IncrementalParserV2::new();
1389
1390 let source1 = "my $x = 42;";
1392 let start = Instant::now();
1393 let _tree1 = parser.parse(source1)?;
1394 let initial_time = start.elapsed();
1395
1396 assert_eq!(parser.reparsed_nodes, 4); println!(
1400 "Initial parse: {}µs, {} nodes parsed",
1401 initial_time.as_micros(),
1402 parser.reparsed_nodes
1403 );
1404
1405 parser.edit(Edit::new(
1407 8,
1408 10,
1409 12, Position::new(8, 1, 9),
1411 Position::new(10, 1, 11),
1412 Position::new(12, 1, 13),
1413 ));
1414
1415 let source2 = "my $x = 4242;";
1416 let start = Instant::now();
1417 let tree2 = parser.parse(source2)?;
1418 let incremental_time = start.elapsed();
1419
1420 println!(
1421 "Incremental parse: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1422 incremental_time.as_micros(),
1423 parser.reused_nodes,
1424 parser.reparsed_nodes
1425 );
1426 assert_eq!(parser.reused_nodes, 3); assert_eq!(parser.reparsed_nodes, 1); assert!(incremental_time.as_micros() < 500, "Incremental update should be <500µs");
1431 let efficiency =
1432 parser.reused_nodes as f64 / (parser.reused_nodes + parser.reparsed_nodes) as f64;
1433 assert!(
1434 efficiency >= 0.75,
1435 "Node reuse efficiency should be ≥75%, got {:.1}%",
1436 efficiency * 100.0
1437 );
1438
1439 if let NodeKind::Program { statements } = &tree2.kind {
1441 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1442 &statements[0].kind
1443 {
1444 if let NodeKind::Number { value } = &init.kind {
1445 assert_eq!(value, "4242");
1446 }
1447 }
1448 }
1449
1450 Ok(())
1451 }
1452
1453 #[test]
1454 fn test_multiple_value_changes() -> ParseResult<()> {
1455 let mut parser = IncrementalParserV2::new();
1456
1457 let source1 = "my $x = 10;\nmy $y = 20;";
1459 let start = Instant::now();
1460 parser.parse(source1)?;
1461 let initial_time = start.elapsed();
1462 let initial_nodes = parser.reparsed_nodes;
1463
1464 println!(
1465 "Initial parse (multi-statement): {}µs, {} nodes",
1466 initial_time.as_micros(),
1467 initial_nodes
1468 );
1469
1470 parser.edit(Edit::new(
1472 8,
1473 10,
1474 11, Position::new(8, 1, 9),
1476 Position::new(10, 1, 11),
1477 Position::new(11, 1, 12),
1478 ));
1479
1480 parser.edit(Edit::new(
1481 21,
1482 23,
1483 24, Position::new(21, 2, 9),
1485 Position::new(23, 2, 11),
1486 Position::new(24, 2, 12),
1487 ));
1488
1489 let source2 = "my $x = 100;\nmy $y = 200;";
1490 let start = Instant::now();
1491 let tree = parser.parse(source2)?;
1492 let incremental_time = start.elapsed();
1493
1494 println!(
1495 "Multiple edits: {}µs, reused_nodes = {}, reparsed_nodes = {}",
1496 incremental_time.as_micros(),
1497 parser.reused_nodes,
1498 parser.reparsed_nodes
1499 );
1500 assert!(
1503 parser.reused_nodes >= 5,
1504 "Should reuse at least 5 nodes, got {}",
1505 parser.reused_nodes
1506 );
1507 assert!(
1508 parser.reparsed_nodes >= 1,
1509 "Should reparse at least 1 node, got {}",
1510 parser.reparsed_nodes
1511 );
1512
1513 assert!(incremental_time.as_micros() < 5000, "Multiple edits should be <5ms");
1515 let total_nodes = parser.reused_nodes + parser.reparsed_nodes;
1516 let reuse_ratio = parser.reused_nodes as f64 / total_nodes as f64;
1517 assert!(
1518 reuse_ratio >= 0.7,
1519 "Multi-edit reuse ratio should be ≥70%, got {:.1}%",
1520 reuse_ratio * 100.0
1521 );
1522
1523 if let NodeKind::Program { statements } = &tree.kind {
1525 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1526 &statements[0].kind
1527 {
1528 if let NodeKind::Number { value } = &init.kind {
1529 assert_eq!(value, "100");
1530 }
1531 }
1532 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1533 &statements[1].kind
1534 {
1535 if let NodeKind::Number { value } = &init.kind {
1536 assert_eq!(value, "200");
1537 }
1538 }
1539 }
1540
1541 Ok(())
1542 }
1543
1544 #[test]
1545 fn test_too_many_edits_fallback() -> ParseResult<()> {
1546 let mut parser = IncrementalParserV2::new();
1547
1548 let source1 = "my $x = 1;";
1550 parser.parse(source1)?;
1551
1552 for i in 0..15 {
1554 parser.edit(Edit::new(
1555 8 + i,
1556 9 + i,
1557 10 + i,
1558 Position::new(8 + i, 1, (9 + i) as u32),
1559 Position::new(9 + i, 1, (10 + i) as u32),
1560 Position::new(10 + i, 1, (11 + i) as u32),
1561 ));
1562 }
1563
1564 let source2 = "my $x = 123456789012345;";
1565 let tree = parser.parse(source2)?;
1566
1567 assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1570 if let NodeKind::Program { statements } = &tree.kind {
1574 assert_eq!(statements.len(), 1);
1575 }
1576
1577 Ok(())
1578 }
1579
1580 #[test]
1581 fn test_invalid_edit_bounds() -> ParseResult<()> {
1582 let mut parser = IncrementalParserV2::new();
1583
1584 let source1 = "my $x = 42;";
1586 parser.parse(source1)?;
1587
1588 parser.edit(Edit::new(
1590 8,
1591 12, 13,
1593 Position::new(8, 1, 9),
1594 Position::new(12, 1, 13),
1595 Position::new(13, 1, 14),
1596 ));
1597
1598 let source2 = "my $x = 123;";
1599 let tree = parser.parse(source2)?;
1600
1601 assert!(parser.reparsed_nodes > 0, "Should reparse some nodes");
1604 if let NodeKind::Program { statements } = &tree.kind {
1608 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1609 &statements[0].kind
1610 {
1611 if let NodeKind::Number { value } = &init.kind {
1612 assert_eq!(value, "123");
1613 }
1614 }
1615 }
1616
1617 Ok(())
1618 }
1619
1620 #[test]
1621 fn test_string_edit() -> ParseResult<()> {
1622 let mut parser = IncrementalParserV2::new();
1623
1624 let source1 = "my $name = \"hello\";";
1626 parser.parse(source1)?;
1627
1628 parser.edit(Edit::new(
1630 12,
1631 17, 17,
1633 Position::new(12, 1, 13),
1634 Position::new(17, 1, 18),
1635 Position::new(17, 1, 18),
1636 ));
1637
1638 let source2 = "my $name = \"world\";";
1639 let tree = parser.parse(source2)?;
1640
1641 println!(
1643 "DEBUG test_string_edit: reused_nodes = {}, reparsed_nodes = {}",
1644 parser.reused_nodes, parser.reparsed_nodes
1645 );
1646 assert_eq!(parser.reused_nodes, 3); assert_eq!(parser.reparsed_nodes, 1); if let NodeKind::Program { statements } = &tree.kind {
1651 if let NodeKind::VariableDeclaration { initializer: Some(init), .. } =
1652 &statements[0].kind
1653 {
1654 if let NodeKind::String { value, .. } = &init.kind {
1655 assert_eq!(value, "\"world\"");
1656 }
1657 }
1658 }
1659
1660 Ok(())
1661 }
1662
1663 #[test]
1664 fn test_empty_source_handling() -> ParseResult<()> {
1665 let mut parser = IncrementalParserV2::new();
1666
1667 let source1 = "my $x = 42;";
1669 let start = Instant::now();
1670 parser.parse(source1)?;
1671 let initial_time = start.elapsed();
1672 println!("Initial parse time: {}µs", initial_time.as_micros());
1673
1674 parser.edit(Edit::new(
1676 8,
1677 10,
1678 11,
1679 Position::new(8, 1, 9),
1680 Position::new(10, 1, 11),
1681 Position::new(11, 1, 12),
1682 ));
1683
1684 let source2 = "";
1686 let start = Instant::now();
1687 let result = parser.parse(source2);
1688 let parse_time = start.elapsed();
1689
1690 println!("Empty source parse time: {}µs", parse_time.as_micros());
1691
1692 match result {
1694 Ok(_) => {
1695 assert_eq!(parser.reused_nodes, 0);
1697 println!("Empty source parsing succeeded with fallback");
1698 }
1699 Err(_) => {
1700 assert_eq!(parser.reused_nodes, 0);
1702 println!("Empty source parsing failed gracefully (expected)");
1703 }
1704 }
1705
1706 assert!(parse_time.as_millis() < 100, "Empty source handling should be fast");
1708
1709 Ok(())
1710 }
1711
1712 #[test]
1713 fn test_complex_nested_structure_edits() -> ParseResult<()> {
1714 let mut parser = IncrementalParserV2::new();
1715
1716 let source1 = r#"
1718if ($condition) {
1719 my $nested = {
1720 key1 => "value1",
1721 key2 => 42,
1722 key3 => [1, 2, 3]
1723 };
1724 process($nested);
1725}
1726"#;
1727
1728 let start = Instant::now();
1729 parser.parse(source1)?;
1730 let initial_time = start.elapsed();
1731 let initial_nodes = parser.reparsed_nodes;
1732
1733 println!(
1734 "Complex structure initial parse: {}µs, {} nodes",
1735 initial_time.as_micros(),
1736 initial_nodes
1737 );
1738
1739 let value_start =
1741 source1.find("42").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1742 parser.edit(Edit::new(
1743 value_start,
1744 value_start + 2,
1745 value_start + 4, Position::new(value_start, 1, 1),
1747 Position::new(value_start + 2, 1, 3),
1748 Position::new(value_start + 4, 1, 5),
1749 ));
1750
1751 let source2 = source1.replace("42", "9999");
1752 let start = Instant::now();
1753 let _tree = parser.parse(&source2)?;
1754 let incremental_time = start.elapsed();
1755
1756 println!(
1757 "Complex nested edit: {}µs, reused={}, reparsed={}",
1758 incremental_time.as_micros(),
1759 parser.reused_nodes,
1760 parser.reparsed_nodes
1761 );
1762
1763 assert!(incremental_time.as_millis() < 10, "Complex nested edit should be <10ms");
1765
1766 if parser.reused_nodes > 0 {
1768 println!("Successfully reused {} nodes in complex structure", parser.reused_nodes);
1769 } else {
1770 println!("Complex structure caused full reparse (acceptable for edge cases)");
1771 }
1772
1773 Ok(())
1774 }
1775
1776 #[test]
1777 fn test_large_document_performance() -> ParseResult<()> {
1778 let mut parser = IncrementalParserV2::new();
1779
1780 let mut large_source = String::new();
1782 for i in 0..100 {
1783 large_source.push_str(&format!("my $var{} = {};\n", i, i * 10));
1784 }
1785
1786 let start = Instant::now();
1787 parser.parse(&large_source)?;
1788 let initial_time = start.elapsed();
1789 let initial_nodes = parser.reparsed_nodes;
1790
1791 println!(
1792 "Large document initial parse: {}ms, {} nodes",
1793 initial_time.as_millis(),
1794 initial_nodes
1795 );
1796
1797 let edit_pos = large_source
1799 .find("my $var50 = 500")
1800 .ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?
1801 + 13;
1802 parser.edit(Edit::new(
1803 edit_pos,
1804 edit_pos + 3, edit_pos + 3,
1806 Position::new(edit_pos, 1, 1),
1807 Position::new(edit_pos + 3, 1, 4),
1808 Position::new(edit_pos + 3, 1, 4),
1809 ));
1810
1811 let source2 = large_source.replace("500", "999");
1812 let start = Instant::now();
1813 let _tree = parser.parse(&source2)?;
1814 let incremental_time = start.elapsed();
1815
1816 println!(
1817 "Large document incremental: {}ms, reused={}, reparsed={}",
1818 incremental_time.as_millis(),
1819 parser.reused_nodes,
1820 parser.reparsed_nodes
1821 );
1822
1823 assert!(incremental_time.as_millis() < 50, "Large document incremental should be <50ms");
1825
1826 if parser.reused_nodes > 0 {
1828 let reuse_percentage = parser.reused_nodes as f64
1829 / (parser.reused_nodes + parser.reparsed_nodes) as f64
1830 * 100.0;
1831 println!("Large document reuse rate: {:.1}%", reuse_percentage);
1832 assert!(reuse_percentage > 50.0, "Large document should reuse >50% of nodes");
1833 }
1834
1835 Ok(())
1836 }
1837
1838 #[test]
1839 fn test_unicode_heavy_incremental_parsing() -> ParseResult<()> {
1840 let mut parser = IncrementalParserV2::new();
1841
1842 let source1 = "my $🌟variable = '你好世界'; # Comment with emoji 🚀\nmy $café = 'résumé';";
1844
1845 let start = Instant::now();
1846 parser.parse(source1)?;
1847 let initial_time = start.elapsed();
1848
1849 println!("Unicode document initial parse: {}µs", initial_time.as_micros());
1850
1851 let edit_start =
1853 source1.find("你好世界").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
1854 let edit_end = edit_start + "你好世界".len();
1855 parser.edit(Edit::new(
1856 edit_start,
1857 edit_end,
1858 edit_start + "再见".len(), Position::new(edit_start, 1, 1),
1860 Position::new(edit_end, 1, 2),
1861 Position::new(edit_start + "再见".len(), 1, 2),
1862 ));
1863
1864 let source2 = source1.replace("你好世界", "再见");
1865 let start = Instant::now();
1866 let _tree = parser.parse(&source2)?;
1867 let incremental_time = start.elapsed();
1868
1869 println!(
1870 "Unicode incremental edit: {}µs, reused={}, reparsed={}",
1871 incremental_time.as_micros(),
1872 parser.reused_nodes,
1873 parser.reparsed_nodes
1874 );
1875
1876 let unicode_budget_micros = adaptive_perf_budget_micros(5_000);
1878 assert!(
1879 incremental_time.as_micros() < unicode_budget_micros,
1880 "Unicode incremental parsing should be <{}µs (got {}µs)",
1881 unicode_budget_micros,
1882 incremental_time.as_micros()
1883 );
1884 assert!(parser.reused_nodes > 0 || parser.reparsed_nodes > 0, "Should parse successfully");
1885
1886 Ok(())
1887 }
1888
1889 #[test]
1890 fn test_edit_near_ast_node_boundaries() -> ParseResult<()> {
1891 let mut parser = IncrementalParserV2::new();
1892
1893 let source1 = "sub func { my $x = 123; return $x * 2; }";
1895
1896 parser.parse(source1)?;
1897
1898 let number_end =
1900 source1.find("123").ok_or(perl_parser_core::error::ParseError::UnexpectedEof)? + 3;
1901 parser.edit(Edit::new(
1902 number_end - 1, number_end,
1904 number_end + 1, Position::new(number_end - 1, 1, 1),
1906 Position::new(number_end, 1, 2),
1907 Position::new(number_end + 1, 1, 3),
1908 ));
1909
1910 let source2 = source1.replace("123", "12456");
1911 let start = Instant::now();
1912 let _tree = parser.parse(&source2)?;
1913 let boundary_edit_time = start.elapsed();
1914
1915 println!(
1916 "Boundary edit time: {}µs, reused={}, reparsed={}",
1917 boundary_edit_time.as_micros(),
1918 parser.reused_nodes,
1919 parser.reparsed_nodes
1920 );
1921
1922 let boundary_budget_micros = adaptive_perf_budget_micros(5_000);
1924 assert!(
1925 boundary_edit_time.as_micros() < boundary_budget_micros,
1926 "AST boundary edit should be <{}µs (got {}µs)",
1927 boundary_budget_micros,
1928 boundary_edit_time.as_micros()
1929 );
1930 assert!(parser.reparsed_nodes >= 1, "Should reparse at least the modified node");
1931
1932 Ok(())
1933 }
1934
1935 #[test]
1936 fn test_whitespace_insertion_reuses_tree() -> ParseResult<()> {
1937 let mut parser = IncrementalParserV2::new();
1938 let source1 = "my $x = 42;";
1939 parser.parse(source1)?;
1940
1941 parser.edit(Edit::new(
1942 5,
1943 5,
1944 7,
1945 Position::new(5, 1, 6),
1946 Position::new(5, 1, 6),
1947 Position::new(7, 1, 8),
1948 ));
1949
1950 let source2 = "my $x = 42;";
1951 parser.parse(source2)?;
1952
1953 assert!(parser.reused_nodes > 0, "Whitespace insertion should reuse prior tree");
1954 assert!(parser.reparsed_nodes <= 1, "Whitespace insertion should avoid broad reparsing");
1955 Ok(())
1956 }
1957
1958 #[test]
1959 fn test_performance_regression_detection() -> ParseResult<()> {
1960 let mut parser = IncrementalParserV2::new();
1961
1962 let source = "my $baseline = 42; my $test = 'hello';";
1964 let mut parse_times = Vec::new();
1965
1966 for i in 0..10 {
1968 let start = Instant::now();
1969 parser.parse(source)?;
1970 let time = start.elapsed();
1971 parse_times.push(time.as_micros());
1972
1973 parser.edit(Edit::new(
1975 15,
1976 17,
1977 19, Position::new(15, 1, 16),
1979 Position::new(17, 1, 18),
1980 Position::new(19, 1, 20),
1981 ));
1982
1983 let test_source = if i % 2 == 0 {
1985 "my $baseline = 99; my $test = 'hello';"
1986 } else {
1987 "my $baseline = 42; my $test = 'hello';"
1988 };
1989
1990 let start = Instant::now();
1991 parser.parse(test_source)?;
1992 let incremental_time = start.elapsed();
1993
1994 println!(
1995 "Run {}: initial={}µs, incremental={}µs, reused={}, reparsed={}",
1996 i + 1,
1997 time.as_micros(),
1998 incremental_time.as_micros(),
1999 parser.reused_nodes,
2000 parser.reparsed_nodes
2001 );
2002
2003 assert!(
2005 incremental_time.as_millis() < 10,
2006 "Run {} performance regression detected: {}ms",
2007 i + 1,
2008 incremental_time.as_millis()
2009 );
2010 }
2011
2012 let avg_time = parse_times.iter().sum::<u128>() / parse_times.len() as u128;
2014 let max_time =
2015 *parse_times.iter().max().ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
2016 let min_time =
2017 *parse_times.iter().min().ok_or(perl_parser_core::error::ParseError::UnexpectedEof)?;
2018
2019 println!(
2020 "Performance statistics: avg={}µs, min={}µs, max={}µs",
2021 avg_time, min_time, max_time
2022 );
2023
2024 let variation_factor = max_time as f64 / avg_time as f64;
2025 assert!(
2026 variation_factor <= 10.0,
2027 "Extreme performance inconsistency detected: max={}µs, avg={}µs ({}x variation)",
2028 max_time,
2029 avg_time,
2030 variation_factor
2031 );
2032 if variation_factor > 5.0 {
2033 println!(
2034 "⚠️ High performance variation detected: max={}µs, avg={}µs ({}x variation) - may indicate system load",
2035 max_time, avg_time, variation_factor
2036 );
2037 }
2038
2039 Ok(())
2040 }
2041
2042 #[test]
2044 fn test_whitespace_before_operator_reuses_tree() -> ParseResult<()> {
2045 let mut parser = IncrementalParserV2::new();
2046 let source1 = "my $x = 42;";
2047 parser.parse(source1)?;
2048 parser.edit(Edit::new(
2049 6,
2050 6,
2051 9,
2052 Position::new(6, 1, 7),
2053 Position::new(6, 1, 7),
2054 Position::new(9, 1, 10),
2055 ));
2056 let source2 = "my $x = 42;";
2057 parser.parse(source2)?;
2058 assert!(parser.reused_nodes > 0);
2059 Ok(())
2060 }
2061
2062 #[test]
2063 fn test_comment_insertion_reuses_tree() -> ParseResult<()> {
2064 let mut parser = IncrementalParserV2::new();
2065 let source1 = "my $x = 42;";
2066 parser.parse(source1)?;
2067 parser.edit(Edit::new(
2068 10,
2069 11,
2070 24,
2071 Position::new(10, 1, 11),
2072 Position::new(11, 1, 12),
2073 Position::new(24, 1, 25),
2074 ));
2075 let source2 = "my $x = 42; # comment";
2076 parser.parse(source2)?;
2077 assert!(parser.reused_nodes > 0);
2078 Ok(())
2079 }
2080
2081 #[test]
2082 fn test_trailing_whitespace_reuses_tree() -> ParseResult<()> {
2083 let mut parser = IncrementalParserV2::new();
2084 let source1 = "my $x = 42;";
2085 parser.parse(source1)?;
2086 parser.edit(Edit::new(
2087 11,
2088 11,
2089 16,
2090 Position::new(11, 1, 12),
2091 Position::new(11, 1, 12),
2092 Position::new(16, 1, 17),
2093 ));
2094 let source2 = "my $x = 42; ";
2095 parser.parse(source2)?;
2096 assert!(parser.reused_nodes > 0);
2097 Ok(())
2098 }
2099
2100 #[test]
2101 fn test_newline_insertion_reuses_tree() -> ParseResult<()> {
2102 let mut parser = IncrementalParserV2::new();
2103 let source1 = "my $x = 42;";
2104 parser.parse(source1)?;
2105 parser.edit(Edit::new(
2106 11,
2107 11,
2108 12,
2109 Position::new(11, 1, 12),
2110 Position::new(11, 1, 12),
2111 Position::new(12, 2, 1),
2112 ));
2113 let source2 = "my $x = 42;\n";
2114 parser.parse(source2)?;
2115 assert!(parser.reused_nodes > 0);
2116 Ok(())
2117 }
2118
2119 #[test]
2120 fn test_whitespace_deletion_reuses_tree() -> ParseResult<()> {
2121 let mut parser = IncrementalParserV2::new();
2122 let source1 = "my $x = 42;";
2123 parser.parse(source1)?;
2124 parser.edit(Edit::new(
2125 3,
2126 5,
2127 4,
2128 Position::new(3, 1, 4),
2129 Position::new(5, 1, 6),
2130 Position::new(4, 1, 5),
2131 ));
2132 let source2 = "my $x = 42;";
2133 parser.parse(source2)?;
2134 assert!(parser.reused_nodes > 0);
2135 Ok(())
2136 }
2137
2138 #[test]
2139 fn test_whitespace_at_statement_boundary() -> ParseResult<()> {
2140 let mut parser = IncrementalParserV2::new();
2141 let source1 = "print 'hello';my $x = 42;";
2142 parser.parse(source1)?;
2143 parser.edit(Edit::new(
2144 14,
2145 14,
2146 15,
2147 Position::new(14, 1, 15),
2148 Position::new(14, 1, 15),
2149 Position::new(15, 1, 16),
2150 ));
2151 let source2 = "print 'hello'; my $x = 42;";
2152 parser.parse(source2)?;
2153 assert!(parser.reused_nodes > 0);
2154 Ok(())
2155 }
2156
2157 #[test]
2158 fn test_whitespace_edit_checks_both_old_and_new() -> ParseResult<()> {
2159 let mut parser = IncrementalParserV2::new();
2160 let source1 = "my $x = 42;";
2161 parser.parse(source1)?;
2162 parser.edit(Edit::new(
2163 6,
2164 7,
2165 8,
2166 Position::new(6, 1, 7),
2167 Position::new(7, 1, 8),
2168 Position::new(8, 1, 9),
2169 ));
2170 let source2 = "my $x= 42;";
2171 parser.parse(source2)?;
2172 assert!(parser.reused_nodes > 0);
2173 Ok(())
2174 }
2175}