Skip to content

Commit f0546fe

Browse files
committed
Add a version sort implementation and tests
1 parent 1e9af9f commit f0546fe

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed

rustfmt-core/rustfmt-lib/src/reorder.rs

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

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

0 commit comments

Comments
 (0)