Coverage Report

Created: 2025-11-16 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tinystr-0.8.2/src/ascii.rs
Line
Count
Source
1
// This file is part of ICU4X. For terms of use, please see the file
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5
use crate::asciibyte::AsciiByte;
6
use crate::int_ops::{Aligned4, Aligned8};
7
use crate::ParseError;
8
use core::borrow::Borrow;
9
use core::fmt;
10
use core::ops::Deref;
11
use core::str::{self, FromStr};
12
13
#[repr(transparent)]
14
#[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
15
pub struct TinyAsciiStr<const N: usize> {
16
    bytes: [AsciiByte; N],
17
}
18
19
impl<const N: usize> TinyAsciiStr<N> {
20
    #[inline]
21
0
    pub const fn try_from_str(s: &str) -> Result<Self, ParseError> {
22
0
        Self::try_from_utf8(s.as_bytes())
23
0
    }
24
25
    /// Creates a `TinyAsciiStr<N>` from the given UTF-8 slice.
26
    /// `code_units` may contain at most `N` non-null ASCII code points.
27
    #[inline]
28
0
    pub const fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
29
0
        Self::try_from_utf8_inner(code_units, false)
30
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::try_from_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::try_from_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::try_from_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::try_from_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::try_from_utf8
31
32
    /// Creates a `TinyAsciiStr<N>` from the given UTF-16 slice.
33
    /// `code_units` may contain at most `N` non-null ASCII code points.
34
    #[inline]
35
0
    pub const fn try_from_utf16(code_units: &[u16]) -> Result<Self, ParseError> {
36
0
        Self::try_from_utf16_inner(code_units, 0, code_units.len(), false)
37
0
    }
38
39
    /// Creates a `TinyAsciiStr<N>` from a UTF-8 slice, replacing invalid code units.
40
    ///
41
    /// Invalid code units, as well as null or non-ASCII code points
42
    /// (i.e. those outside the range U+0001..=U+007F`)
43
    /// will be replaced with the replacement byte.
44
    ///
45
    /// The input slice will be truncated if its length exceeds `N`.
46
0
    pub const fn from_utf8_lossy(code_units: &[u8], replacement: u8) -> Self {
47
0
        let mut out = [0; N];
48
0
        let mut i = 0;
49
        // Ord is not available in const, so no `.min(N)`
50
0
        let len = if code_units.len() > N {
51
0
            N
52
        } else {
53
0
            code_units.len()
54
        };
55
56
        // Indexing is protected by the len check above
57
        #[expect(clippy::indexing_slicing)]
58
0
        while i < len {
59
0
            let b = code_units[i];
60
0
            if b > 0 && b < 0x80 {
61
0
                out[i] = b;
62
0
            } else {
63
0
                out[i] = replacement;
64
0
            }
65
0
            i += 1;
66
        }
67
68
0
        Self {
69
0
            // SAFETY: `out` only contains ASCII bytes and has same size as `self.bytes`
70
0
            bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
71
0
        }
72
0
    }
73
74
    /// Creates a `TinyAsciiStr<N>` from a UTF-16 slice, replacing invalid code units.
75
    ///
76
    /// Invalid code units, as well as null or non-ASCII code points
77
    /// (i.e. those outside the range U+0001..=U+007F`)
78
    /// will be replaced with the replacement byte.
79
    ///
80
    /// The input slice will be truncated if its length exceeds `N`.
81
0
    pub const fn from_utf16_lossy(code_units: &[u16], replacement: u8) -> Self {
82
0
        let mut out = [0; N];
83
0
        let mut i = 0;
84
        // Ord is not available in const, so no `.min(N)`
85
0
        let len = if code_units.len() > N {
86
0
            N
87
        } else {
88
0
            code_units.len()
89
        };
90
91
        // Indexing is protected by the len check above
92
        #[expect(clippy::indexing_slicing)]
93
0
        while i < len {
94
0
            let b = code_units[i];
95
0
            if b > 0 && b < 0x80 {
96
0
                out[i] = b as u8;
97
0
            } else {
98
0
                out[i] = replacement;
99
0
            }
100
0
            i += 1;
101
        }
102
103
0
        Self {
104
0
            // SAFETY: `out` only contains ASCII bytes and has same size as `self.bytes`
105
0
            bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
106
0
        }
107
0
    }
108
109
    /// Attempts to parse a fixed-length byte array to a `TinyAsciiStr`.
110
    ///
111
    /// The byte array may contain trailing NUL bytes.
112
    ///
113
    /// # Example
114
    ///
115
    /// ```
116
    /// use tinystr::tinystr;
117
    /// use tinystr::TinyAsciiStr;
118
    ///
119
    /// assert_eq!(
120
    ///     TinyAsciiStr::<3>::try_from_raw(*b"GB\0"),
121
    ///     Ok(tinystr!(3, "GB"))
122
    /// );
123
    /// assert_eq!(
124
    ///     TinyAsciiStr::<3>::try_from_raw(*b"USD"),
125
    ///     Ok(tinystr!(3, "USD"))
126
    /// );
127
    /// assert!(TinyAsciiStr::<3>::try_from_raw(*b"\0A\0").is_err());
128
    /// ```
129
0
    pub const fn try_from_raw(raw: [u8; N]) -> Result<Self, ParseError> {
130
0
        Self::try_from_utf8_inner(&raw, true)
131
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::try_from_raw
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::try_from_raw
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::try_from_raw
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::try_from_raw
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::try_from_raw
132
133
0
    pub(crate) const fn try_from_utf8_inner(
134
0
        code_units: &[u8],
135
0
        allow_trailing_null: bool,
136
0
    ) -> Result<Self, ParseError> {
137
0
        if code_units.len() > N {
138
0
            return Err(ParseError::TooLong {
139
0
                max: N,
140
0
                len: code_units.len(),
141
0
            });
142
0
        }
143
144
0
        let mut out = [0; N];
145
0
        let mut i = 0;
146
0
        let mut found_null = false;
147
        // Indexing is protected by TinyStrError::TooLarge
148
        #[expect(clippy::indexing_slicing)]
149
0
        while i < code_units.len() {
150
0
            let b = code_units[i];
151
152
0
            if b == 0 {
153
0
                found_null = true;
154
0
            } else if b >= 0x80 {
155
0
                return Err(ParseError::NonAscii);
156
0
            } else if found_null {
157
                // Error if there are contentful bytes after null
158
0
                return Err(ParseError::ContainsNull);
159
0
            }
160
0
            out[i] = b;
161
162
0
            i += 1;
163
        }
164
165
0
        if !allow_trailing_null && found_null {
166
            // We found some trailing nulls, error
167
0
            return Err(ParseError::ContainsNull);
168
0
        }
169
170
0
        Ok(Self {
171
0
            // SAFETY: `out` only contains ASCII bytes and has same size as `self.bytes`
172
0
            bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
173
0
        })
174
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::try_from_utf8_inner
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::try_from_utf8_inner
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::try_from_utf8_inner
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::try_from_utf8_inner
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::try_from_utf8_inner
175
176
0
    pub(crate) const fn try_from_utf16_inner(
177
0
        code_units: &[u16],
178
0
        start: usize,
179
0
        end: usize,
180
0
        allow_trailing_null: bool,
181
0
    ) -> Result<Self, ParseError> {
182
0
        let len = end - start;
183
0
        if len > N {
184
0
            return Err(ParseError::TooLong { max: N, len });
185
0
        }
186
187
0
        let mut out = [0; N];
188
0
        let mut i = 0;
189
0
        let mut found_null = false;
190
        // Indexing is protected by TinyStrError::TooLarge
191
        #[expect(clippy::indexing_slicing)]
192
0
        while i < len {
193
0
            let b = code_units[start + i];
194
195
0
            if b == 0 {
196
0
                found_null = true;
197
0
            } else if b >= 0x80 {
198
0
                return Err(ParseError::NonAscii);
199
0
            } else if found_null {
200
                // Error if there are contentful bytes after null
201
0
                return Err(ParseError::ContainsNull);
202
0
            }
203
0
            out[i] = b as u8;
204
205
0
            i += 1;
206
        }
207
208
0
        if !allow_trailing_null && found_null {
209
            // We found some trailing nulls, error
210
0
            return Err(ParseError::ContainsNull);
211
0
        }
212
213
0
        Ok(Self {
214
0
            // SAFETY: `out` only contains ASCII bytes and has same size as `self.bytes`
215
0
            bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
216
0
        })
217
0
    }
218
219
    /// Creates a `TinyAsciiStr<N>` containing the decimal representation of
220
    /// the given unsigned integer.
221
    ///
222
    /// If the number of decimal digits exceeds `N`, the highest-magnitude
223
    /// digits are truncated, and the lowest-magnitude digits are returned
224
    /// as the error.
225
    ///
226
    /// Note: this function takes a u32. Larger integer types should probably
227
    /// not be stored in a `TinyAsciiStr`.
228
    ///
229
    /// # Examples
230
    ///
231
    /// ```
232
    /// use tinystr::tinystr;
233
    /// use tinystr::TinyAsciiStr;
234
    ///
235
    /// let s0_4 = TinyAsciiStr::<4>::new_unsigned_decimal(0).unwrap();
236
    /// let s456_4 = TinyAsciiStr::<4>::new_unsigned_decimal(456).unwrap();
237
    /// let s456_3 = TinyAsciiStr::<3>::new_unsigned_decimal(456).unwrap();
238
    /// let s456_2 = TinyAsciiStr::<2>::new_unsigned_decimal(456).unwrap_err();
239
    ///
240
    /// assert_eq!(s0_4, tinystr!(4, "0"));
241
    /// assert_eq!(s456_4, tinystr!(4, "456"));
242
    /// assert_eq!(s456_3, tinystr!(3, "456"));
243
    /// assert_eq!(s456_2, tinystr!(2, "56"));
244
    /// ```
245
    ///
246
    /// Example with saturating the value:
247
    ///
248
    /// ```
249
    /// use tinystr::tinystr;
250
    /// use tinystr::TinyAsciiStr;
251
    ///
252
    /// let str_truncated =
253
    ///     TinyAsciiStr::<2>::new_unsigned_decimal(456).unwrap_or_else(|s| s);
254
    /// let str_saturated = TinyAsciiStr::<2>::new_unsigned_decimal(456)
255
    ///     .unwrap_or(tinystr!(2, "99"));
256
    ///
257
    /// assert_eq!(str_truncated, tinystr!(2, "56"));
258
    /// assert_eq!(str_saturated, tinystr!(2, "99"));
259
    /// ```
260
0
    pub fn new_unsigned_decimal(number: u32) -> Result<Self, Self> {
261
0
        let mut bytes = [AsciiByte::B0; N];
262
0
        let mut x = number;
263
0
        let mut i = 0usize;
264
        #[expect(clippy::indexing_slicing)] // in-range: i < N
265
0
        while i < N && (x != 0 || i == 0) {
266
0
            bytes[N - i - 1] = AsciiByte::from_decimal_digit((x % 10) as u8);
267
0
            x /= 10;
268
0
            i += 1;
269
0
        }
270
0
        if i < N {
271
0
            bytes.copy_within((N - i)..N, 0);
272
0
            bytes[i..N].fill(AsciiByte::B0);
273
0
        }
274
0
        let s = Self { bytes };
275
0
        if x != 0 {
276
0
            Err(s)
277
        } else {
278
0
            Ok(s)
279
        }
280
0
    }
281
282
    #[inline]
283
0
    pub const fn as_str(&self) -> &str {
284
        // as_utf8 is valid utf8
285
0
        unsafe { str::from_utf8_unchecked(self.as_utf8()) }
286
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::as_str
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::as_str
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::as_str
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::as_str
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::as_str
287
288
    #[inline]
289
    #[must_use]
290
0
    pub const fn len(&self) -> usize {
291
0
        if N <= 4 {
292
0
            Aligned4::from_ascii_bytes(&self.bytes).len()
293
0
        } else if N <= 8 {
294
0
            Aligned8::from_ascii_bytes(&self.bytes).len()
295
        } else {
296
0
            let mut i = 0;
297
            #[expect(clippy::indexing_slicing)] // < N is safe
298
0
            while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
299
0
                i += 1
300
            }
301
0
            i
302
        }
303
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::len
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::len
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::len
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::len
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::len
304
305
    #[inline]
306
    #[must_use]
307
0
    pub const fn is_empty(&self) -> bool {
308
0
        self.bytes[0] as u8 == AsciiByte::B0 as u8
309
0
    }
310
311
    #[inline]
312
    #[must_use]
313
0
    pub const fn as_utf8(&self) -> &[u8] {
314
        // Safe because `self.bytes.as_slice()` pointer-casts to `&[u8]`,
315
        // and changing the length of that slice to self.len() < N is safe.
316
        unsafe {
317
0
            core::slice::from_raw_parts(self.bytes.as_slice().as_ptr() as *const u8, self.len())
318
        }
319
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::as_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::as_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::as_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::as_utf8
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::as_utf8
320
321
    #[inline]
322
    #[must_use]
323
0
    pub const fn all_bytes(&self) -> &[u8; N] {
324
        // SAFETY: `self.bytes` has same size as [u8; N]
325
0
        unsafe { &*(self.bytes.as_ptr() as *const [u8; N]) }
326
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::all_bytes
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::all_bytes
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::all_bytes
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::all_bytes
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::all_bytes
327
328
    #[inline]
329
    #[must_use]
330
    /// Resizes a `TinyAsciiStr<N>` to a `TinyAsciiStr<M>`.
331
    ///
332
    /// If `M < len()` the string gets truncated, otherwise only the
333
    /// memory representation changes.
334
0
    pub const fn resize<const M: usize>(self) -> TinyAsciiStr<M> {
335
0
        let mut bytes = [0; M];
336
0
        let mut i = 0;
337
        // Indexing is protected by the loop guard
338
        #[expect(clippy::indexing_slicing)]
339
0
        while i < M && i < N {
340
0
            bytes[i] = self.bytes[i] as u8;
341
0
            i += 1;
342
0
        }
343
        // `self.bytes` only contains ASCII bytes, with no null bytes between
344
        // ASCII characters, so this also holds for `bytes`.
345
0
        unsafe { TinyAsciiStr::from_utf8_unchecked(bytes) }
346
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::resize::<8>
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::resize::<8>
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::resize::<3>
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::resize::<_>
347
348
    #[inline]
349
    #[must_use]
350
    /// Returns a `TinyAsciiStr<Q>` with the concatenation of this string,
351
    /// `TinyAsciiStr<N>`, and another string, `TinyAsciiStr<M>`.
352
    ///
353
    /// If `Q < N + M`, the string gets truncated.
354
    ///
355
    /// # Examples
356
    ///
357
    /// ```
358
    /// use tinystr::tinystr;
359
    /// use tinystr::TinyAsciiStr;
360
    ///
361
    /// let abc = tinystr!(6, "abc");
362
    /// let defg = tinystr!(6, "defg");
363
    ///
364
    /// // The concatenation is successful if Q is large enough...
365
    /// assert_eq!(abc.concat(defg), tinystr!(16, "abcdefg"));
366
    /// assert_eq!(abc.concat(defg), tinystr!(12, "abcdefg"));
367
    /// assert_eq!(abc.concat(defg), tinystr!(8, "abcdefg"));
368
    /// assert_eq!(abc.concat(defg), tinystr!(7, "abcdefg"));
369
    ///
370
    /// /// ...but it truncates of Q is too small.
371
    /// assert_eq!(abc.concat(defg), tinystr!(6, "abcdef"));
372
    /// assert_eq!(abc.concat(defg), tinystr!(2, "ab"));
373
    /// ```
374
0
    pub const fn concat<const M: usize, const Q: usize>(
375
0
        self,
376
0
        other: TinyAsciiStr<M>,
377
0
    ) -> TinyAsciiStr<Q> {
378
0
        let mut result = self.resize::<Q>();
379
0
        let mut i = self.len();
380
0
        let mut j = 0;
381
        // Indexing is protected by the loop guard
382
        #[expect(clippy::indexing_slicing)]
383
0
        while i < Q && j < M {
384
0
            result.bytes[i] = other.bytes[j];
385
0
            i += 1;
386
0
            j += 1;
387
0
        }
388
0
        result
389
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::concat::<4, 8>
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::concat::<_, _>
390
391
    /// # Safety
392
    /// Must be called with a bytes array made of valid ASCII bytes, with no null bytes
393
    /// between ASCII characters
394
    #[must_use]
395
0
    pub const unsafe fn from_utf8_unchecked(code_units: [u8; N]) -> Self {
396
0
        Self {
397
0
            bytes: AsciiByte::to_ascii_byte_array(&code_units),
398
0
        }
399
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::from_utf8_unchecked
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::from_utf8_unchecked
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::from_utf8_unchecked
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::from_utf8_unchecked
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::from_utf8_unchecked
400
}
401
402
macro_rules! check_is {
403
    ($self:ident, $check_int:ident, $check_u8:ident) => {
404
        if N <= 4 {
405
            Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
406
        } else if N <= 8 {
407
            Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
408
        } else {
409
            let mut i = 0;
410
            while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
411
                if !($self.bytes[i] as u8).$check_u8() {
412
                    return false;
413
                }
414
                i += 1;
415
            }
416
            true
417
        }
418
    };
419
    ($self:ident, $check_int:ident, !$check_u8_0_inv:ident, !$check_u8_1_inv:ident) => {
420
        if N <= 4 {
421
            Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
422
        } else if N <= 8 {
423
            Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
424
        } else {
425
            // Won't panic because N is > 8
426
            if ($self.bytes[0] as u8).$check_u8_0_inv() {
427
                return false;
428
            }
429
            let mut i = 1;
430
            while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
431
                if ($self.bytes[i] as u8).$check_u8_1_inv() {
432
                    return false;
433
                }
434
                i += 1;
435
            }
436
            true
437
        }
438
    };
439
    ($self:ident, $check_int:ident, $check_u8_0_inv:ident, $check_u8_1_inv:ident) => {
440
        if N <= 4 {
441
            Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
442
        } else if N <= 8 {
443
            Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
444
        } else {
445
            // Won't panic because N is > 8
446
            if !($self.bytes[0] as u8).$check_u8_0_inv() {
447
                return false;
448
            }
449
            let mut i = 1;
450
            while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
451
                if !($self.bytes[i] as u8).$check_u8_1_inv() {
452
                    return false;
453
                }
454
                i += 1;
455
            }
456
            true
457
        }
458
    };
459
}
460
461
impl<const N: usize> TinyAsciiStr<N> {
462
    /// Checks if the value is composed of ASCII alphabetic characters:
463
    ///
464
    ///  * U+0041 'A' ..= U+005A 'Z', or
465
    ///  * U+0061 'a' ..= U+007A 'z'.
466
    ///
467
    /// # Examples
468
    ///
469
    /// ```
470
    /// use tinystr::TinyAsciiStr;
471
    ///
472
    /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse.");
473
    /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse.");
474
    ///
475
    /// assert!(s1.is_ascii_alphabetic());
476
    /// assert!(!s2.is_ascii_alphabetic());
477
    /// ```
478
    #[inline]
479
    #[must_use]
480
0
    pub const fn is_ascii_alphabetic(&self) -> bool {
481
0
        check_is!(self, is_ascii_alphabetic, is_ascii_alphabetic)
482
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::is_ascii_alphabetic
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::is_ascii_alphabetic
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::is_ascii_alphabetic
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::is_ascii_alphabetic
483
484
    /// Checks if the value is composed of ASCII alphanumeric characters:
485
    ///
486
    ///  * U+0041 'A' ..= U+005A 'Z', or
487
    ///  * U+0061 'a' ..= U+007A 'z', or
488
    ///  * U+0030 '0' ..= U+0039 '9'.
489
    ///
490
    /// # Examples
491
    ///
492
    /// ```
493
    /// use tinystr::TinyAsciiStr;
494
    ///
495
    /// let s1: TinyAsciiStr<4> = "A15b".parse().expect("Failed to parse.");
496
    /// let s2: TinyAsciiStr<4> = "[3@w".parse().expect("Failed to parse.");
497
    ///
498
    /// assert!(s1.is_ascii_alphanumeric());
499
    /// assert!(!s2.is_ascii_alphanumeric());
500
    /// ```
501
    #[inline]
502
    #[must_use]
503
0
    pub const fn is_ascii_alphanumeric(&self) -> bool {
504
0
        check_is!(self, is_ascii_alphanumeric, is_ascii_alphanumeric)
505
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::is_ascii_alphanumeric
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::is_ascii_alphanumeric
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::is_ascii_alphanumeric
506
507
    /// Checks if the value is composed of ASCII decimal digits:
508
    ///
509
    ///  * U+0030 '0' ..= U+0039 '9'.
510
    ///
511
    /// # Examples
512
    ///
513
    /// ```
514
    /// use tinystr::TinyAsciiStr;
515
    ///
516
    /// let s1: TinyAsciiStr<4> = "312".parse().expect("Failed to parse.");
517
    /// let s2: TinyAsciiStr<4> = "3d".parse().expect("Failed to parse.");
518
    ///
519
    /// assert!(s1.is_ascii_numeric());
520
    /// assert!(!s2.is_ascii_numeric());
521
    /// ```
522
    #[inline]
523
    #[must_use]
524
0
    pub const fn is_ascii_numeric(&self) -> bool {
525
0
        check_is!(self, is_ascii_numeric, is_ascii_digit)
526
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::is_ascii_numeric
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::is_ascii_numeric
527
528
    /// Checks if the value is in ASCII lower case.
529
    ///
530
    /// All letter characters are checked for case. Non-letter characters are ignored.
531
    ///
532
    /// # Examples
533
    ///
534
    /// ```
535
    /// use tinystr::TinyAsciiStr;
536
    ///
537
    /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse.");
538
    /// let s2: TinyAsciiStr<4> = "test".parse().expect("Failed to parse.");
539
    /// let s3: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse.");
540
    ///
541
    /// assert!(!s1.is_ascii_lowercase());
542
    /// assert!(s2.is_ascii_lowercase());
543
    /// assert!(s3.is_ascii_lowercase());
544
    /// ```
545
    #[inline]
546
    #[must_use]
547
0
    pub const fn is_ascii_lowercase(&self) -> bool {
548
0
        check_is!(
549
            self,
550
            is_ascii_lowercase,
551
            !is_ascii_uppercase,
552
            !is_ascii_uppercase
553
        )
554
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::is_ascii_lowercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::is_ascii_lowercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::is_ascii_lowercase
555
556
    /// Checks if the value is in ASCII title case.
557
    ///
558
    /// This verifies that the first character is ASCII uppercase and all others ASCII lowercase.
559
    /// Non-letter characters are ignored.
560
    ///
561
    /// # Examples
562
    ///
563
    /// ```
564
    /// use tinystr::TinyAsciiStr;
565
    ///
566
    /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse.");
567
    /// let s2: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse.");
568
    /// let s3: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse.");
569
    ///
570
    /// assert!(!s1.is_ascii_titlecase());
571
    /// assert!(s2.is_ascii_titlecase());
572
    /// assert!(s3.is_ascii_titlecase());
573
    /// ```
574
    #[inline]
575
    #[must_use]
576
0
    pub const fn is_ascii_titlecase(&self) -> bool {
577
0
        check_is!(
578
            self,
579
            is_ascii_titlecase,
580
            !is_ascii_lowercase,
581
            !is_ascii_uppercase
582
        )
583
0
    }
584
585
    /// Checks if the value is in ASCII upper case.
586
    ///
587
    /// All letter characters are checked for case. Non-letter characters are ignored.
588
    ///
589
    /// # Examples
590
    ///
591
    /// ```
592
    /// use tinystr::TinyAsciiStr;
593
    ///
594
    /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse.");
595
    /// let s2: TinyAsciiStr<4> = "TEST".parse().expect("Failed to parse.");
596
    /// let s3: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse.");
597
    ///
598
    /// assert!(!s1.is_ascii_uppercase());
599
    /// assert!(s2.is_ascii_uppercase());
600
    /// assert!(!s3.is_ascii_uppercase());
601
    /// ```
602
    #[inline]
603
    #[must_use]
604
0
    pub const fn is_ascii_uppercase(&self) -> bool {
605
0
        check_is!(
606
            self,
607
            is_ascii_uppercase,
608
            !is_ascii_lowercase,
609
            !is_ascii_lowercase
610
        )
611
0
    }
612
613
    /// Checks if the value is composed of ASCII alphabetic lower case characters:
614
    ///
615
    ///  * U+0061 'a' ..= U+007A 'z',
616
    ///
617
    /// # Examples
618
    ///
619
    /// ```
620
    /// use tinystr::TinyAsciiStr;
621
    ///
622
    /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse.");
623
    /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse.");
624
    /// let s3: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse.");
625
    /// let s4: TinyAsciiStr<4> = "test".parse().expect("Failed to parse.");
626
    /// let s5: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse.");
627
    ///
628
    /// assert!(!s1.is_ascii_alphabetic_lowercase());
629
    /// assert!(!s2.is_ascii_alphabetic_lowercase());
630
    /// assert!(!s3.is_ascii_alphabetic_lowercase());
631
    /// assert!(s4.is_ascii_alphabetic_lowercase());
632
    /// assert!(!s5.is_ascii_alphabetic_lowercase());
633
    /// ```
634
    #[inline]
635
    #[must_use]
636
0
    pub const fn is_ascii_alphabetic_lowercase(&self) -> bool {
637
0
        check_is!(
638
            self,
639
            is_ascii_alphabetic_lowercase,
640
            is_ascii_lowercase,
641
            is_ascii_lowercase
642
        )
643
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::is_ascii_alphabetic_lowercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::is_ascii_alphabetic_lowercase
644
645
    /// Checks if the value is composed of ASCII alphabetic, with the first character being ASCII uppercase, and all others ASCII lowercase.
646
    ///
647
    /// # Examples
648
    ///
649
    /// ```
650
    /// use tinystr::TinyAsciiStr;
651
    ///
652
    /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse.");
653
    /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse.");
654
    /// let s3: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse.");
655
    /// let s4: TinyAsciiStr<4> = "test".parse().expect("Failed to parse.");
656
    /// let s5: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse.");
657
    ///
658
    /// assert!(s1.is_ascii_alphabetic_titlecase());
659
    /// assert!(!s2.is_ascii_alphabetic_titlecase());
660
    /// assert!(!s3.is_ascii_alphabetic_titlecase());
661
    /// assert!(!s4.is_ascii_alphabetic_titlecase());
662
    /// assert!(!s5.is_ascii_alphabetic_titlecase());
663
    /// ```
664
    #[inline]
665
    #[must_use]
666
0
    pub const fn is_ascii_alphabetic_titlecase(&self) -> bool {
667
0
        check_is!(
668
            self,
669
            is_ascii_alphabetic_titlecase,
670
            is_ascii_uppercase,
671
            is_ascii_lowercase
672
        )
673
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::is_ascii_alphabetic_titlecase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::is_ascii_alphabetic_titlecase
674
675
    /// Checks if the value is composed of ASCII alphabetic upper case characters:
676
    ///
677
    ///  * U+0041 'A' ..= U+005A 'Z',
678
    ///
679
    /// # Examples
680
    ///
681
    /// ```
682
    /// use tinystr::TinyAsciiStr;
683
    ///
684
    /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse.");
685
    /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse.");
686
    /// let s3: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse.");
687
    /// let s4: TinyAsciiStr<4> = "TEST".parse().expect("Failed to parse.");
688
    /// let s5: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse.");
689
    ///
690
    /// assert!(!s1.is_ascii_alphabetic_uppercase());
691
    /// assert!(!s2.is_ascii_alphabetic_uppercase());
692
    /// assert!(!s3.is_ascii_alphabetic_uppercase());
693
    /// assert!(s4.is_ascii_alphabetic_uppercase());
694
    /// assert!(!s5.is_ascii_alphabetic_uppercase());
695
    /// ```
696
    #[inline]
697
    #[must_use]
698
0
    pub const fn is_ascii_alphabetic_uppercase(&self) -> bool {
699
0
        check_is!(
700
            self,
701
            is_ascii_alphabetic_uppercase,
702
            is_ascii_uppercase,
703
            is_ascii_uppercase
704
        )
705
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::is_ascii_alphabetic_uppercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::is_ascii_alphabetic_uppercase
706
}
707
708
macro_rules! to {
709
    ($self:ident, $to:ident, $later_char_to:ident $(,$first_char_to:ident)?) => {{
710
        let mut i = 0;
711
        if N <= 4 {
712
            let aligned = Aligned4::from_ascii_bytes(&$self.bytes).$to().to_ascii_bytes();
713
            // Won't panic because self.bytes has length N and aligned has length >= N
714
            #[expect(clippy::indexing_slicing)]
715
            while i < N {
716
                $self.bytes[i] = aligned[i];
717
                i += 1;
718
            }
719
        } else if N <= 8 {
720
            let aligned = Aligned8::from_ascii_bytes(&$self.bytes).$to().to_ascii_bytes();
721
            // Won't panic because self.bytes has length N and aligned has length >= N
722
            #[expect(clippy::indexing_slicing)]
723
            while i < N {
724
                $self.bytes[i] = aligned[i];
725
                i += 1;
726
            }
727
        } else {
728
            while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 {
729
                // SAFETY: AsciiByte is repr(u8) and has same size as u8
730
                unsafe {
731
                    $self.bytes[i] = core::mem::transmute::<u8, AsciiByte>(
732
                        ($self.bytes[i] as u8).$later_char_to()
733
                    );
734
                }
735
                i += 1;
736
            }
737
            // SAFETY: AsciiByte is repr(u8) and has same size as u8
738
            $(
739
                $self.bytes[0] = unsafe {
740
                    core::mem::transmute::<u8, AsciiByte>(($self.bytes[0] as u8).$first_char_to())
741
                };
742
            )?
743
        }
744
        $self
745
    }};
746
}
747
748
impl<const N: usize> TinyAsciiStr<N> {
749
    /// Converts this type to its ASCII lower case equivalent in-place.
750
    ///
751
    /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z', other characters are unchanged.
752
    ///
753
    /// # Examples
754
    ///
755
    /// ```
756
    /// use tinystr::TinyAsciiStr;
757
    ///
758
    /// let s1: TinyAsciiStr<4> = "TeS3".parse().expect("Failed to parse.");
759
    ///
760
    /// assert_eq!(&*s1.to_ascii_lowercase(), "tes3");
761
    /// ```
762
    #[inline]
763
    #[must_use]
764
0
    pub const fn to_ascii_lowercase(mut self) -> Self {
765
0
        to!(self, to_ascii_lowercase, to_ascii_lowercase)
766
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<2>>::to_ascii_lowercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::to_ascii_lowercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::to_ascii_lowercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8>>::to_ascii_lowercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::to_ascii_lowercase
767
768
    /// Converts this type to its ASCII title case equivalent in-place.
769
    ///
770
    /// The first character is converted to ASCII uppercase; the remaining characters
771
    /// are converted to ASCII lowercase.
772
    ///
773
    /// # Examples
774
    ///
775
    /// ```
776
    /// use tinystr::TinyAsciiStr;
777
    ///
778
    /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse.");
779
    ///
780
    /// assert_eq!(&*s1.to_ascii_titlecase(), "Test");
781
    /// ```
782
    #[inline]
783
    #[must_use]
784
0
    pub const fn to_ascii_titlecase(mut self) -> Self {
785
0
        to!(
786
            self,
787
            to_ascii_titlecase,
788
            to_ascii_lowercase,
789
            to_ascii_uppercase
790
        )
791
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<4>>::to_ascii_titlecase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::to_ascii_titlecase
792
793
    /// Converts this type to its ASCII upper case equivalent in-place.
794
    ///
795
    /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', other characters are unchanged.
796
    ///
797
    /// # Examples
798
    ///
799
    /// ```
800
    /// use tinystr::TinyAsciiStr;
801
    ///
802
    /// let s1: TinyAsciiStr<4> = "Tes3".parse().expect("Failed to parse.");
803
    ///
804
    /// assert_eq!(&*s1.to_ascii_uppercase(), "TES3");
805
    /// ```
806
    #[inline]
807
    #[must_use]
808
0
    pub const fn to_ascii_uppercase(mut self) -> Self {
809
0
        to!(self, to_ascii_uppercase, to_ascii_uppercase)
810
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<3>>::to_ascii_uppercase
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_>>::to_ascii_uppercase
811
}
812
813
impl<const N: usize> fmt::Debug for TinyAsciiStr<N> {
814
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
815
0
        fmt::Debug::fmt(self.as_str(), f)
816
0
    }
817
}
818
819
impl<const N: usize> fmt::Display for TinyAsciiStr<N> {
820
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
821
0
        fmt::Display::fmt(self.as_str(), f)
822
0
    }
823
}
824
825
impl<const N: usize> Deref for TinyAsciiStr<N> {
826
    type Target = str;
827
    #[inline]
828
0
    fn deref(&self) -> &str {
829
0
        self.as_str()
830
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8> as core::ops::deref::Deref>::deref
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_> as core::ops::deref::Deref>::deref
831
}
832
833
impl<const N: usize> Borrow<str> for TinyAsciiStr<N> {
834
    #[inline]
835
0
    fn borrow(&self) -> &str {
836
0
        self.as_str()
837
0
    }
838
}
839
840
impl<const N: usize> FromStr for TinyAsciiStr<N> {
841
    type Err = ParseError;
842
    #[inline]
843
0
    fn from_str(s: &str) -> Result<Self, Self::Err> {
844
0
        Self::try_from_str(s)
845
0
    }
846
}
847
848
impl<const N: usize> PartialEq<str> for TinyAsciiStr<N> {
849
0
    fn eq(&self, other: &str) -> bool {
850
0
        self.deref() == other
851
0
    }
852
}
853
854
impl<const N: usize> PartialEq<&str> for TinyAsciiStr<N> {
855
0
    fn eq(&self, other: &&str) -> bool {
856
0
        self.deref() == *other
857
0
    }
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<8> as core::cmp::PartialEq<&str>>::eq
Unexecuted instantiation: <tinystr::ascii::TinyAsciiStr<_> as core::cmp::PartialEq<&str>>::eq
858
}
859
860
#[cfg(feature = "alloc")]
861
impl<const N: usize> PartialEq<alloc::string::String> for TinyAsciiStr<N> {
862
    fn eq(&self, other: &alloc::string::String) -> bool {
863
        self.deref() == other.deref()
864
    }
865
}
866
867
#[cfg(feature = "alloc")]
868
impl<const N: usize> PartialEq<TinyAsciiStr<N>> for alloc::string::String {
869
    fn eq(&self, other: &TinyAsciiStr<N>) -> bool {
870
        self.deref() == other.deref()
871
    }
872
}
873
874
#[cfg(test)]
875
mod test {
876
    use super::*;
877
    use rand::distr::Distribution;
878
    use rand::distr::StandardUniform;
879
    use rand::rngs::SmallRng;
880
    use rand::SeedableRng;
881
882
    const STRINGS: [&str; 26] = [
883
        "Latn",
884
        "laTn",
885
        "windows",
886
        "AR",
887
        "Hans",
888
        "macos",
889
        "AT",
890
        "infiniband",
891
        "FR",
892
        "en",
893
        "Cyrl",
894
        "FromIntegral",
895
        "NO",
896
        "419",
897
        "MacintoshOSX2019",
898
        "a3z",
899
        "A3z",
900
        "A3Z",
901
        "a3Z",
902
        "3A",
903
        "3Z",
904
        "3a",
905
        "3z",
906
        "@@[`{",
907
        "UK",
908
        "E12",
909
    ];
910
911
    fn gen_strings(num_strings: usize, allowed_lengths: &[usize]) -> Vec<String> {
912
        use rand::seq::IndexedRandom;
913
        let mut rng = SmallRng::seed_from_u64(2022);
914
        // Need to do this in 2 steps since the RNG is needed twice
915
        let string_lengths = core::iter::repeat_with(|| *allowed_lengths.choose(&mut rng).unwrap())
916
            .take(num_strings)
917
            .collect::<Vec<usize>>();
918
        string_lengths
919
            .iter()
920
            .map(|len| {
921
                StandardUniform
922
                    .sample_iter(&mut rng)
923
                    .filter(|b: &u8| *b > 0 && *b < 0x80)
924
                    .take(*len)
925
                    .collect::<Vec<u8>>()
926
            })
927
            .map(|byte_vec| String::from_utf8(byte_vec).expect("All ASCII"))
928
            .collect()
929
    }
930
931
    fn check_operation<T, F1, F2, const N: usize>(reference_f: F1, tinystr_f: F2)
932
    where
933
        F1: Fn(&str) -> T,
934
        F2: Fn(TinyAsciiStr<N>) -> T,
935
        T: core::fmt::Debug + core::cmp::PartialEq,
936
    {
937
        for s in STRINGS
938
            .into_iter()
939
            .map(str::to_owned)
940
            .chain(gen_strings(100, &[3, 4, 5, 8, 12]))
941
        {
942
            let t = match TinyAsciiStr::<N>::from_str(&s) {
943
                Ok(t) => t,
944
                Err(ParseError::TooLong { .. }) => continue,
945
                Err(e) => panic!("{}", e),
946
            };
947
            let expected = reference_f(&s);
948
            let actual = tinystr_f(t);
949
            assert_eq!(expected, actual, "TinyAsciiStr<{N}>: {s:?}");
950
951
            let s_utf16: Vec<u16> = s.encode_utf16().collect();
952
            let t = match TinyAsciiStr::<N>::try_from_utf16(&s_utf16) {
953
                Ok(t) => t,
954
                Err(ParseError::TooLong { .. }) => continue,
955
                Err(e) => panic!("{}", e),
956
            };
957
            let expected = reference_f(&s);
958
            let actual = tinystr_f(t);
959
            assert_eq!(expected, actual, "TinyAsciiStr<{N}>: {s:?}");
960
        }
961
    }
962
963
    #[test]
964
    fn test_is_ascii_alphabetic() {
965
        fn check<const N: usize>() {
966
            check_operation(
967
                |s| s.chars().all(|c| c.is_ascii_alphabetic()),
968
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic(&t),
969
            )
970
        }
971
        check::<2>();
972
        check::<3>();
973
        check::<4>();
974
        check::<5>();
975
        check::<8>();
976
        check::<16>();
977
    }
978
979
    #[test]
980
    fn test_is_ascii_alphanumeric() {
981
        fn check<const N: usize>() {
982
            check_operation(
983
                |s| s.chars().all(|c| c.is_ascii_alphanumeric()),
984
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphanumeric(&t),
985
            )
986
        }
987
        check::<2>();
988
        check::<3>();
989
        check::<4>();
990
        check::<5>();
991
        check::<8>();
992
        check::<16>();
993
    }
994
995
    #[test]
996
    fn test_is_ascii_numeric() {
997
        fn check<const N: usize>() {
998
            check_operation(
999
                |s| s.chars().all(|c| c.is_ascii_digit()),
1000
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_numeric(&t),
1001
            )
1002
        }
1003
        check::<2>();
1004
        check::<3>();
1005
        check::<4>();
1006
        check::<5>();
1007
        check::<8>();
1008
        check::<16>();
1009
    }
1010
1011
    #[test]
1012
    fn test_is_ascii_lowercase() {
1013
        fn check<const N: usize>() {
1014
            check_operation(
1015
                |s| {
1016
                    s == TinyAsciiStr::<16>::try_from_str(s)
1017
                        .unwrap()
1018
                        .to_ascii_lowercase()
1019
                        .as_str()
1020
                },
1021
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_lowercase(&t),
1022
            )
1023
        }
1024
        check::<2>();
1025
        check::<3>();
1026
        check::<4>();
1027
        check::<5>();
1028
        check::<8>();
1029
        check::<16>();
1030
    }
1031
1032
    #[test]
1033
    fn test_is_ascii_titlecase() {
1034
        fn check<const N: usize>() {
1035
            check_operation(
1036
                |s| {
1037
                    s == TinyAsciiStr::<16>::try_from_str(s)
1038
                        .unwrap()
1039
                        .to_ascii_titlecase()
1040
                        .as_str()
1041
                },
1042
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_titlecase(&t),
1043
            )
1044
        }
1045
        check::<2>();
1046
        check::<3>();
1047
        check::<4>();
1048
        check::<5>();
1049
        check::<8>();
1050
        check::<16>();
1051
    }
1052
1053
    #[test]
1054
    fn test_is_ascii_uppercase() {
1055
        fn check<const N: usize>() {
1056
            check_operation(
1057
                |s| {
1058
                    s == TinyAsciiStr::<16>::try_from_str(s)
1059
                        .unwrap()
1060
                        .to_ascii_uppercase()
1061
                        .as_str()
1062
                },
1063
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_uppercase(&t),
1064
            )
1065
        }
1066
        check::<2>();
1067
        check::<3>();
1068
        check::<4>();
1069
        check::<5>();
1070
        check::<8>();
1071
        check::<16>();
1072
    }
1073
1074
    #[test]
1075
    fn test_is_ascii_alphabetic_lowercase() {
1076
        fn check<const N: usize>() {
1077
            check_operation(
1078
                |s| {
1079
                    // Check alphabetic
1080
                    s.chars().all(|c| c.is_ascii_alphabetic()) &&
1081
                    // Check lowercase
1082
                    s == TinyAsciiStr::<16>::try_from_str(s)
1083
                        .unwrap()
1084
                        .to_ascii_lowercase()
1085
                        .as_str()
1086
                },
1087
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_lowercase(&t),
1088
            )
1089
        }
1090
        check::<2>();
1091
        check::<3>();
1092
        check::<4>();
1093
        check::<5>();
1094
        check::<8>();
1095
        check::<16>();
1096
    }
1097
1098
    #[test]
1099
    fn test_is_ascii_alphabetic_titlecase() {
1100
        fn check<const N: usize>() {
1101
            check_operation(
1102
                |s| {
1103
                    // Check alphabetic
1104
                    s.chars().all(|c| c.is_ascii_alphabetic()) &&
1105
                    // Check titlecase
1106
                    s == TinyAsciiStr::<16>::try_from_str(s)
1107
                        .unwrap()
1108
                        .to_ascii_titlecase()
1109
                        .as_str()
1110
                },
1111
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_titlecase(&t),
1112
            )
1113
        }
1114
        check::<2>();
1115
        check::<3>();
1116
        check::<4>();
1117
        check::<5>();
1118
        check::<8>();
1119
        check::<16>();
1120
    }
1121
1122
    #[test]
1123
    fn test_is_ascii_alphabetic_uppercase() {
1124
        fn check<const N: usize>() {
1125
            check_operation(
1126
                |s| {
1127
                    // Check alphabetic
1128
                    s.chars().all(|c| c.is_ascii_alphabetic()) &&
1129
                    // Check uppercase
1130
                    s == TinyAsciiStr::<16>::try_from_str(s)
1131
                        .unwrap()
1132
                        .to_ascii_uppercase()
1133
                        .as_str()
1134
                },
1135
                |t: TinyAsciiStr<N>| TinyAsciiStr::is_ascii_alphabetic_uppercase(&t),
1136
            )
1137
        }
1138
        check::<2>();
1139
        check::<3>();
1140
        check::<4>();
1141
        check::<5>();
1142
        check::<8>();
1143
        check::<16>();
1144
    }
1145
1146
    #[test]
1147
    fn test_to_ascii_lowercase() {
1148
        fn check<const N: usize>() {
1149
            check_operation(
1150
                |s| {
1151
                    s.chars()
1152
                        .map(|c| c.to_ascii_lowercase())
1153
                        .collect::<String>()
1154
                },
1155
                |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_lowercase(t).as_str().to_owned(),
1156
            )
1157
        }
1158
        check::<2>();
1159
        check::<3>();
1160
        check::<4>();
1161
        check::<5>();
1162
        check::<8>();
1163
        check::<16>();
1164
    }
1165
1166
    #[test]
1167
    fn test_to_ascii_titlecase() {
1168
        fn check<const N: usize>() {
1169
            check_operation(
1170
                |s| {
1171
                    let mut r = s
1172
                        .chars()
1173
                        .map(|c| c.to_ascii_lowercase())
1174
                        .collect::<String>();
1175
                    // Safe because the string is nonempty and an ASCII string
1176
                    unsafe { r.as_bytes_mut()[0].make_ascii_uppercase() };
1177
                    r
1178
                },
1179
                |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_titlecase(t).as_str().to_owned(),
1180
            )
1181
        }
1182
        check::<2>();
1183
        check::<3>();
1184
        check::<4>();
1185
        check::<5>();
1186
        check::<8>();
1187
        check::<16>();
1188
    }
1189
1190
    #[test]
1191
    fn test_to_ascii_uppercase() {
1192
        fn check<const N: usize>() {
1193
            check_operation(
1194
                |s| {
1195
                    s.chars()
1196
                        .map(|c| c.to_ascii_uppercase())
1197
                        .collect::<String>()
1198
                },
1199
                |t: TinyAsciiStr<N>| TinyAsciiStr::to_ascii_uppercase(t).as_str().to_owned(),
1200
            )
1201
        }
1202
        check::<2>();
1203
        check::<3>();
1204
        check::<4>();
1205
        check::<5>();
1206
        check::<8>();
1207
        check::<16>();
1208
    }
1209
1210
    #[test]
1211
    fn lossy_constructor() {
1212
        assert_eq!(TinyAsciiStr::<4>::from_utf8_lossy(b"", b'?').as_str(), "");
1213
        assert_eq!(
1214
            TinyAsciiStr::<4>::from_utf8_lossy(b"oh\0o", b'?').as_str(),
1215
            "oh?o"
1216
        );
1217
        assert_eq!(
1218
            TinyAsciiStr::<4>::from_utf8_lossy(b"\0", b'?').as_str(),
1219
            "?"
1220
        );
1221
        assert_eq!(
1222
            TinyAsciiStr::<4>::from_utf8_lossy(b"toolong", b'?').as_str(),
1223
            "tool"
1224
        );
1225
        assert_eq!(
1226
            TinyAsciiStr::<4>::from_utf8_lossy(&[b'a', 0x80, 0xFF, b'1'], b'?').as_str(),
1227
            "a??1"
1228
        );
1229
    }
1230
}