Coverage Report

Created: 2026-08-14 08:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/lexicmp-0.2.0/src/cmp.rs
Line
Count
Source
1
use crate::iter::{iterate_lexical, iterate_lexical_only_alnum};
2
use core::cmp::Ordering;
3
4
macro_rules! cmp_ascii_digits {
5
    (first_digits($lhs:ident, $rhs:ident), iterators($iter1:ident, $iter2:ident)) => {
6
        let mut n1 = ascii_to_u64($lhs);
7
        let mut n2 = ascii_to_u64($rhs);
8
        loop {
9
            match (
10
0
                $iter1.peek().copied().filter(|c| c.is_ascii_digit()),
Unexecuted instantiation: lexicmp::cmp::natural_cmp::{closure#0}
Unexecuted instantiation: lexicmp::cmp::natural_lexical_cmp::{closure#0}
Unexecuted instantiation: lexicmp::cmp::natural_only_alnum_cmp::{closure#2}
Unexecuted instantiation: lexicmp::cmp::natural_lexical_only_alnum_cmp::{closure#0}
11
0
                $iter2.peek().copied().filter(|c| c.is_ascii_digit()),
Unexecuted instantiation: lexicmp::cmp::natural_cmp::{closure#1}
Unexecuted instantiation: lexicmp::cmp::natural_lexical_cmp::{closure#1}
Unexecuted instantiation: lexicmp::cmp::natural_only_alnum_cmp::{closure#3}
Unexecuted instantiation: lexicmp::cmp::natural_lexical_only_alnum_cmp::{closure#1}
12
            ) {
13
                (Some(lhs), Some(rhs)) => {
14
                    n1 = n1 * 10 + ascii_to_u64(lhs);
15
                    n2 = n2 * 10 + ascii_to_u64(rhs);
16
                    let _ = $iter1.next();
17
                    let _ = $iter2.next();
18
                }
19
                (Some(_), None) => return Ordering::Greater,
20
                (None, Some(_)) => return Ordering::Less,
21
                (None, None) => {
22
                    if n1 != n2 {
23
                        return n1.cmp(&n2);
24
                    } else {
25
                        break;
26
                    }
27
                }
28
            }
29
        }
30
    };
31
}
32
33
#[inline]
34
0
fn ascii_to_u64(c: char) -> u64 {
35
0
    (c as u64) - (b'0' as u64)
36
0
}
37
38
#[inline]
39
0
fn ret_ordering(lhs: char, rhs: char) -> Ordering {
40
0
    let is_lhs_alnum = lhs.is_alphanumeric();
41
0
    let is_rhs_alnum = rhs.is_alphanumeric();
42
43
0
    let result = if is_lhs_alnum == is_rhs_alnum {
44
0
        lhs.cmp(&rhs)
45
0
    } else if is_lhs_alnum {
46
0
        Ordering::Greater
47
    } else {
48
0
        Ordering::Less
49
    };
50
0
    result
51
0
}
52
53
/// Compares strings lexicographically
54
///
55
/// For example, `"a" < "ä" < "aa"`
56
0
pub fn lexical_cmp(lhs: &str, rhs: &str) -> Ordering {
57
0
    let mut iter1 = iterate_lexical(lhs);
58
0
    let mut iter2 = iterate_lexical(rhs);
59
60
    loop {
61
0
        match (iter1.next(), iter2.next()) {
62
0
            (Some(lhs), Some(rhs)) => {
63
0
                if lhs != rhs {
64
0
                    return ret_ordering(lhs, rhs);
65
0
                }
66
            }
67
0
            (Some(_), None) => return Ordering::Greater,
68
0
            (None, Some(_)) => return Ordering::Less,
69
0
            (None, None) => return lhs.cmp(&rhs),
70
        }
71
    }
72
0
}
73
74
/// Compares strings lexicographically, skipping non-alphanumeric characters
75
///
76
/// For example, `"a" < " ä" < "ä" < "aa"`
77
0
pub fn lexical_only_alnum_cmp(s1: &str, s2: &str) -> Ordering {
78
0
    let mut iter1 = iterate_lexical_only_alnum(s1);
79
0
    let mut iter2 = iterate_lexical_only_alnum(s2);
80
81
    loop {
82
0
        match (iter1.next(), iter2.next()) {
83
0
            (Some(lhs), Some(rhs)) => {
84
0
                if lhs != rhs {
85
0
                    return lhs.cmp(&rhs);
86
0
                }
87
            }
88
0
            (Some(_), None) => return Ordering::Greater,
89
0
            (None, Some(_)) => return Ordering::Less,
90
0
            (None, None) => return s1.cmp(&s2),
91
        }
92
    }
93
0
}
94
95
/// Compares strings naturally and lexicographically
96
///
97
/// For example, `"a" < "ä" < "aa"`, `"50" < "100"`
98
0
pub fn natural_lexical_cmp(s1: &str, s2: &str) -> Ordering {
99
0
    let mut iter1 = iterate_lexical(s1).peekable();
100
0
    let mut iter2 = iterate_lexical(s2).peekable();
101
102
    loop {
103
0
        match (iter1.next(), iter2.next()) {
104
0
            (Some(lhs), Some(rhs)) => {
105
0
                if lhs.is_ascii_digit() && rhs.is_ascii_digit() {
106
0
                    cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2));
107
0
                } else if lhs != rhs {
108
0
                    return ret_ordering(lhs, rhs);
109
0
                }
110
            }
111
0
            (Some(_), None) => return Ordering::Greater,
112
0
            (None, Some(_)) => return Ordering::Less,
113
0
            (None, None) => return s1.cmp(&s2),
114
        }
115
    }
116
0
}
117
118
/// Compares strings naturally and lexicographically, skipping non-alphanumeric characters
119
///
120
/// For example, `"a" < " ä" < "ä" < "aa"`, `"50" < "100"`
121
0
pub fn natural_lexical_only_alnum_cmp(s1: &str, s2: &str) -> Ordering {
122
0
    let mut iter1 = iterate_lexical_only_alnum(s1).peekable();
123
0
    let mut iter2 = iterate_lexical_only_alnum(s2).peekable();
124
125
    loop {
126
0
        match (iter1.next(), iter2.next()) {
127
0
            (Some(lhs), Some(rhs)) => {
128
0
                if lhs.is_ascii_digit() && rhs.is_ascii_digit() {
129
0
                    cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2));
130
0
                } else if lhs != rhs {
131
0
                    return lhs.cmp(&rhs);
132
0
                }
133
            }
134
0
            (Some(_), None) => return Ordering::Greater,
135
0
            (None, Some(_)) => return Ordering::Less,
136
0
            (None, None) => return s1.cmp(&s2),
137
        }
138
    }
139
0
}
140
141
/// Compares strings naturally
142
///
143
/// For example, `"50" < "100"`
144
0
pub fn natural_cmp(s1: &str, s2: &str) -> Ordering {
145
0
    let mut iter1 = s1.chars().peekable();
146
0
    let mut iter2 = s2.chars().peekable();
147
148
    loop {
149
0
        match (iter1.next(), iter2.next()) {
150
0
            (Some(lhs), Some(rhs)) => {
151
0
                if lhs.is_ascii_digit() && rhs.is_ascii_digit() {
152
0
                    cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2));
153
0
                } else if lhs != rhs {
154
0
                    return lhs.cmp(&rhs);
155
0
                }
156
            }
157
0
            (Some(_), None) => return Ordering::Greater,
158
0
            (None, Some(_)) => return Ordering::Less,
159
0
            (None, None) => return Ordering::Equal,
160
        }
161
    }
162
0
}
163
164
/// Compares strings naturally, skipping non-alphanumeric characters
165
///
166
/// For example, `"a" < " b" < "b"`, `"50" < "100"`
167
0
pub fn natural_only_alnum_cmp(s1: &str, s2: &str) -> Ordering {
168
0
    let mut iter1 = s1.chars().filter(|c| c.is_alphanumeric()).peekable();
169
0
    let mut iter2 = s2.chars().filter(|c| c.is_alphanumeric()).peekable();
170
171
    loop {
172
0
        match (iter1.next(), iter2.next()) {
173
0
            (Some(lhs), Some(rhs)) => {
174
0
                if lhs.is_ascii_digit() && rhs.is_ascii_digit() {
175
0
                    cmp_ascii_digits!(first_digits(lhs, rhs), iterators(iter1, iter2));
176
0
                } else if lhs != rhs {
177
0
                    return lhs.cmp(&rhs);
178
0
                }
179
            }
180
0
            (Some(_), None) => return Ordering::Greater,
181
0
            (None, Some(_)) => return Ordering::Less,
182
0
            (None, None) => return s1.cmp(&s2),
183
        }
184
    }
185
0
}
186
187
/// Compares strings, skipping non-alphanumeric characters
188
///
189
/// For example, `"a" < " b" < "b"`
190
0
pub fn only_alnum_cmp(s1: &str, s2: &str) -> Ordering {
191
0
    let mut iter1 = s1.chars().filter(|c| c.is_alphanumeric());
192
0
    let mut iter2 = s2.chars().filter(|c| c.is_alphanumeric());
193
194
    loop {
195
0
        match (iter1.next(), iter2.next()) {
196
0
            (Some(lhs), Some(rhs)) => {
197
0
                if lhs != rhs {
198
0
                    return lhs.cmp(&rhs);
199
0
                }
200
            }
201
0
            (Some(_), None) => return Ordering::Greater,
202
0
            (None, Some(_)) => return Ordering::Less,
203
0
            (None, None) => return s1.cmp(&s2),
204
        }
205
    }
206
0
}
207
208
/// Compares strings (not lexicographically or naturally, doesn't skip non-alphanumeric characters)
209
///
210
/// For example, `"B" < "a" < "b" < "ä"`
211
0
pub fn cmp(s1: &str, s2: &str) -> Ordering {
212
0
    let mut iter1 = s1.chars();
213
0
    let mut iter2 = s2.chars();
214
215
    loop {
216
0
        match (iter1.next(), iter2.next()) {
217
0
            (Some(lhs), Some(rhs)) => {
218
0
                if lhs != rhs {
219
0
                    return lhs.cmp(&rhs);
220
0
                }
221
            }
222
0
            (Some(_), None) => return Ordering::Greater,
223
0
            (None, Some(_)) => return Ordering::Less,
224
0
            (None, None) => return Ordering::Equal,
225
        }
226
    }
227
0
}
228
229
#[cfg(test)]
230
mod tests {
231
    use super::*;
232
233
    fn make_test(desc: &'static str, algo: impl Fn(&str, &str) -> Ordering) -> impl Fn(&str, &str) {
234
        move |lhs, rhs| {
235
            let success = algo(lhs, rhs) == Ordering::Less;
236
            assert!(success, "{} comparison {:?} < {:?} failed", desc, lhs, rhs);
237
238
            let success = algo(rhs, lhs) == Ordering::Greater;
239
            assert!(success, "{} comparison {:?} > {:?} failed", desc, rhs, lhs);
240
        }
241
    }
242
243
    #[test]
244
    fn test_cmp() {
245
        let ordered = make_test("Cmp", cmp);
246
247
        ordered("aaa", "aaaa");
248
        ordered("aaa", "aab");
249
        ordered("AAb", "aaa");
250
        ordered("aab", "äáa");
251
        ordered("aaa", "äáb");
252
253
        ordered("T-20", "T-5");
254
        ordered("T-5", "Ŧ-5");
255
    }
256
257
    #[test]
258
    fn test_only_alnum() {
259
        let ordered = make_test("Only-alnum", only_alnum_cmp);
260
261
        ordered("aaa", "aaaa");
262
        ordered("aaa", "aab");
263
        ordered("AAb", "aaa");
264
        ordered("aab", "äáa");
265
        ordered("aaa", "äáb");
266
267
        ordered("_ad", "_æ");
268
        ordered("_ae", "_æ");
269
        ordered("_ae_", "_æ");
270
        ordered("_af", "_æ");
271
272
        ordered("T-20", "T-5");
273
        ordered("T-5", "Ŧ-5");
274
    }
275
276
    #[test]
277
    fn test_lexical() {
278
        let ordered = make_test("Lexical", lexical_cmp);
279
280
        ordered("aaa", "aaaa");
281
        ordered("aaa", "aab");
282
        ordered("aaa", "AAb");
283
        ordered("äáa", "aab");
284
        ordered("aaa", "äáb");
285
286
        ordered("_ad", "_æ");
287
        ordered("_ae", "_æ");
288
        ordered("_æ", "_ae_");
289
        ordered("_æ", "_af");
290
291
        ordered("T-20", "T-5");
292
        ordered("T-5", "Ŧ-5");
293
    }
294
295
    #[test]
296
    fn test_lexical_only_alnum() {
297
        let ordered = make_test("Lexical, only-alnum", lexical_only_alnum_cmp);
298
299
        ordered("aaa", "aaaa");
300
        ordered("aaa", "aab");
301
        ordered("aaa", "AAb");
302
        ordered("äáa", "aab");
303
        ordered("aaa", "äáb");
304
305
        ordered("_ad", "_æ");
306
        ordered("_ae", "_æ");
307
        ordered("_ae_", "_æ");
308
        ordered("_æ", "_af");
309
310
        ordered("T20", "T-21");
311
        ordered("T-21", "T22");
312
        ordered("T-21", "T3");
313
    }
314
315
    #[test]
316
    fn test_natural() {
317
        let ordered = make_test("Natural", natural_cmp);
318
319
        ordered("1", "10");
320
        ordered("10", "15");
321
        ordered("150", "220");
322
        ordered("334", "335");
323
        ordered("433", "533");
324
325
        ordered("T-1", "T-5");
326
        ordered("T-27", "T5");
327
        ordered("T-27a", "T27b");
328
329
        ordered("T-27", "Ŧ-5");
330
        ordered("T-5", "Ŧ-27");
331
        ordered("T-5", "Ŧ-5");
332
    }
333
334
    #[test]
335
    fn test_natural_only_alnum() {
336
        let ordered = make_test("Natural, only-alnum", natural_only_alnum_cmp);
337
338
        ordered("aaa", "aaaa");
339
        ordered("aaa", "aab");
340
        ordered("AAb", "aaa");
341
        ordered("aab", "äáa");
342
        ordered("aaa", "äáb");
343
344
        ordered("_ad", "_æ");
345
        ordered("_ae", "_æ");
346
        ordered("_ae_", "_æ");
347
        ordered("_af", "_æ");
348
349
        ordered("T20", "T-21");
350
        ordered("T-21", "T22");
351
        ordered("T3", "T-21");
352
    }
353
354
    #[test]
355
    fn test_natural_lexical() {
356
        let ordered = make_test("Natural, lexical", natural_lexical_cmp);
357
358
        ordered("1", "10");
359
        ordered("10", "15");
360
        ordered("150", "220");
361
        ordered("334", "335");
362
        ordered("433", "533");
363
364
        ordered("T-1", "T-5");
365
        ordered("T-5", "T-27");
366
        ordered("T-27a", "T-27b");
367
368
        ordered("Ŧ-5", "T-27");
369
        ordered("T-5", "Ŧ-27");
370
        ordered("T-5", "Ŧ-5");
371
    }
372
373
    #[test]
374
    fn test_natural_lexical_only_alnum() {
375
        let ordered = make_test(
376
            "Natural, lexical, only-alnum",
377
            natural_lexical_only_alnum_cmp,
378
        );
379
380
        ordered("1", "10");
381
        ordered("10", "15");
382
        ordered("150", "220");
383
        ordered("334", "335");
384
        ordered("433", "533");
385
386
        ordered("T-1", "T-5");
387
        ordered("T5", "T-27");
388
        ordered("T-27a", "T27b");
389
390
        ordered("Ŧ-5", "T-27");
391
        ordered("T-5", "Ŧ-27");
392
        ordered("T-5", "Ŧ-5");
393
    }
394
}