rusteerio 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/Cargo.lock +42 -1
  2. package/Cargo.toml +8 -1
  3. package/README.md +90 -51
  4. package/THIRD_PARTY_NOTICES.md +107 -2
  5. package/dist/{chunk-DMRD3FGC.mjs → chunk-FPRP6UIX.mjs} +2233 -751
  6. package/dist/chunk-FPRP6UIX.mjs.map +1 -0
  7. package/dist/index.cjs +2232 -750
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +1 -1
  12. package/dist/slim.cjs +2226 -745
  13. package/dist/slim.cjs.map +1 -1
  14. package/dist/slim.d.mts +2 -2
  15. package/dist/slim.d.ts +2 -2
  16. package/dist/slim.mjs +1 -1
  17. package/dist/{types-BP6TpxE5.d.mts → types-Cs9UP1Vv.d.mts} +3 -3
  18. package/dist/{types-BP6TpxE5.d.ts → types-Cs9UP1Vv.d.ts} +3 -3
  19. package/dist/utils.d.mts +1 -1
  20. package/dist/utils.d.ts +1 -1
  21. package/native/src/arena.rs +219 -80
  22. package/native/src/attributes.rs +11 -5
  23. package/native/src/document_order.rs +711 -0
  24. package/native/src/extraction.rs +15 -3
  25. package/native/src/gc.rs +12 -3
  26. package/native/src/id_hash.rs +42 -0
  27. package/native/src/immutable.rs +256 -0
  28. package/native/src/lib.rs +1039 -171
  29. package/native/src/runtime.rs +189 -33
  30. package/native/src/selector.rs +346 -44
  31. package/native/src/sink.rs +14 -1
  32. package/native/src/snapshot.rs +527 -0
  33. package/native/src/stream.rs +2 -2
  34. package/native/src/tokenizer.rs +29 -12
  35. package/native/vendor/html5ever-0.35.0/src/tokenizer/char_ref/mod.rs +79 -0
  36. package/package.json +3 -1
  37. package/rusteerio.darwin-arm64.node +0 -0
  38. package/rusteerio.darwin-x64.node +0 -0
  39. package/rusteerio.linux-arm64-gnu.node +0 -0
  40. package/rusteerio.linux-arm64-musl.node +0 -0
  41. package/rusteerio.linux-x64-gnu.node +0 -0
  42. package/rusteerio.linux-x64-musl.node +0 -0
  43. package/rusteerio.win32-arm64-msvc.node +0 -0
  44. package/rusteerio.win32-x64-msvc.node +0 -0
  45. package/src/attributes.ts +78 -44
  46. package/src/codec.ts +22 -3
  47. package/src/extract.ts +86 -2
  48. package/src/hooks.ts +148 -2
  49. package/src/index.ts +51 -40
  50. package/src/match.ts +207 -0
  51. package/src/native.ts +9 -1
  52. package/src/nodes.ts +531 -183
  53. package/src/router.ts +792 -229
  54. package/src/selection.ts +239 -125
  55. package/src/snapshot.ts +255 -0
  56. package/src/transport.ts +51 -42
  57. package/dist/chunk-DMRD3FGC.mjs.map +0 -1
@@ -0,0 +1,711 @@
1
+ //! Cheerio's `uniqueSort`: domutils' `uniqueSort` sorted with V8's
2
+ //! `Array.prototype.sort`.
3
+ //!
4
+ //! domutils' comparison is not a total order. Nodes in different trees compare
5
+ //! equal, and a text node compares before both its parent and its siblings in
6
+ //! either argument order. Which order Cheerio returns then depends on the
7
+ //! exact steps of V8's TimSort, so this module repeats them. A general sort
8
+ //! would return another order, and Rust's sorts may panic on such comparisons.
9
+ use crate::selector::SelectDom;
10
+ use std::cmp::Ordering;
11
+ use std::collections::{HashMap, HashSet};
12
+
13
+ /// `ids` without repeats, keeping each id's last occurrence, in the order
14
+ /// Cheerio's `uniqueSort` returns them.
15
+ pub(crate) fn unique_sort<D: SelectDom>(dom: &D, ids: &[u32]) -> Vec<u32> {
16
+ let mut seen = HashSet::new();
17
+ let mut unique: Vec<u32> = ids
18
+ .iter()
19
+ .rev()
20
+ .copied()
21
+ .filter(|id| seen.insert(*id))
22
+ .collect();
23
+ unique.reverse();
24
+ let positions = Positions::new(dom, &unique);
25
+ tim_sort(&mut unique, |a, b| positions.compare(*a, *b));
26
+ unique
27
+ }
28
+
29
+ /// Whether domutils compares every pair of `ids` consistently with document
30
+ /// order, so any sort by local id returns Cheerio's order: they are all
31
+ /// elements, which domutils places on their own ancestor path.
32
+ pub(crate) fn is_document_ordered<D: SelectDom>(dom: &D, ids: &[u32]) -> bool {
33
+ ids.iter().all(|id| has_children(dom, *id))
34
+ }
35
+
36
+ /// For each node, the path domutils' `compareDocumentPosition` builds: from
37
+ /// the root down to the node itself when it can have children, or else down
38
+ /// to its parent, with each path node's index among its parent's children.
39
+ struct Positions {
40
+ paths: HashMap<u32, Vec<(u32, i64)>>,
41
+ }
42
+
43
+ impl Positions {
44
+ fn new<D: SelectDom>(dom: &D, ids: &[u32]) -> Self {
45
+ let mut sibling_indexes: HashMap<(u32, u32), i64> = HashMap::new();
46
+ let mut indexed_parents: HashSet<u32> = HashSet::new();
47
+ let mut paths = HashMap::with_capacity(ids.len());
48
+ for id in ids {
49
+ let start = if has_children(dom, *id) {
50
+ Some(*id)
51
+ } else {
52
+ dom.parent(*id)
53
+ };
54
+ let mut path = Vec::new();
55
+ let mut visited = HashSet::new();
56
+ let mut current = start;
57
+ while let Some(node) = current {
58
+ if !visited.insert(node) {
59
+ break;
60
+ }
61
+ path.push(node);
62
+ current = dom.parent(node);
63
+ }
64
+ path.reverse();
65
+ let indexed: Vec<(u32, i64)> = path
66
+ .iter()
67
+ .enumerate()
68
+ .map(|(depth, node)| {
69
+ let Some(parent) = depth.checked_sub(1).map(|depth| path[depth]) else {
70
+ return (*node, -1);
71
+ };
72
+ if indexed_parents.insert(parent) {
73
+ for (index, child) in dom.children(parent).iter().enumerate() {
74
+ sibling_indexes
75
+ .entry((parent, *child))
76
+ .or_insert(index as i64);
77
+ }
78
+ }
79
+ let index = sibling_indexes.get(&(parent, *node)).copied();
80
+ (*node, index.unwrap_or(-1))
81
+ })
82
+ .collect();
83
+ paths.insert(*id, indexed);
84
+ }
85
+ Self { paths }
86
+ }
87
+
88
+ /// domutils' `uniqueSort` comparison of `a` and `b`.
89
+ fn compare(&self, a: u32, b: u32) -> Ordering {
90
+ if a == b {
91
+ return Ordering::Equal;
92
+ }
93
+ let (a_path, b_path) = (&self.paths[&a], &self.paths[&b]);
94
+ let shared = a_path
95
+ .iter()
96
+ .zip(b_path.iter())
97
+ .take_while(|(a, b)| a.0 == b.0)
98
+ .count();
99
+ if shared == 0 {
100
+ return Ordering::Equal;
101
+ }
102
+ let a_index = a_path.get(shared).map_or(-1, |(_, index)| *index);
103
+ let b_index = b_path.get(shared).map_or(-1, |(_, index)| *index);
104
+ if a_index > b_index {
105
+ Ordering::Greater
106
+ } else {
107
+ Ordering::Less
108
+ }
109
+ }
110
+ }
111
+
112
+ fn has_children<D: SelectDom>(dom: &D, id: u32) -> bool {
113
+ matches!(dom.kind(id), "tag" | "script" | "style" | "root" | "cdata")
114
+ }
115
+
116
+ const MINIMUM_GALLOP_WINS: usize = 7;
117
+
118
+ /// V8's TimSort, step for step, so comparisons that are not a total order
119
+ /// produce the order `Array.prototype.sort` produces.
120
+ fn tim_sort<T: Copy>(items: &mut [T], compare: impl Fn(&T, &T) -> Ordering) {
121
+ let length = items.len();
122
+ if length < 2 {
123
+ return;
124
+ }
125
+ let mut state = SortState {
126
+ items,
127
+ compare,
128
+ minimum_gallop: MINIMUM_GALLOP_WINS,
129
+ runs: Vec::new(),
130
+ };
131
+ let minimum_run = minimum_run_length(length);
132
+ let mut low = 0;
133
+ let mut remaining = length;
134
+ while remaining != 0 {
135
+ let mut run = state.count_and_make_run(low, low + remaining);
136
+ if run < minimum_run {
137
+ let forced = minimum_run.min(remaining);
138
+ state.binary_insertion_sort(low, low + run, low + forced);
139
+ run = forced;
140
+ }
141
+ state.runs.push((low, run));
142
+ state.merge_collapse();
143
+ low += run;
144
+ remaining -= run;
145
+ }
146
+ state.merge_force_collapse();
147
+ }
148
+
149
+ fn minimum_run_length(mut length: usize) -> usize {
150
+ let mut remainder = 0;
151
+ while length >= 64 {
152
+ remainder |= length & 1;
153
+ length >>= 1;
154
+ }
155
+ length + remainder
156
+ }
157
+
158
+ struct SortState<'a, T, C> {
159
+ items: &'a mut [T],
160
+ compare: C,
161
+ minimum_gallop: usize,
162
+ runs: Vec<(usize, usize)>,
163
+ }
164
+
165
+ impl<T: Copy, C: Fn(&T, &T) -> Ordering> SortState<'_, T, C> {
166
+ fn is_less(&self, a: &T, b: &T) -> bool {
167
+ (self.compare)(a, b) == Ordering::Less
168
+ }
169
+
170
+ fn count_and_make_run(&mut self, low_start: usize, high: usize) -> usize {
171
+ let low = low_start + 1;
172
+ if low == high {
173
+ return 1;
174
+ }
175
+ let mut run = 2;
176
+ let is_descending = self.is_less(&self.items[low], &self.items[low - 1]);
177
+ let mut previous = self.items[low];
178
+ for index in low + 1..high {
179
+ let current = self.items[index];
180
+ let is_current_less = self.is_less(&current, &previous);
181
+ if is_descending != is_current_less {
182
+ break;
183
+ }
184
+ previous = current;
185
+ run += 1;
186
+ }
187
+ if is_descending {
188
+ self.items[low_start..low_start + run].reverse();
189
+ }
190
+ run
191
+ }
192
+
193
+ fn binary_insertion_sort(&mut self, low: usize, start: usize, high: usize) {
194
+ let start = if low == start { start + 1 } else { start };
195
+ for start in start..high {
196
+ let pivot = self.items[start];
197
+ let (mut left, mut right) = (low, start);
198
+ while left < right {
199
+ let middle = left + ((right - left) >> 1);
200
+ if self.is_less(&pivot, &self.items[middle]) {
201
+ right = middle;
202
+ } else {
203
+ left = middle + 1;
204
+ }
205
+ }
206
+ self.items.copy_within(left..start, left + 1);
207
+ self.items[left] = pivot;
208
+ }
209
+ }
210
+
211
+ fn run_length(&self, index: usize) -> usize {
212
+ self.runs[index].1
213
+ }
214
+
215
+ fn is_run_invariant_established(&self, index: usize) -> bool {
216
+ index < 2
217
+ || self.run_length(index - 2) > self.run_length(index - 1) + self.run_length(index)
218
+ }
219
+
220
+ fn merge_collapse(&mut self) {
221
+ while self.runs.len() > 1 {
222
+ let mut index = self.runs.len() - 2;
223
+ if !self.is_run_invariant_established(index + 1)
224
+ || !self.is_run_invariant_established(index)
225
+ {
226
+ if self.run_length(index - 1) < self.run_length(index + 1) {
227
+ index -= 1;
228
+ }
229
+ self.merge_at(index);
230
+ } else if self.run_length(index) <= self.run_length(index + 1) {
231
+ self.merge_at(index);
232
+ } else {
233
+ break;
234
+ }
235
+ }
236
+ }
237
+
238
+ fn merge_force_collapse(&mut self) {
239
+ while self.runs.len() > 1 {
240
+ let mut index = self.runs.len() - 2;
241
+ if index > 0 && self.run_length(index - 1) < self.run_length(index + 1) {
242
+ index -= 1;
243
+ }
244
+ self.merge_at(index);
245
+ }
246
+ }
247
+
248
+ fn merge_at(&mut self, index: usize) {
249
+ let (mut base_a, mut length_a) = self.runs[index];
250
+ let (base_b, mut length_b) = self.runs[index + 1];
251
+ self.runs[index].1 = length_a + length_b;
252
+ self.runs.remove(index + 1);
253
+
254
+ let key_right = self.items[base_b];
255
+ let skipped = self.gallop_right(Source::Items, &key_right, base_a, length_a, 0);
256
+ base_a += skipped;
257
+ length_a -= skipped;
258
+ if length_a == 0 {
259
+ return;
260
+ }
261
+ let key_left = self.items[base_a + length_a - 1];
262
+ length_b = self.gallop_left(Source::Items, &key_left, base_b, length_b, length_b - 1);
263
+ if length_b == 0 {
264
+ return;
265
+ }
266
+ if length_a <= length_b {
267
+ self.merge_low(base_a, length_a, base_b, length_b);
268
+ } else {
269
+ self.merge_high(base_a, length_a, base_b, length_b);
270
+ }
271
+ }
272
+
273
+ fn element(&self, source: Source<'_, T>, index: usize) -> T {
274
+ match source {
275
+ Source::Items => self.items[index],
276
+ Source::Temporary(temporary) => temporary[index],
277
+ }
278
+ }
279
+
280
+ /// The position in the run at `base` before the first element not less
281
+ /// than `key`, searched from `hint`.
282
+ fn gallop_left(
283
+ &self,
284
+ source: Source<'_, T>,
285
+ key: &T,
286
+ base: usize,
287
+ length: usize,
288
+ hint: usize,
289
+ ) -> usize {
290
+ let mut last_offset: isize = 0;
291
+ let mut offset: isize = 1;
292
+ let hint = hint as isize;
293
+ let length = length as isize;
294
+ let at = |position: isize| self.element(source, base + position as usize);
295
+ if self.is_less(&at(hint), key) {
296
+ let maximum_offset = length - hint;
297
+ while offset < maximum_offset {
298
+ if !self.is_less(&at(hint + offset), key) {
299
+ break;
300
+ }
301
+ last_offset = offset;
302
+ offset = (offset << 1) + 1;
303
+ }
304
+ offset = offset.min(maximum_offset);
305
+ last_offset += hint;
306
+ offset += hint;
307
+ } else {
308
+ let maximum_offset = hint + 1;
309
+ while offset < maximum_offset {
310
+ if self.is_less(&at(hint - offset), key) {
311
+ break;
312
+ }
313
+ last_offset = offset;
314
+ offset = (offset << 1) + 1;
315
+ }
316
+ offset = offset.min(maximum_offset);
317
+ let previous = last_offset;
318
+ last_offset = hint - offset;
319
+ offset = hint - previous;
320
+ }
321
+ last_offset += 1;
322
+ while last_offset < offset {
323
+ let middle = last_offset + ((offset - last_offset) >> 1);
324
+ if self.is_less(&at(middle), key) {
325
+ last_offset = middle + 1;
326
+ } else {
327
+ offset = middle;
328
+ }
329
+ }
330
+ offset as usize
331
+ }
332
+
333
+ /// The position in the run at `base` after the last element not greater
334
+ /// than `key`, searched from `hint`.
335
+ fn gallop_right(
336
+ &self,
337
+ source: Source<'_, T>,
338
+ key: &T,
339
+ base: usize,
340
+ length: usize,
341
+ hint: usize,
342
+ ) -> usize {
343
+ let mut last_offset: isize = 0;
344
+ let mut offset: isize = 1;
345
+ let hint = hint as isize;
346
+ let length = length as isize;
347
+ let at = |position: isize| self.element(source, base + position as usize);
348
+ if self.is_less(key, &at(hint)) {
349
+ let maximum_offset = hint + 1;
350
+ while offset < maximum_offset {
351
+ if !self.is_less(key, &at(hint - offset)) {
352
+ break;
353
+ }
354
+ last_offset = offset;
355
+ offset = (offset << 1) + 1;
356
+ }
357
+ offset = offset.min(maximum_offset);
358
+ let previous = last_offset;
359
+ last_offset = hint - offset;
360
+ offset = hint - previous;
361
+ } else {
362
+ let maximum_offset = length - hint;
363
+ while offset < maximum_offset {
364
+ if self.is_less(key, &at(hint + offset)) {
365
+ break;
366
+ }
367
+ last_offset = offset;
368
+ offset = (offset << 1) + 1;
369
+ }
370
+ offset = offset.min(maximum_offset);
371
+ last_offset += hint;
372
+ offset += hint;
373
+ }
374
+ last_offset += 1;
375
+ while last_offset < offset {
376
+ let middle = last_offset + ((offset - last_offset) >> 1);
377
+ if self.is_less(key, &at(middle)) {
378
+ offset = middle;
379
+ } else {
380
+ last_offset = middle + 1;
381
+ }
382
+ }
383
+ offset as usize
384
+ }
385
+
386
+ fn merge_low(&mut self, base_a: usize, length_a: usize, base_b: usize, length_b: usize) {
387
+ let temporary: Vec<T> = self.items[base_a..base_a + length_a].to_vec();
388
+ let (mut length_a, mut length_b) = (length_a, length_b);
389
+ let mut destination = base_a;
390
+ let mut cursor_temporary = 0;
391
+ let mut cursor_b = base_b;
392
+ self.items[destination] = self.items[cursor_b];
393
+ destination += 1;
394
+ cursor_b += 1;
395
+ length_b -= 1;
396
+ let outcome = 'merge: {
397
+ if length_b == 0 {
398
+ break 'merge LowOutcome::Succeed;
399
+ }
400
+ if length_a == 1 {
401
+ break 'merge LowOutcome::CopyB;
402
+ }
403
+ let mut minimum_gallop = self.minimum_gallop;
404
+ loop {
405
+ let mut wins_a = 0;
406
+ let mut wins_b = 0;
407
+ loop {
408
+ if self.is_less(&self.items[cursor_b], &temporary[cursor_temporary]) {
409
+ self.items[destination] = self.items[cursor_b];
410
+ destination += 1;
411
+ cursor_b += 1;
412
+ wins_b += 1;
413
+ length_b -= 1;
414
+ wins_a = 0;
415
+ if length_b == 0 {
416
+ break 'merge LowOutcome::Succeed;
417
+ }
418
+ if wins_b >= minimum_gallop {
419
+ break;
420
+ }
421
+ } else {
422
+ self.items[destination] = temporary[cursor_temporary];
423
+ destination += 1;
424
+ cursor_temporary += 1;
425
+ wins_a += 1;
426
+ length_a -= 1;
427
+ wins_b = 0;
428
+ if length_a == 1 {
429
+ break 'merge LowOutcome::CopyB;
430
+ }
431
+ if wins_a >= minimum_gallop {
432
+ break;
433
+ }
434
+ }
435
+ }
436
+ minimum_gallop += 1;
437
+ let mut is_first_iteration = true;
438
+ while wins_a >= MINIMUM_GALLOP_WINS
439
+ || wins_b >= MINIMUM_GALLOP_WINS
440
+ || is_first_iteration
441
+ {
442
+ is_first_iteration = false;
443
+ minimum_gallop = minimum_gallop.saturating_sub(1).max(1);
444
+ self.minimum_gallop = minimum_gallop;
445
+
446
+ let key = self.items[cursor_b];
447
+ wins_a = self.gallop_right(
448
+ Source::Temporary(&temporary),
449
+ &key,
450
+ cursor_temporary,
451
+ length_a,
452
+ 0,
453
+ );
454
+ if wins_a > 0 {
455
+ self.items[destination..destination + wins_a].copy_from_slice(
456
+ &temporary[cursor_temporary..cursor_temporary + wins_a],
457
+ );
458
+ destination += wins_a;
459
+ cursor_temporary += wins_a;
460
+ length_a -= wins_a;
461
+ if length_a == 1 {
462
+ break 'merge LowOutcome::CopyB;
463
+ }
464
+ if length_a == 0 {
465
+ break 'merge LowOutcome::Succeed;
466
+ }
467
+ }
468
+ self.items[destination] = self.items[cursor_b];
469
+ destination += 1;
470
+ cursor_b += 1;
471
+ length_b -= 1;
472
+ if length_b == 0 {
473
+ break 'merge LowOutcome::Succeed;
474
+ }
475
+
476
+ let key = temporary[cursor_temporary];
477
+ wins_b = self.gallop_left(Source::Items, &key, cursor_b, length_b, 0);
478
+ if wins_b > 0 {
479
+ self.items
480
+ .copy_within(cursor_b..cursor_b + wins_b, destination);
481
+ destination += wins_b;
482
+ cursor_b += wins_b;
483
+ length_b -= wins_b;
484
+ if length_b == 0 {
485
+ break 'merge LowOutcome::Succeed;
486
+ }
487
+ }
488
+ self.items[destination] = temporary[cursor_temporary];
489
+ destination += 1;
490
+ cursor_temporary += 1;
491
+ length_a -= 1;
492
+ if length_a == 1 {
493
+ break 'merge LowOutcome::CopyB;
494
+ }
495
+ }
496
+ minimum_gallop += 1;
497
+ self.minimum_gallop = minimum_gallop;
498
+ }
499
+ };
500
+ match outcome {
501
+ LowOutcome::Succeed => {
502
+ if length_a > 0 {
503
+ self.items[destination..destination + length_a]
504
+ .copy_from_slice(&temporary[cursor_temporary..cursor_temporary + length_a]);
505
+ }
506
+ }
507
+ LowOutcome::CopyB => {
508
+ self.items
509
+ .copy_within(cursor_b..cursor_b + length_b, destination);
510
+ self.items[destination + length_b] = temporary[cursor_temporary];
511
+ }
512
+ }
513
+ }
514
+
515
+ fn merge_high(&mut self, base_a: usize, length_a: usize, base_b: usize, length_b: usize) {
516
+ let temporary: Vec<T> = self.items[base_b..base_b + length_b].to_vec();
517
+ let (mut length_a, mut length_b) = (length_a, length_b);
518
+ // Cursors may step one past the start of their runs, which signed
519
+ // positions represent as -1.
520
+ let mut destination = (base_b + length_b - 1) as isize;
521
+ let mut cursor_temporary = length_b as isize - 1;
522
+ let mut cursor_a = (base_a + length_a - 1) as isize;
523
+ self.items[destination as usize] = self.items[cursor_a as usize];
524
+ destination -= 1;
525
+ cursor_a -= 1;
526
+ length_a -= 1;
527
+ let outcome = 'merge: {
528
+ if length_a == 0 {
529
+ break 'merge HighOutcome::Succeed;
530
+ }
531
+ if length_b == 1 {
532
+ break 'merge HighOutcome::CopyA;
533
+ }
534
+ let mut minimum_gallop = self.minimum_gallop;
535
+ loop {
536
+ let mut wins_a = 0;
537
+ let mut wins_b = 0;
538
+ loop {
539
+ if self.is_less(
540
+ &temporary[cursor_temporary as usize],
541
+ &self.items[cursor_a as usize],
542
+ ) {
543
+ self.items[destination as usize] = self.items[cursor_a as usize];
544
+ destination -= 1;
545
+ cursor_a -= 1;
546
+ wins_a += 1;
547
+ length_a -= 1;
548
+ wins_b = 0;
549
+ if length_a == 0 {
550
+ break 'merge HighOutcome::Succeed;
551
+ }
552
+ if wins_a >= minimum_gallop {
553
+ break;
554
+ }
555
+ } else {
556
+ self.items[destination as usize] = temporary[cursor_temporary as usize];
557
+ destination -= 1;
558
+ cursor_temporary -= 1;
559
+ wins_b += 1;
560
+ length_b -= 1;
561
+ wins_a = 0;
562
+ if length_b == 1 {
563
+ break 'merge HighOutcome::CopyA;
564
+ }
565
+ if wins_b >= minimum_gallop {
566
+ break;
567
+ }
568
+ }
569
+ }
570
+ minimum_gallop += 1;
571
+ let mut is_first_iteration = true;
572
+ while wins_a >= MINIMUM_GALLOP_WINS
573
+ || wins_b >= MINIMUM_GALLOP_WINS
574
+ || is_first_iteration
575
+ {
576
+ is_first_iteration = false;
577
+ minimum_gallop = minimum_gallop.saturating_sub(1).max(1);
578
+ self.minimum_gallop = minimum_gallop;
579
+
580
+ let key = temporary[cursor_temporary as usize];
581
+ let position =
582
+ self.gallop_right(Source::Items, &key, base_a, length_a, length_a - 1);
583
+ wins_a = length_a - position;
584
+ if wins_a > 0 {
585
+ destination -= wins_a as isize;
586
+ cursor_a -= wins_a as isize;
587
+ let from = (cursor_a + 1) as usize;
588
+ self.items
589
+ .copy_within(from..from + wins_a, (destination + 1) as usize);
590
+ length_a -= wins_a;
591
+ if length_a == 0 {
592
+ break 'merge HighOutcome::Succeed;
593
+ }
594
+ }
595
+ self.items[destination as usize] = temporary[cursor_temporary as usize];
596
+ destination -= 1;
597
+ cursor_temporary -= 1;
598
+ length_b -= 1;
599
+ if length_b == 1 {
600
+ break 'merge HighOutcome::CopyA;
601
+ }
602
+
603
+ let key = self.items[cursor_a as usize];
604
+ let position = self.gallop_left(
605
+ Source::Temporary(&temporary),
606
+ &key,
607
+ 0,
608
+ length_b,
609
+ length_b - 1,
610
+ );
611
+ wins_b = length_b - position;
612
+ if wins_b > 0 {
613
+ destination -= wins_b as isize;
614
+ cursor_temporary -= wins_b as isize;
615
+ let from = (cursor_temporary + 1) as usize;
616
+ let to = (destination + 1) as usize;
617
+ self.items[to..to + wins_b]
618
+ .copy_from_slice(&temporary[from..from + wins_b]);
619
+ length_b -= wins_b;
620
+ if length_b == 1 {
621
+ break 'merge HighOutcome::CopyA;
622
+ }
623
+ if length_b == 0 {
624
+ break 'merge HighOutcome::Succeed;
625
+ }
626
+ }
627
+ self.items[destination as usize] = self.items[cursor_a as usize];
628
+ destination -= 1;
629
+ cursor_a -= 1;
630
+ length_a -= 1;
631
+ if length_a == 0 {
632
+ break 'merge HighOutcome::Succeed;
633
+ }
634
+ }
635
+ minimum_gallop += 1;
636
+ self.minimum_gallop = minimum_gallop;
637
+ }
638
+ };
639
+ match outcome {
640
+ HighOutcome::Succeed => {
641
+ if length_b > 0 {
642
+ let to = (destination - (length_b as isize - 1)) as usize;
643
+ self.items[to..to + length_b].copy_from_slice(&temporary[..length_b]);
644
+ }
645
+ }
646
+ HighOutcome::CopyA => {
647
+ destination -= length_a as isize;
648
+ cursor_a -= length_a as isize;
649
+ let from = (cursor_a + 1) as usize;
650
+ self.items
651
+ .copy_within(from..from + length_a, (destination + 1) as usize);
652
+ self.items[destination as usize] = temporary[cursor_temporary as usize];
653
+ }
654
+ }
655
+ }
656
+ }
657
+
658
+ #[derive(Clone, Copy)]
659
+ enum Source<'a, T> {
660
+ Items,
661
+ Temporary(&'a [T]),
662
+ }
663
+
664
+ enum LowOutcome {
665
+ Succeed,
666
+ CopyB,
667
+ }
668
+
669
+ enum HighOutcome {
670
+ Succeed,
671
+ CopyA,
672
+ }
673
+
674
+ #[cfg(test)]
675
+ mod tests {
676
+ use super::*;
677
+
678
+ #[test]
679
+ fn sorts_consistent_comparisons_stably() {
680
+ let mut state = 0x2545_f491_u64;
681
+ for length in [0, 1, 2, 5, 31, 64, 65, 200, 1000, 5000] {
682
+ let mut items: Vec<(u32, usize)> = (0..length)
683
+ .map(|index| {
684
+ state ^= state << 13;
685
+ state ^= state >> 7;
686
+ state ^= state << 17;
687
+ ((state % 50) as u32, index)
688
+ })
689
+ .collect();
690
+ let mut expected = items.clone();
691
+ expected.sort_by_key(|item| item.0);
692
+ tim_sort(&mut items, |a, b| a.0.cmp(&b.0));
693
+ assert_eq!(items, expected);
694
+ }
695
+ }
696
+
697
+ #[test]
698
+ fn terminates_on_inconsistent_comparisons() {
699
+ let mut items: Vec<u32> = (0..3000).collect();
700
+ tim_sort(&mut items, |a, b| {
701
+ if (a ^ b) % 3 == 0 {
702
+ Ordering::Less
703
+ } else {
704
+ b.cmp(a)
705
+ }
706
+ });
707
+ let mut sorted = items.clone();
708
+ sorted.sort_unstable();
709
+ assert_eq!(sorted, (0..3000).collect::<Vec<_>>());
710
+ }
711
+ }