rusteerio 1.0.1 → 1.1.0
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.
- package/Cargo.lock +42 -1
- package/Cargo.toml +8 -1
- package/README.md +90 -51
- package/THIRD_PARTY_NOTICES.md +107 -2
- package/dist/{chunk-DMRD3FGC.mjs → chunk-FPRP6UIX.mjs} +2233 -751
- package/dist/chunk-FPRP6UIX.mjs.map +1 -0
- package/dist/index.cjs +2232 -750
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/slim.cjs +2226 -745
- package/dist/slim.cjs.map +1 -1
- package/dist/slim.d.mts +2 -2
- package/dist/slim.d.ts +2 -2
- package/dist/slim.mjs +1 -1
- package/dist/{types-BP6TpxE5.d.mts → types-Cs9UP1Vv.d.mts} +3 -3
- package/dist/{types-BP6TpxE5.d.ts → types-Cs9UP1Vv.d.ts} +3 -3
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/native/src/arena.rs +219 -80
- package/native/src/attributes.rs +11 -5
- package/native/src/document_order.rs +711 -0
- package/native/src/extraction.rs +15 -3
- package/native/src/gc.rs +12 -3
- package/native/src/id_hash.rs +42 -0
- package/native/src/immutable.rs +256 -0
- package/native/src/lib.rs +1039 -171
- package/native/src/runtime.rs +189 -33
- package/native/src/selector.rs +346 -44
- package/native/src/sink.rs +14 -1
- package/native/src/snapshot.rs +509 -0
- package/native/src/stream.rs +2 -2
- package/native/src/tokenizer.rs +29 -12
- package/native/vendor/html5ever-0.35.0/src/tokenizer/char_ref/mod.rs +79 -0
- package/package.json +3 -1
- package/rusteerio.darwin-arm64.node +0 -0
- package/rusteerio.darwin-x64.node +0 -0
- package/rusteerio.linux-arm64-gnu.node +0 -0
- package/rusteerio.linux-arm64-musl.node +0 -0
- package/rusteerio.linux-x64-gnu.node +0 -0
- package/rusteerio.linux-x64-musl.node +0 -0
- package/rusteerio.win32-arm64-msvc.node +0 -0
- package/rusteerio.win32-x64-msvc.node +0 -0
- package/src/attributes.ts +78 -44
- package/src/codec.ts +22 -3
- package/src/extract.ts +86 -2
- package/src/hooks.ts +148 -2
- package/src/index.ts +51 -40
- package/src/match.ts +207 -0
- package/src/native.ts +9 -1
- package/src/nodes.ts +531 -183
- package/src/router.ts +792 -229
- package/src/selection.ts +239 -125
- package/src/snapshot.ts +255 -0
- package/src/transport.ts +51 -42
- 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(¤t, &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
|
+
}
|