perl_incremental_parsing/incremental/
incremental_edit.rs1use crate::position::Position;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct IncrementalEdit {
11 pub start_byte: usize,
13 pub old_end_byte: usize,
15 pub new_text: String,
17 pub start_position: Position,
19 pub old_end_position: Position,
21}
22
23impl IncrementalEdit {
24 pub fn new(start_byte: usize, old_end_byte: usize, new_text: String) -> Self {
26 IncrementalEdit {
27 start_byte,
28 old_end_byte,
29 new_text,
30 start_position: Position::new(start_byte, 0, 0),
31 old_end_position: Position::new(old_end_byte, 0, 0),
32 }
33 }
34
35 pub fn with_positions(
37 start_byte: usize,
38 old_end_byte: usize,
39 new_text: String,
40 start_position: Position,
41 old_end_position: Position,
42 ) -> Self {
43 IncrementalEdit { start_byte, old_end_byte, new_text, start_position, old_end_position }
44 }
45
46 pub fn new_end_byte(&self) -> usize {
48 self.start_byte + self.new_text.len()
49 }
50
51 pub fn byte_shift(&self) -> isize {
53 self.new_text.len() as isize - (self.old_end_byte - self.start_byte) as isize
54 }
55
56 pub fn overlaps(&self, start: usize, end: usize) -> bool {
58 self.start_byte < end && self.old_end_byte > start
59 }
60
61 pub fn is_before(&self, pos: usize) -> bool {
63 self.old_end_byte <= pos
64 }
65
66 pub fn is_after(&self, pos: usize) -> bool {
68 self.start_byte >= pos
69 }
70}
71
72#[derive(Debug, Clone, Default)]
74pub struct IncrementalEditSet {
75 pub edits: Vec<IncrementalEdit>,
76}
77
78impl IncrementalEditSet {
79 pub fn new() -> Self {
81 IncrementalEditSet { edits: Vec::new() }
82 }
83
84 pub fn add(&mut self, edit: IncrementalEdit) {
86 self.edits.push(edit);
87 }
88
89 pub fn sort(&mut self) {
91 self.edits.sort_by_key(|e| e.start_byte);
92 }
93
94 pub fn sort_reverse(&mut self) {
96 self.edits.sort_by_key(|e| std::cmp::Reverse(e.start_byte));
97 }
98
99 pub fn is_empty(&self) -> bool {
101 self.edits.is_empty()
102 }
103
104 pub fn total_byte_shift(&self) -> isize {
106 self.edits.iter().map(|e| e.byte_shift()).sum()
107 }
108
109 pub fn apply_to_string(&self, source: &str) -> String {
111 if self.edits.is_empty() {
112 return source.to_string();
113 }
114
115 let mut sorted_edits = self.edits.clone();
117 sorted_edits.sort_by_key(|e| std::cmp::Reverse(e.start_byte));
118
119 let mut result = source.to_string();
120 for edit in &sorted_edits {
121 result.replace_range(edit.start_byte..edit.old_end_byte, &edit.new_text);
122 }
123
124 result
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_incremental_edit_basic() {
134 let edit = IncrementalEdit::new(5, 10, "hello".to_string());
135 assert_eq!(edit.new_end_byte(), 10);
136 assert_eq!(edit.byte_shift(), 0);
137 }
138
139 #[test]
140 fn test_incremental_edit_insertion() {
141 let edit = IncrementalEdit::new(5, 5, "inserted".to_string());
142 assert_eq!(edit.new_end_byte(), 13);
143 assert_eq!(edit.byte_shift(), 8);
144 }
145
146 #[test]
147 fn test_incremental_edit_deletion() {
148 let edit = IncrementalEdit::new(5, 15, "".to_string());
149 assert_eq!(edit.new_end_byte(), 5);
150 assert_eq!(edit.byte_shift(), -10);
151 }
152
153 #[test]
154 fn test_incremental_edit_replacement() {
155 let edit = IncrementalEdit::new(5, 10, "replaced".to_string());
156 assert_eq!(edit.new_end_byte(), 13);
157 assert_eq!(edit.byte_shift(), 3);
158 }
159
160 #[test]
161 fn test_edit_set_apply() {
162 let mut edits = IncrementalEditSet::new();
163 edits.add(IncrementalEdit::new(0, 5, "Hello".to_string()));
164 edits.add(IncrementalEdit::new(6, 11, "Perl".to_string()));
165
166 let source = "hello world";
167 let result = edits.apply_to_string(source);
168 assert_eq!(result, "Hello Perl");
169 }
170}