Skip to content

Commit 9c69cf0

Browse files
committed
Add a version sort implementation and tests
1 parent f800ce4 commit 9c69cf0

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed

src/reorder.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,101 @@ use crate::spanned::Spanned;
2121
use crate::utils::{contains_skip, mk_sp};
2222
use crate::visitor::FmtVisitor;
2323

24+
/// Compare strings according to version sort (roughly equivalent to `strverscmp`)
25+
pub(crate) fn compare_as_versions(left: &str, right: &str) -> Ordering {
26+
let mut left = left.chars().peekable();
27+
let mut right = right.chars().peekable();
28+
29+
loop {
30+
// The strings are equal so far and not inside a number in both sides
31+
let (l, r) = match (left.next(), right.next()) {
32+
// Is this the end of both strings?
33+
(None, None) => return Ordering::Equal,
34+
// If for one, the shorter one is considered smaller
35+
(None, Some(_)) => return Ordering::Less,
36+
(Some(_), None) => return Ordering::Greater,
37+
(Some(l), Some(r)) => (l, r),
38+
};
39+
let next_ordering = match (l.to_digit(10), r.to_digit(10)) {
40+
// If neither is a digit, just compare them
41+
(None, None) => Ord::cmp(&l, &r),
42+
// The one with shorter non-digit run is smaller
43+
// For `strverscmp` it's smaller iff next char in longer is greater than digits
44+
(None, Some(_)) => Ordering::Greater,
45+
(Some(_), None) => Ordering::Less,
46+
// If both start numbers, we have to compare the numbers
47+
(Some(l), Some(r)) => {
48+
if l == 0 || r == 0 {
49+
// Fraction mode: compare as if there was leading `0.`
50+
let ordering = Ord::cmp(&l, &r);
51+
if ordering != Ordering::Equal {
52+
return ordering;
53+
}
54+
loop {
55+
// Get next pair
56+
let (l, r) = match (left.peek(), right.peek()) {
57+
// Is this the end of both strings?
58+
(None, None) => return Ordering::Equal,
59+
// If for one, the shorter one is considered smaller
60+
(None, Some(_)) => return Ordering::Less,
61+
(Some(_), None) => return Ordering::Greater,
62+
(Some(l), Some(r)) => (l, r),
63+
};
64+
// Are they digits?
65+
match (l.to_digit(10), r.to_digit(10)) {
66+
// If out of digits, use the stored ordering due to equal length
67+
(None, None) => break Ordering::Equal,
68+
// If one is shorter, it's smaller
69+
(None, Some(_)) => return Ordering::Less,
70+
(Some(_), None) => return Ordering::Greater,
71+
// If both are digits, consume them and take into account
72+
(Some(l), Some(r)) => {
73+
left.next();
74+
right.next();
75+
let ordering = Ord::cmp(&l, &r);
76+
if ordering != Ordering::Equal {
77+
return ordering;
78+
}
79+
}
80+
}
81+
}
82+
} else {
83+
// Integer mode
84+
let mut same_length_ordering = Ord::cmp(&l, &r);
85+
loop {
86+
// Get next pair
87+
let (l, r) = match (left.peek(), right.peek()) {
88+
// Is this the end of both strings?
89+
(None, None) => return same_length_ordering,
90+
// If for one, the shorter one is considered smaller
91+
(None, Some(_)) => return Ordering::Less,
92+
(Some(_), None) => return Ordering::Greater,
93+
(Some(l), Some(r)) => (l, r),
94+
};
95+
// Are they digits?
96+
match (l.to_digit(10), r.to_digit(10)) {
97+
// If out of digits, use the stored ordering due to equal length
98+
(None, None) => break same_length_ordering,
99+
// If one is shorter, it's smaller
100+
(None, Some(_)) => return Ordering::Less,
101+
(Some(_), None) => return Ordering::Greater,
102+
// If both are digits, consume them and take into account
103+
(Some(l), Some(r)) => {
104+
left.next();
105+
right.next();
106+
same_length_ordering = same_length_ordering.then(Ord::cmp(&l, &r));
107+
}
108+
}
109+
}
110+
}
111+
}
112+
};
113+
if next_ordering != Ordering::Equal {
114+
return next_ordering;
115+
}
116+
}
117+
}
118+
24119
/// Choose the ordering between the given two items.
25120
fn compare_items(a: &ast::Item, b: &ast::Item) -> Ordering {
26121
match (&a.node, &b.node) {
@@ -265,3 +360,28 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
265360
}
266361
}
267362
}
363+
364+
#[cfg(test)]
365+
mod tests {
366+
#[test]
367+
fn test_compare_as_versions() {
368+
use super::compare_as_versions;
369+
use std::cmp::Ordering;
370+
let mut strings: &[&'static str] = &[
371+
"9", "i8", "ia32", "u009", "u08", "u08", "u080", "u8", "u8", "u16", "u32", "u128",
372+
];
373+
while !strings.is_empty() {
374+
let (first, tail) = strings.split_first().unwrap();
375+
for second in tail {
376+
if first == second {
377+
assert_eq!(compare_as_versions(first, second), Ordering::Equal);
378+
assert_eq!(compare_as_versions(second, first), Ordering::Equal);
379+
} else {
380+
assert_eq!(compare_as_versions(first, second), Ordering::Less);
381+
assert_eq!(compare_as_versions(second, first), Ordering::Greater);
382+
}
383+
}
384+
strings = tail;
385+
}
386+
}
387+
}

0 commit comments

Comments
 (0)