1use crate::algorithm::{
3 change::{Change, DeltaType},
4 diff_algorithm_listener::DiffAlgorithmListener,
5 DiffAlgorithm,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10struct Snake {
11 start: usize,
12 end: usize,
13 diag: isize,
14}
15
16pub struct MyersDiffWithLinearSpace<T> {
17 equalizer: Option<Box<dyn Fn(&T, &T) -> bool>>,
18}
19
20impl<T> Default for MyersDiffWithLinearSpace<T> {
21 fn default() -> Self {
22 Self { equalizer: None }
23 }
24}
25
26impl<T> MyersDiffWithLinearSpace<T> {
27 pub fn new() -> Self {
28 Self::default()
29 }
30
31 pub fn with_equalizer<F>(equalizer: F) -> Self
32 where
33 F: Fn(&T, &T) -> bool + 'static,
34 {
35 Self {
36 equalizer: Some(Box::new(equalizer)),
37 }
38 }
39}
40
41impl<T: PartialEq> DiffAlgorithm<T> for MyersDiffWithLinearSpace<T> {
42 fn diff_with_listener(
43 &self,
44 source: &[T],
45 target: &[T],
46 listener: &mut dyn DiffAlgorithmListener,
47 ) -> Vec<Change> {
48 let mut ws = LinearWorkspace::new();
49 if let Some(ref eq) = self.equalizer {
50 compute_diff_full(source, target, eq, &mut ws, Some(listener))
51 } else {
52 compute_diff_full(source, target, |a, b| a == b, &mut ws, Some(listener))
53 }
54 }
55}
56
57#[derive(Default)]
59pub struct LinearWorkspace {
60 v_down: Vec<usize>,
61 v_up: Vec<usize>,
62}
63
64impl LinearWorkspace {
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 fn prepare_buffers(&mut self, required_len: usize) {
70 if self.v_down.len() < required_len {
71 self.v_down.resize(required_len, 0);
72 self.v_up.resize(required_len, 0);
73 } else {
74 self.v_down[..required_len].fill(0);
75 self.v_up[..required_len].fill(0);
76 }
77 }
78}
79
80pub struct NoOpListener;
82impl DiffAlgorithmListener for NoOpListener {}
83
84pub fn compute_diff<T: PartialEq>(source: &[T], target: &[T]) -> Vec<Change> {
85 compute_diff_with(source, target, |a, b| a == b)
86}
87
88pub fn compute_diff_with<T, F>(source: &[T], target: &[T], equalizer: F) -> Vec<Change>
89where
90 F: Fn(&T, &T) -> bool,
91{
92 let mut workspace = LinearWorkspace::new();
93 compute_diff_full(
94 source,
95 target,
96 equalizer,
97 &mut workspace,
98 Option::<&mut NoOpListener>::None,
99 )
100}
101
102pub fn compute_diff_full<T, F, L>(
103 source: &[T],
104 target: &[T],
105 equalizer: F,
106 workspace: &mut LinearWorkspace,
107 mut listener: Option<&mut L>,
108) -> Vec<Change>
109where
110 F: Fn(&T, &T) -> bool,
111 L: DiffAlgorithmListener + ?Sized,
112{
113 if source.is_empty() && target.is_empty() {
114 return Vec::new();
115 }
116
117 if let Some(l) = listener.as_deref_mut() {
118 l.diff_start();
119 }
120
121 let buffer_size = source.len() + target.len() + 2;
122 workspace.prepare_buffers(buffer_size);
123
124 let mut script = Vec::new();
125 let max_steps = source.len() + target.len();
126
127 partition_and_build(
128 source,
129 target,
130 &equalizer,
131 SubRegion {
132 src_start: 0,
133 src_end: source.len(),
134 tgt_start: 0,
135 tgt_end: target.len(),
136 },
137 workspace,
138 &mut script,
139 listener.as_deref_mut(),
140 max_steps,
141 );
142
143 if let Some(l) = listener {
144 l.diff_end();
145 }
146
147 script
148}
149
150#[derive(Clone, Copy)]
152struct SubRegion {
153 src_start: usize,
154 src_end: usize,
155 tgt_start: usize,
156 tgt_end: usize,
157}
158
159fn push_change(
160 script: &mut Vec<Change>,
161 delta_type: DeltaType,
162 src_start: usize,
163 src_end: usize,
164 tgt_start: usize,
165 tgt_end: usize,
166) {
167 if let Some(last) = script.last_mut() {
169 if last.delta_type == delta_type {
170 match delta_type {
171 DeltaType::Delete if last.end_original == src_start => {
172 last.end_original = src_end;
173 return;
174 }
175 DeltaType::Insert if last.end_revised == tgt_start => {
176 last.end_revised = tgt_end;
177 return;
178 }
179 _ => {}
180 }
181 }
182 }
183
184 script.push(Change {
185 delta_type,
186 start_original: src_start,
187 end_original: src_end,
188 start_revised: tgt_start,
189 end_revised: tgt_end,
190 });
191}
192
193fn partition_and_build<T, F, L>(
194 source: &[T],
195 target: &[T],
196 equalizer: &F,
197 region: SubRegion,
198 ws: &mut LinearWorkspace,
199 script: &mut Vec<Change>,
200 mut listener: Option<&mut L>,
201 max_steps: usize,
202) where
203 F: Fn(&T, &T) -> bool,
204 L: DiffAlgorithmListener + ?Sized,
205{
206 if let Some(l) = listener.as_deref_mut() {
207 let step =
208 (region.src_end - region.src_start) / 2 + (region.tgt_end - region.tgt_start) / 2;
209 l.diff_step(step, max_steps);
210 }
211
212 let middle_snake = find_middle_snake(source, target, equalizer, region, ws);
213
214 let reached_terminal = match middle_snake {
215 None => true,
216 Some(s) => {
217 let diag_offset = region.src_end as isize - region.tgt_end as isize;
218 let start_offset = region.src_start as isize - region.tgt_start as isize;
219
220 (s.start == region.src_end && s.diag == diag_offset)
221 || (s.end == region.src_start && s.diag == start_offset)
222 }
223 };
224
225 if reached_terminal {
226 let mut i = region.src_start;
227 let mut j = region.tgt_start;
228
229 while i < region.src_end || j < region.tgt_end {
230 if i < region.src_end && j < region.tgt_end && equalizer(&source[i], &target[j]) {
231 i += 1;
232 j += 1;
233 } else if (region.src_end - i) > (region.tgt_end - j) {
234 push_change(script, DeltaType::Delete, i, i + 1, j, j);
235 i += 1;
236 } else {
237 push_change(script, DeltaType::Insert, i, i, j, j + 1);
238 j += 1;
239 }
240 }
241 } else if let Some(snake) = middle_snake {
242 let mid_tgt_1 = (snake.start as isize - snake.diag) as usize;
243 let mid_tgt_2 = (snake.end as isize - snake.diag) as usize;
244
245 partition_and_build(
247 source,
248 target,
249 equalizer,
250 SubRegion {
251 src_start: region.src_start,
252 src_end: snake.start,
253 tgt_start: region.tgt_start,
254 tgt_end: mid_tgt_1,
255 },
256 ws,
257 script,
258 listener.as_deref_mut(),
259 max_steps,
260 );
261
262 partition_and_build(
264 source,
265 target,
266 equalizer,
267 SubRegion {
268 src_start: snake.end,
269 src_end: region.src_end,
270 tgt_start: mid_tgt_2,
271 tgt_end: region.tgt_end,
272 },
273 ws,
274 script,
275 listener,
276 max_steps,
277 );
278 }
279}
280
281fn find_middle_snake<T, F>(
282 source: &[T],
283 target: &[T],
284 equalizer: &F,
285 region: SubRegion,
286 ws: &mut LinearWorkspace,
287) -> Option<Snake>
288where
289 F: Fn(&T, &T) -> bool,
290{
291 let src_len = region.src_end - region.src_start;
292 let tgt_len = region.tgt_end - region.tgt_start;
293
294 if src_len == 0 || tgt_len == 0 {
295 return None;
296 }
297
298 let delta = src_len as isize - tgt_len as isize;
299 let total_len = tgt_len + src_len;
300 let offset = if total_len.is_multiple_of(2) {
301 total_len
302 } else {
303 total_len + 1
304 } / 2;
305
306 ws.v_down[1 + offset] = region.src_start;
307 ws.v_up[1 + offset] = region.src_end + 1;
308
309 for d in 0..=offset {
310 let d_step = d as isize;
311
312 for k in (-d_step..=d_step).step_by(2) {
314 let idx = (k + offset as isize) as usize;
315
316 if k == -d_step || (k != d_step && ws.v_down[idx - 1] < ws.v_down[idx + 1]) {
317 ws.v_down[idx] = ws.v_down[idx + 1];
318 } else {
319 ws.v_down[idx] = ws.v_down[idx - 1] + 1;
320 }
321
322 let mut x = ws.v_down[idx];
323 let mut y =
324 (x as isize - region.src_start as isize + region.tgt_start as isize - k) as usize;
325
326 while x < region.src_end && y < region.tgt_end && equalizer(&source[x], &target[y]) {
327 x += 1;
328 ws.v_down[idx] = x;
329 y += 1;
330 }
331
332 if delta % 2 != 0 && (delta - d_step) <= k && k <= (delta + d_step) {
333 let up_idx = (idx as isize - delta) as usize;
334 if ws.v_up.get(up_idx).is_some_and(|&v| v <= ws.v_down[idx]) {
335 return Some(expand_snake(
336 source,
337 target,
338 equalizer,
339 ws.v_up[up_idx],
340 k + region.src_start as isize - region.tgt_start as isize,
341 region.src_end,
342 region.tgt_end,
343 ));
344 }
345 }
346 }
347
348 let k_min = delta - d_step;
350 let k_max = delta + d_step;
351 for k in (k_min..=k_max).step_by(2) {
352 let idx = (k + offset as isize - delta) as usize;
353
354 if k == k_min || (k != k_max && ws.v_up[idx + 1] <= ws.v_up[idx - 1]) {
355 ws.v_up[idx] = ws.v_up[idx + 1].saturating_sub(1);
356 } else {
357 ws.v_up[idx] = ws.v_up[idx - 1];
358 }
359
360 let mut x = ws.v_up[idx].saturating_sub(1);
361 let mut y =
362 (x as isize - region.src_start as isize + region.tgt_start as isize - k) as usize;
363
364 while x >= region.src_start
365 && y >= region.tgt_start
366 && x < region.src_end
367 && y < region.tgt_end
368 && equalizer(&source[x], &target[y])
369 {
370 ws.v_up[idx] = x;
371 if x == 0 || y == 0 {
372 break;
373 }
374 x -= 1;
375 y -= 1;
376 }
377
378 if delta % 2 == 0 && -d_step <= k && k <= d_step {
379 let down_idx = (idx as isize + delta) as usize;
380 if ws.v_down.get(down_idx).is_some_and(|&v| ws.v_up[idx] <= v) {
381 return Some(expand_snake(
382 source,
383 target,
384 equalizer,
385 ws.v_up[idx],
386 k + region.src_start as isize - region.tgt_start as isize,
387 region.src_end,
388 region.tgt_end,
389 ));
390 }
391 }
392 }
393 }
394
395 Some(Snake {
396 start: region.src_start,
397 end: region.src_end,
398 diag: region.src_start as isize - region.tgt_start as isize,
399 })
400}
401
402fn expand_snake<T, F>(
403 source: &[T],
404 target: &[T],
405 equalizer: &F,
406 start: usize,
407 diag: isize,
408 src_bound: usize,
409 tgt_bound: usize,
410) -> Snake
411where
412 F: Fn(&T, &T) -> bool,
413{
414 let mut end = start;
415 while (end as isize - diag) < tgt_bound as isize
416 && end < src_bound
417 && equalizer(&source[end], &target[(end as isize - diag) as usize])
418 {
419 end += 1;
420 }
421
422 Snake { start, end, diag }
423}