@@ -21,6 +21,101 @@ use crate::spanned::Spanned;
2121use crate :: utils:: { contains_skip, mk_sp} ;
2222use 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.
25120fn 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