ascii/
ascii_char.rs

1use core::cmp::Ordering;
2use core::mem;
3use core::{char, fmt};
4#[cfg(feature = "std")]
5use std::error::Error;
6
7#[allow(non_camel_case_types)]
8/// An ASCII character. It wraps a `u8`, with the highest bit always zero.
9#[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Hash, Copy)]
10#[repr(u8)]
11pub enum AsciiChar {
12    /// `'\0'`
13    Null = 0,
14    /// [Start Of Heading](http://en.wikipedia.org/wiki/Start_of_Heading)
15    SOH = 1,
16    /// [Start Of teXt](http://en.wikipedia.org/wiki/Start_of_Text)
17    SOX = 2,
18    /// [End of TeXt](http://en.wikipedia.org/wiki/End-of-Text_character)
19    ETX = 3,
20    /// [End Of Transmission](http://en.wikipedia.org/wiki/End-of-Transmission_character)
21    EOT = 4,
22    /// [Enquiry](http://en.wikipedia.org/wiki/Enquiry_character)
23    ENQ = 5,
24    /// [Acknowledgement](http://en.wikipedia.org/wiki/Acknowledge_character)
25    ACK = 6,
26    /// [bell / alarm / audible](http://en.wikipedia.org/wiki/Bell_character)
27    ///
28    /// `'\a'` is not recognized by Rust.
29    Bell = 7,
30    /// [Backspace](http://en.wikipedia.org/wiki/Backspace)
31    ///
32    /// `'\b'` is not recognized by Rust.
33    BackSpace = 8,
34    /// `'\t'`
35    Tab = 9,
36    /// `'\n'`
37    LineFeed = 10,
38    /// [Vertical tab](http://en.wikipedia.org/wiki/Vertical_Tab)
39    ///
40    /// `'\v'` is not recognized by Rust.
41    VT = 11,
42    /// [Form Feed](http://en.wikipedia.org/wiki/Form_Feed)
43    ///
44    /// `'\f'` is not recognized by Rust.
45    FF = 12,
46    /// `'\r'`
47    CarriageReturn = 13,
48    /// [Shift In](http://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters)
49    SI = 14,
50    /// [Shift Out](http://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters)
51    SO = 15,
52    /// [Data Link Escape](http://en.wikipedia.org/wiki/Data_Link_Escape)
53    DLE = 16,
54    /// [Device control 1, often XON](http://en.wikipedia.org/wiki/Device_Control_1)
55    DC1 = 17,
56    /// Device control 2
57    DC2 = 18,
58    /// Device control 3, Often XOFF
59    DC3 = 19,
60    /// Device control 4
61    DC4 = 20,
62    /// [Negative AcKnowledgement](http://en.wikipedia.org/wiki/Negative-acknowledge_character)
63    NAK = 21,
64    /// [Synchronous idle](http://en.wikipedia.org/wiki/Synchronous_Idle)
65    SYN = 22,
66    /// [End of Transmission Block](http://en.wikipedia.org/wiki/End-of-Transmission-Block_character)
67    ETB = 23,
68    /// [Cancel](http://en.wikipedia.org/wiki/Cancel_character)
69    CAN = 24,
70    /// [End of Medium](http://en.wikipedia.org/wiki/End_of_Medium)
71    EM = 25,
72    /// [Substitute](http://en.wikipedia.org/wiki/Substitute_character)
73    SUB = 26,
74    /// [Escape](http://en.wikipedia.org/wiki/Escape_character)
75    ///
76    /// `'\e'` is not recognized by Rust.
77    ESC = 27,
78    /// [File Separator](http://en.wikipedia.org/wiki/File_separator)
79    FS = 28,
80    /// [Group Separator](http://en.wikipedia.org/wiki/Group_separator)
81    GS = 29,
82    /// [Record Separator](http://en.wikipedia.org/wiki/Record_separator)
83    RS = 30,
84    /// [Unit Separator](http://en.wikipedia.org/wiki/Unit_separator)
85    US = 31,
86    /// `' '`
87    Space = 32,
88    /// `'!'`
89    Exclamation = 33,
90    /// `'"'`
91    Quotation = 34,
92    /// `'#'`
93    Hash = 35,
94    /// `'$'`
95    Dollar = 36,
96    /// `'%'`
97    Percent = 37,
98    /// `'&'`
99    Ampersand = 38,
100    /// `'\''`
101    Apostrophe = 39,
102    /// `'('`
103    ParenOpen = 40,
104    /// `')'`
105    ParenClose = 41,
106    /// `'*'`
107    Asterisk = 42,
108    /// `'+'`
109    Plus = 43,
110    /// `','`
111    Comma = 44,
112    /// `'-'`
113    Minus = 45,
114    /// `'.'`
115    Dot = 46,
116    /// `'/'`
117    Slash = 47,
118    /// `'0'`
119    _0 = 48,
120    /// `'1'`
121    _1 = 49,
122    /// `'2'`
123    _2 = 50,
124    /// `'3'`
125    _3 = 51,
126    /// `'4'`
127    _4 = 52,
128    /// `'5'`
129    _5 = 53,
130    /// `'6'`
131    _6 = 54,
132    /// `'7'`
133    _7 = 55,
134    /// `'8'`
135    _8 = 56,
136    /// `'9'`
137    _9 = 57,
138    /// `':'`
139    Colon = 58,
140    /// `';'`
141    Semicolon = 59,
142    /// `'<'`
143    LessThan = 60,
144    /// `'='`
145    Equal = 61,
146    /// `'>'`
147    GreaterThan = 62,
148    /// `'?'`
149    Question = 63,
150    /// `'@'`
151    At = 64,
152    /// `'A'`
153    A = 65,
154    /// `'B'`
155    B = 66,
156    /// `'C'`
157    C = 67,
158    /// `'D'`
159    D = 68,
160    /// `'E'`
161    E = 69,
162    /// `'F'`
163    F = 70,
164    /// `'G'`
165    G = 71,
166    /// `'H'`
167    H = 72,
168    /// `'I'`
169    I = 73,
170    /// `'J'`
171    J = 74,
172    /// `'K'`
173    K = 75,
174    /// `'L'`
175    L = 76,
176    /// `'M'`
177    M = 77,
178    /// `'N'`
179    N = 78,
180    /// `'O'`
181    O = 79,
182    /// `'P'`
183    P = 80,
184    /// `'Q'`
185    Q = 81,
186    /// `'R'`
187    R = 82,
188    /// `'S'`
189    S = 83,
190    /// `'T'`
191    T = 84,
192    /// `'U'`
193    U = 85,
194    /// `'V'`
195    V = 86,
196    /// `'W'`
197    W = 87,
198    /// `'X'`
199    X = 88,
200    /// `'Y'`
201    Y = 89,
202    /// `'Z'`
203    Z = 90,
204    /// `'['`
205    BracketOpen = 91,
206    /// `'\'`
207    BackSlash = 92,
208    /// `']'`
209    BracketClose = 93,
210    /// `'^'`
211    Caret = 94,
212    /// `'_'`
213    UnderScore = 95,
214    /// `'`'`
215    Grave = 96,
216    /// `'a'`
217    a = 97,
218    /// `'b'`
219    b = 98,
220    /// `'c'`
221    c = 99,
222    /// `'d'`
223    d = 100,
224    /// `'e'`
225    e = 101,
226    /// `'f'`
227    f = 102,
228    /// `'g'`
229    g = 103,
230    /// `'h'`
231    h = 104,
232    /// `'i'`
233    i = 105,
234    /// `'j'`
235    j = 106,
236    /// `'k'`
237    k = 107,
238    /// `'l'`
239    l = 108,
240    /// `'m'`
241    m = 109,
242    /// `'n'`
243    n = 110,
244    /// `'o'`
245    o = 111,
246    /// `'p'`
247    p = 112,
248    /// `'q'`
249    q = 113,
250    /// `'r'`
251    r = 114,
252    /// `'s'`
253    s = 115,
254    /// `'t'`
255    t = 116,
256    /// `'u'`
257    u = 117,
258    /// `'v'`
259    v = 118,
260    /// `'w'`
261    w = 119,
262    /// `'x'`
263    x = 120,
264    /// `'y'`
265    y = 121,
266    /// `'z'`
267    z = 122,
268    /// `'{'`
269    CurlyBraceOpen = 123,
270    /// `'|'`
271    VerticalBar = 124,
272    /// `'}'`
273    CurlyBraceClose = 125,
274    /// `'~'`
275    Tilde = 126,
276    /// [Delete](http://en.wikipedia.org/wiki/Delete_character)
277    DEL = 127,
278}
279
280impl AsciiChar {
281    /// Constructs an ASCII character from a `u8`, `char` or other character type.
282    ///
283    /// # Errors
284    /// Returns `Err(())` if the character can't be ASCII encoded.
285    ///
286    /// # Example
287    /// ```
288    /// # use ascii::AsciiChar;
289    /// let a = AsciiChar::from_ascii('g').unwrap();
290    /// assert_eq!(a.as_char(), 'g');
291    /// ```
292    #[inline]
293    pub fn from_ascii<C: ToAsciiChar>(ch: C) -> Result<Self, ToAsciiCharError> {
294        ch.to_ascii_char()
295    }
296
297    /// Create an `AsciiChar` from a `char`, panicking if it's not ASCII.
298    ///
299    /// This function is intended for creating `AsciiChar` values from
300    /// hardcoded known-good character literals such as `'K'`, `'-'` or `'\0'`,
301    /// and for use in `const` contexts.
302    /// Use [`from_ascii()`](#method.from_ascii) instead when you're not
303    /// certain the character is ASCII.
304    ///
305    /// # Examples
306    ///
307    /// ```
308    /// # use ascii::AsciiChar;
309    /// assert_eq!(AsciiChar::new('@'), AsciiChar::At);
310    /// assert_eq!(AsciiChar::new('C').as_char(), 'C');
311    /// ```
312    ///
313    /// In a constant:
314    /// ```
315    /// # use ascii::AsciiChar;
316    /// const SPLIT_ON: AsciiChar = AsciiChar::new(',');
317    /// ```
318    ///
319    /// This will not compile:
320    /// ```compile_fail
321    /// # use ascii::AsciiChar;
322    /// const BAD: AsciiChar = AsciiChar::new('Ø');
323    /// ```
324    ///
325    /// # Panics
326    ///
327    /// This function will panic if passed a non-ASCII character.
328    ///
329    /// The panic message might not be the most descriptive due to the
330    /// current limitations of `const fn`.
331    #[must_use]
332    pub const fn new(ch: char) -> AsciiChar {
333        // It's restricted to this function, and without it
334        // we'd need to specify `AsciiChar::` or `Self::` 128 times.
335        #[allow(clippy::enum_glob_use)]
336        use AsciiChar::*;
337
338        #[rustfmt::skip]
339        const ALL: [AsciiChar; 128] = [
340            Null, SOH, SOX, ETX, EOT, ENQ, ACK, Bell,
341            BackSpace, Tab, LineFeed, VT, FF, CarriageReturn, SI, SO,
342            DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB,
343            CAN, EM, SUB, ESC, FS, GS, RS, US,
344            Space, Exclamation, Quotation, Hash, Dollar, Percent, Ampersand, Apostrophe,
345            ParenOpen, ParenClose, Asterisk, Plus, Comma, Minus, Dot, Slash,
346            _0, _1, _2, _3, _4, _5, _6, _7,
347            _8, _9, Colon, Semicolon, LessThan, Equal, GreaterThan, Question,
348            At, A, B, C, D, E, F, G,
349            H, I, J, K, L, M, N, O,
350            P, Q, R, S, T, U, V, W,
351            X, Y, Z, BracketOpen, BackSlash, BracketClose, Caret, UnderScore,
352            Grave, a, b, c, d, e, f, g,
353            h, i, j, k, l, m, n, o,
354            p, q, r, s, t, u, v, w,
355            x, y, z, CurlyBraceOpen, VerticalBar, CurlyBraceClose, Tilde, DEL,
356		];
357
358        // We want to slice here and detect `const_err` from rustc if the slice is invalid
359        #[allow(clippy::indexing_slicing)]
360        ALL[ch as usize]
361    }
362
363    /// Constructs an ASCII character from a `u8`, `char` or other character
364    /// type without any checks.
365    ///
366    /// # Safety
367    ///
368    /// This function is very unsafe as it can create invalid enum
369    /// discriminants, which instantly creates undefined behavior.
370    /// (`let _ = AsciiChar::from_ascii_unchecked(200);` alone is UB).
371    ///
372    /// The undefined behavior is not just theoretical either:
373    /// For example, `[0; 128][AsciiChar::from_ascii_unchecked(255) as u8 as usize] = 0`
374    /// might not panic, creating a buffer overflow,
375    /// and `Some(AsciiChar::from_ascii_unchecked(128))` might be `None`.
376    #[inline]
377    #[must_use]
378    pub unsafe fn from_ascii_unchecked(ch: u8) -> Self {
379        // SAFETY: Caller guarantees `ch` is within bounds of ascii.
380        unsafe { ch.to_ascii_char_unchecked() }
381    }
382
383    /// Converts an ASCII character into a `u8`.
384    #[inline]
385    #[must_use]
386    pub const fn as_byte(self) -> u8 {
387        self as u8
388    }
389
390    /// Converts an ASCII character into a `char`.
391    #[inline]
392    #[must_use]
393    pub const fn as_char(self) -> char {
394        self as u8 as char
395    }
396
397    // the following methods are like ctype, and the implementation is inspired by musl.
398    // The ascii_ methods take self by reference for maximum compatibility
399    // with the corresponding methods on u8 and char.
400    // It is bad for both usability and performance, but marking those
401    // that doesn't have a non-ascii sibling #[inline] should
402    // make the compiler optimize away the indirection.
403
404    /// Turns uppercase into lowercase, but also modifies '@' and '<'..='_'
405    #[must_use]
406    const fn to_not_upper(self) -> u8 {
407        self as u8 | 0b010_0000
408    }
409
410    /// Check if the character is a letter (a-z, A-Z)
411    #[inline]
412    #[must_use]
413    pub const fn is_alphabetic(self) -> bool {
414        (self.to_not_upper() >= b'a') & (self.to_not_upper() <= b'z')
415    }
416
417    /// Check if the character is a letter (a-z, A-Z).
418    ///
419    /// This method is identical to [`is_alphabetic()`](#method.is_alphabetic)
420    #[inline]
421    #[must_use]
422    pub const fn is_ascii_alphabetic(&self) -> bool {
423        self.is_alphabetic()
424    }
425
426    /// Check if the character is a digit in the given radix.
427    ///
428    /// If the radix is always 10 or 16,
429    /// [`is_ascii_digit()`](#method.is_ascii_digit) and
430    /// [`is_ascii_hexdigit()`](#method.is_ascii_hexdigit()) will be faster.
431    ///
432    /// # Panics
433    ///
434    /// Radixes greater than 36 are not supported and will result in a panic.
435    #[must_use]
436    pub fn is_digit(self, radix: u32) -> bool {
437        match (self as u8, radix) {
438            (b'0'..=b'9', 0..=36) => u32::from(self as u8 - b'0') < radix,
439            (b'a'..=b'z', 11..=36) => u32::from(self as u8 - b'a') < radix - 10,
440            (b'A'..=b'Z', 11..=36) => u32::from(self as u8 - b'A') < radix - 10,
441            (_, 0..=36) => false,
442            (_, _) => panic!("radixes greater than 36 are not supported"),
443        }
444    }
445
446    /// Check if the character is a number (0-9)
447    ///
448    /// # Examples
449    /// ```
450    /// # use ascii::AsciiChar;
451    /// assert_eq!(AsciiChar::new('0').is_ascii_digit(), true);
452    /// assert_eq!(AsciiChar::new('9').is_ascii_digit(), true);
453    /// assert_eq!(AsciiChar::new('a').is_ascii_digit(), false);
454    /// assert_eq!(AsciiChar::new('A').is_ascii_digit(), false);
455    /// assert_eq!(AsciiChar::new('/').is_ascii_digit(), false);
456    /// ```
457    #[inline]
458    #[must_use]
459    pub const fn is_ascii_digit(&self) -> bool {
460        (*self as u8 >= b'0') & (*self as u8 <= b'9')
461    }
462
463    /// Check if the character is a letter or number
464    #[inline]
465    #[must_use]
466    pub const fn is_alphanumeric(self) -> bool {
467        self.is_alphabetic() | self.is_ascii_digit()
468    }
469
470    /// Check if the character is a letter or number
471    ///
472    /// This method is identical to [`is_alphanumeric()`](#method.is_alphanumeric)
473    #[inline]
474    #[must_use]
475    pub const fn is_ascii_alphanumeric(&self) -> bool {
476        self.is_alphanumeric()
477    }
478
479    /// Check if the character is a space or horizontal tab
480    ///
481    /// # Examples
482    /// ```
483    /// # use ascii::AsciiChar;
484    /// assert!(AsciiChar::Space.is_ascii_blank());
485    /// assert!(AsciiChar::Tab.is_ascii_blank());
486    /// assert!(!AsciiChar::VT.is_ascii_blank());
487    /// assert!(!AsciiChar::LineFeed.is_ascii_blank());
488    /// assert!(!AsciiChar::CarriageReturn.is_ascii_blank());
489    /// assert!(!AsciiChar::FF.is_ascii_blank());
490    /// ```
491    #[inline]
492    #[must_use]
493    pub const fn is_ascii_blank(&self) -> bool {
494        (*self as u8 == b' ') | (*self as u8 == b'\t')
495    }
496
497    /// Check if the character one of ' ', '\t', '\n', '\r',
498    /// '\0xb' (vertical tab) or '\0xc' (form feed).
499    #[inline]
500    #[must_use]
501    pub const fn is_whitespace(self) -> bool {
502        let b = self as u8;
503        self.is_ascii_blank() | (b == b'\n') | (b == b'\r') | (b == 0x0b) | (b == 0x0c)
504    }
505
506    /// Check if the character is a ' ', '\t', '\n', '\r' or '\0xc' (form feed).
507    ///
508    /// This method is NOT identical to `is_whitespace()`.
509    #[inline]
510    #[must_use]
511    pub const fn is_ascii_whitespace(&self) -> bool {
512        self.is_ascii_blank()
513            | (*self as u8 == b'\n')
514            | (*self as u8 == b'\r')
515            | (*self as u8 == 0x0c/*form feed*/)
516    }
517
518    /// Check if the character is a control character
519    ///
520    /// # Examples
521    /// ```
522    /// # use ascii::AsciiChar;
523    /// assert_eq!(AsciiChar::new('\0').is_ascii_control(), true);
524    /// assert_eq!(AsciiChar::new('n').is_ascii_control(), false);
525    /// assert_eq!(AsciiChar::new(' ').is_ascii_control(), false);
526    /// assert_eq!(AsciiChar::new('\n').is_ascii_control(), true);
527    /// assert_eq!(AsciiChar::new('\t').is_ascii_control(), true);
528    /// assert_eq!(AsciiChar::EOT.is_ascii_control(), true);
529    /// ```
530    #[inline]
531    #[must_use]
532    pub const fn is_ascii_control(&self) -> bool {
533        ((*self as u8) < b' ') | (*self as u8 == 127)
534    }
535
536    /// Checks if the character is printable (except space)
537    ///
538    /// # Examples
539    /// ```
540    /// # use ascii::AsciiChar;
541    /// assert_eq!(AsciiChar::new('n').is_ascii_graphic(), true);
542    /// assert_eq!(AsciiChar::new(' ').is_ascii_graphic(), false);
543    /// assert_eq!(AsciiChar::new('\n').is_ascii_graphic(), false);
544    /// ```
545    #[inline]
546    #[must_use]
547    pub const fn is_ascii_graphic(&self) -> bool {
548        self.as_byte().wrapping_sub(b' ' + 1) < 0x5E
549    }
550
551    /// Checks if the character is printable (including space)
552    ///
553    /// # Examples
554    /// ```
555    /// # use ascii::AsciiChar;
556    /// assert_eq!(AsciiChar::new('n').is_ascii_printable(), true);
557    /// assert_eq!(AsciiChar::new(' ').is_ascii_printable(), true);
558    /// assert_eq!(AsciiChar::new('\n').is_ascii_printable(), false);
559    /// ```
560    #[inline]
561    #[must_use]
562    pub const fn is_ascii_printable(&self) -> bool {
563        self.as_byte().wrapping_sub(b' ') < 0x5F
564    }
565
566    /// Checks if the character is alphabetic and lowercase (a-z).
567    ///
568    /// # Examples
569    /// ```
570    /// use ascii::AsciiChar;
571    /// assert_eq!(AsciiChar::new('a').is_lowercase(), true);
572    /// assert_eq!(AsciiChar::new('A').is_lowercase(), false);
573    /// assert_eq!(AsciiChar::new('@').is_lowercase(), false);
574    /// ```
575    #[inline]
576    #[must_use]
577    pub const fn is_lowercase(self) -> bool {
578        self.as_byte().wrapping_sub(b'a') < 26
579    }
580
581    /// Checks if the character is alphabetic and lowercase (a-z).
582    ///
583    /// This method is identical to [`is_lowercase()`](#method.is_lowercase)
584    #[inline]
585    #[must_use]
586    pub const fn is_ascii_lowercase(&self) -> bool {
587        self.is_lowercase()
588    }
589
590    /// Checks if the character is alphabetic and uppercase (A-Z).
591    ///
592    /// # Examples
593    /// ```
594    /// # use ascii::AsciiChar;
595    /// assert_eq!(AsciiChar::new('A').is_uppercase(), true);
596    /// assert_eq!(AsciiChar::new('a').is_uppercase(), false);
597    /// assert_eq!(AsciiChar::new('@').is_uppercase(), false);
598    /// ```
599    #[inline]
600    #[must_use]
601    pub const fn is_uppercase(self) -> bool {
602        self.as_byte().wrapping_sub(b'A') < 26
603    }
604
605    /// Checks if the character is alphabetic and uppercase (A-Z).
606    ///
607    /// This method is identical to [`is_uppercase()`](#method.is_uppercase)
608    #[inline]
609    #[must_use]
610    pub const fn is_ascii_uppercase(&self) -> bool {
611        self.is_uppercase()
612    }
613
614    /// Checks if the character is punctuation
615    ///
616    /// # Examples
617    /// ```
618    /// # use ascii::AsciiChar;
619    /// assert_eq!(AsciiChar::new('n').is_ascii_punctuation(), false);
620    /// assert_eq!(AsciiChar::new(' ').is_ascii_punctuation(), false);
621    /// assert_eq!(AsciiChar::new('_').is_ascii_punctuation(), true);
622    /// assert_eq!(AsciiChar::new('~').is_ascii_punctuation(), true);
623    /// ```
624    #[inline]
625    #[must_use]
626    pub const fn is_ascii_punctuation(&self) -> bool {
627        self.is_ascii_graphic() & !self.is_alphanumeric()
628    }
629
630    /// Checks if the character is a valid hex digit
631    ///
632    /// # Examples
633    /// ```
634    /// # use ascii::AsciiChar;
635    /// assert_eq!(AsciiChar::new('5').is_ascii_hexdigit(), true);
636    /// assert_eq!(AsciiChar::new('a').is_ascii_hexdigit(), true);
637    /// assert_eq!(AsciiChar::new('F').is_ascii_hexdigit(), true);
638    /// assert_eq!(AsciiChar::new('G').is_ascii_hexdigit(), false);
639    /// assert_eq!(AsciiChar::new(' ').is_ascii_hexdigit(), false);
640    /// ```
641    #[inline]
642    #[must_use]
643    pub const fn is_ascii_hexdigit(&self) -> bool {
644        self.is_ascii_digit() | ((*self as u8 | 0x20_u8).wrapping_sub(b'a') < 6)
645    }
646
647    /// Unicode has printable versions of the ASCII control codes, like '␛'.
648    ///
649    /// This function is identical with `.as_char()`
650    /// for all values `.is_printable()` returns true for,
651    /// but replaces the control codes with those unicodes printable versions.
652    ///
653    /// # Examples
654    /// ```
655    /// # use ascii::AsciiChar;
656    /// assert_eq!(AsciiChar::new('\0').as_printable_char(), '␀');
657    /// assert_eq!(AsciiChar::new('\n').as_printable_char(), '␊');
658    /// assert_eq!(AsciiChar::new(' ').as_printable_char(), ' ');
659    /// assert_eq!(AsciiChar::new('p').as_printable_char(), 'p');
660    /// ```
661    #[must_use]
662    pub fn as_printable_char(self) -> char {
663        match self as u8 {
664            // Non printable characters
665            // SAFETY: From codepoint 0x2400 ('␀') to 0x241f (`␟`), there are characters representing
666            //         the unprintable characters from 0x0 to 0x1f, ordered correctly.
667            //         As `b` is guaranteed to be within 0x0 to 0x1f, the conversion represents a
668            //         valid character.
669            b @ 0x0..=0x1f => unsafe { char::from_u32_unchecked(u32::from('␀') + u32::from(b)) },
670
671            // 0x7f (delete) has it's own character at codepoint 0x2420, not 0x247f, so it is special
672            // cased to return it's character
673            0x7f => '␡',
674
675            // All other characters are printable, and per function contract use `Self::as_char`
676            _ => self.as_char(),
677        }
678    }
679
680    /// Replaces letters `a` to `z` with `A` to `Z`
681    pub fn make_ascii_uppercase(&mut self) {
682        *self = self.to_ascii_uppercase();
683    }
684
685    /// Replaces letters `A` to `Z` with `a` to `z`
686    pub fn make_ascii_lowercase(&mut self) {
687        *self = self.to_ascii_lowercase();
688    }
689
690    /// Maps letters a-z to A-Z and returns any other character unchanged.
691    ///
692    /// # Examples
693    /// ```
694    /// # use ascii::AsciiChar;
695    /// assert_eq!(AsciiChar::new('u').to_ascii_uppercase().as_char(), 'U');
696    /// assert_eq!(AsciiChar::new('U').to_ascii_uppercase().as_char(), 'U');
697    /// assert_eq!(AsciiChar::new('2').to_ascii_uppercase().as_char(), '2');
698    /// assert_eq!(AsciiChar::new('=').to_ascii_uppercase().as_char(), '=');
699    /// assert_eq!(AsciiChar::new('[').to_ascii_uppercase().as_char(), '[');
700    /// ```
701    #[inline]
702    #[must_use]
703    #[allow(clippy::indexing_slicing)] // We're sure it'll either access one or the other, as `bool` is either `0` or `1`
704    pub const fn to_ascii_uppercase(&self) -> Self {
705        [*self, AsciiChar::new((*self as u8 & 0b101_1111) as char)][self.is_lowercase() as usize]
706    }
707
708    /// Maps letters A-Z to a-z and returns any other character unchanged.
709    ///
710    /// # Examples
711    /// ```
712    /// # use ascii::AsciiChar;
713    /// assert_eq!(AsciiChar::new('U').to_ascii_lowercase().as_char(), 'u');
714    /// assert_eq!(AsciiChar::new('u').to_ascii_lowercase().as_char(), 'u');
715    /// assert_eq!(AsciiChar::new('2').to_ascii_lowercase().as_char(), '2');
716    /// assert_eq!(AsciiChar::new('^').to_ascii_lowercase().as_char(), '^');
717    /// assert_eq!(AsciiChar::new('\x7f').to_ascii_lowercase().as_char(), '\x7f');
718    /// ```
719    #[inline]
720    #[must_use]
721    #[allow(clippy::indexing_slicing)] // We're sure it'll either access one or the other, as `bool` is either `0` or `1`
722    pub const fn to_ascii_lowercase(&self) -> Self {
723        [*self, AsciiChar::new(self.to_not_upper() as char)][self.is_uppercase() as usize]
724    }
725
726    /// Compares two characters case-insensitively.
727    #[inline]
728    #[must_use]
729    pub const fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
730        (self.as_byte() == other.as_byte())
731            | (self.is_alphabetic() & (self.to_not_upper() == other.to_not_upper()))
732    }
733}
734
735impl fmt::Display for AsciiChar {
736    #[inline]
737    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
738        self.as_char().fmt(f)
739    }
740}
741
742impl fmt::Debug for AsciiChar {
743    #[inline]
744    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
745        self.as_char().fmt(f)
746    }
747}
748
749impl Default for AsciiChar {
750    fn default() -> AsciiChar {
751        AsciiChar::Null
752    }
753}
754
755macro_rules! impl_into_partial_eq_ord {
756    ($wider:ty, $to_wider:expr) => {
757        impl From<AsciiChar> for $wider {
758            #[inline]
759            fn from(a: AsciiChar) -> $wider {
760                $to_wider(a)
761            }
762        }
763        impl PartialEq<$wider> for AsciiChar {
764            #[inline]
765            fn eq(&self, rhs: &$wider) -> bool {
766                $to_wider(*self) == *rhs
767            }
768        }
769        impl PartialEq<AsciiChar> for $wider {
770            #[inline]
771            fn eq(&self, rhs: &AsciiChar) -> bool {
772                *self == $to_wider(*rhs)
773            }
774        }
775        impl PartialOrd<$wider> for AsciiChar {
776            #[inline]
777            fn partial_cmp(&self, rhs: &$wider) -> Option<Ordering> {
778                $to_wider(*self).partial_cmp(rhs)
779            }
780        }
781        impl PartialOrd<AsciiChar> for $wider {
782            #[inline]
783            fn partial_cmp(&self, rhs: &AsciiChar) -> Option<Ordering> {
784                self.partial_cmp(&$to_wider(*rhs))
785            }
786        }
787    };
788}
789impl_into_partial_eq_ord! {u8, AsciiChar::as_byte}
790impl_into_partial_eq_ord! {char, AsciiChar::as_char}
791
792/// Error returned by `ToAsciiChar`.
793#[derive(Clone, Copy, PartialEq, Eq)]
794pub struct ToAsciiCharError(());
795
796const ERRORMSG_CHAR: &str = "not an ASCII character";
797
798#[cfg(not(feature = "std"))]
799impl ToAsciiCharError {
800    /// Returns a description for this error, like `std::error::Error::description`.
801    #[inline]
802    #[must_use]
803    #[allow(clippy::unused_self)]
804    pub const fn description(&self) -> &'static str {
805        ERRORMSG_CHAR
806    }
807}
808
809impl fmt::Debug for ToAsciiCharError {
810    fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
811        write!(fmtr, "{}", ERRORMSG_CHAR)
812    }
813}
814
815impl fmt::Display for ToAsciiCharError {
816    fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
817        write!(fmtr, "{}", ERRORMSG_CHAR)
818    }
819}
820
821#[cfg(feature = "std")]
822impl Error for ToAsciiCharError {
823    #[inline]
824    fn description(&self) -> &'static str {
825        ERRORMSG_CHAR
826    }
827}
828
829/// Convert `char`, `u8` and other character types to `AsciiChar`.
830pub trait ToAsciiChar {
831    /// Convert to `AsciiChar`.
832    ///
833    /// # Errors
834    /// If `self` is outside the valid ascii range, this returns `Err`
835    fn to_ascii_char(self) -> Result<AsciiChar, ToAsciiCharError>;
836
837    /// Convert to `AsciiChar` without checking that it is an ASCII character.
838    ///
839    /// # Safety
840    /// Calling this function with a value outside of the ascii range, `0x0` to `0x7f` inclusive,
841    /// is undefined behavior.
842    // TODO: Make sure this is the contract we want to express in this function.
843    //       It is ambigous if numbers such as `0xffffff20_u32` are valid ascii characters,
844    //       as this function returns `Ascii::Space` due to the cast to `u8`, even though
845    //       `to_ascii_char` returns `Err()`.
846    unsafe fn to_ascii_char_unchecked(self) -> AsciiChar;
847}
848
849impl ToAsciiChar for AsciiChar {
850    #[inline]
851    fn to_ascii_char(self) -> Result<AsciiChar, ToAsciiCharError> {
852        Ok(self)
853    }
854
855    #[inline]
856    unsafe fn to_ascii_char_unchecked(self) -> AsciiChar {
857        self
858    }
859}
860
861impl ToAsciiChar for u8 {
862    #[inline]
863    fn to_ascii_char(self) -> Result<AsciiChar, ToAsciiCharError> {
864        u32::from(self).to_ascii_char()
865    }
866    #[inline]
867    unsafe fn to_ascii_char_unchecked(self) -> AsciiChar {
868        // SAFETY: Caller guarantees `self` is within bounds of the enum
869        //         variants, so this cast successfully produces a valid ascii
870        //         variant
871        unsafe { mem::transmute::<u8, AsciiChar>(self) }
872    }
873}
874
875// Note: Casts to `u8` here does not cause problems, as the negative
876//       range is mapped outside of ascii bounds and we don't mind losing
877//       the sign, as long as negative numbers are mapped outside ascii range.
878#[allow(clippy::cast_sign_loss)]
879impl ToAsciiChar for i8 {
880    #[inline]
881    fn to_ascii_char(self) -> Result<AsciiChar, ToAsciiCharError> {
882        u32::from(self as u8).to_ascii_char()
883    }
884    #[inline]
885    unsafe fn to_ascii_char_unchecked(self) -> AsciiChar {
886        // SAFETY: Caller guarantees `self` is within bounds of the enum
887        //         variants, so this cast successfully produces a valid ascii
888        //         variant
889        unsafe { mem::transmute::<u8, AsciiChar>(self as u8) }
890    }
891}
892
893impl ToAsciiChar for char {
894    #[inline]
895    fn to_ascii_char(self) -> Result<AsciiChar, ToAsciiCharError> {
896        u32::from(self).to_ascii_char()
897    }
898    #[inline]
899    unsafe fn to_ascii_char_unchecked(self) -> AsciiChar {
900        // SAFETY: Caller guarantees we're within ascii range.
901        unsafe { u32::from(self).to_ascii_char_unchecked() }
902    }
903}
904
905impl ToAsciiChar for u32 {
906    fn to_ascii_char(self) -> Result<AsciiChar, ToAsciiCharError> {
907        match self {
908            // SAFETY: We're within the valid ascii range in this branch.
909            0x0..=0x7f => Ok(unsafe { self.to_ascii_char_unchecked() }),
910            _ => Err(ToAsciiCharError(())),
911        }
912    }
913
914    #[inline]
915    unsafe fn to_ascii_char_unchecked(self) -> AsciiChar {
916        // Note: This cast discards the top bytes, this may cause problems, see
917        //       the TODO on this method's documentation in the trait.
918        // SAFETY: Caller guarantees we're within ascii range.
919        #[allow(clippy::cast_possible_truncation)] // We want to truncate it
920        unsafe {
921            (self as u8).to_ascii_char_unchecked()
922        }
923    }
924}
925
926impl ToAsciiChar for u16 {
927    fn to_ascii_char(self) -> Result<AsciiChar, ToAsciiCharError> {
928        u32::from(self).to_ascii_char()
929    }
930    #[inline]
931    unsafe fn to_ascii_char_unchecked(self) -> AsciiChar {
932        // Note: This cast discards the top bytes, this may cause problems, see
933        //       the TODO on this method's documentation in the trait.
934        // SAFETY: Caller guarantees we're within ascii range.
935        #[allow(clippy::cast_possible_truncation)] // We want to truncate it
936        unsafe {
937            (self as u8).to_ascii_char_unchecked()
938        }
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use super::{AsciiChar, ToAsciiChar, ToAsciiCharError};
945
946    #[test]
947    fn to_ascii_char() {
948        fn generic<C: ToAsciiChar>(ch: C) -> Result<AsciiChar, ToAsciiCharError> {
949            ch.to_ascii_char()
950        }
951        assert_eq!(generic(AsciiChar::A), Ok(AsciiChar::A));
952        assert_eq!(generic(b'A'), Ok(AsciiChar::A));
953        assert_eq!(generic('A'), Ok(AsciiChar::A));
954        assert!(generic(200_u16).is_err());
955        assert!(generic('λ').is_err());
956    }
957
958    #[test]
959    fn as_byte_and_char() {
960        assert_eq!(AsciiChar::A.as_byte(), b'A');
961        assert_eq!(AsciiChar::A.as_char(), 'A');
962    }
963
964    #[test]
965    fn new_array_is_correct() {
966        for byte in 0..128_u8 {
967            assert_eq!(AsciiChar::new(byte as char).as_byte(), byte);
968        }
969    }
970
971    #[test]
972    fn is_all() {
973        #![allow(clippy::is_digit_ascii_radix)] // testing it
974        for byte in 0..128_u8 {
975            let ch = byte as char;
976            let ascii = AsciiChar::new(ch);
977            assert_eq!(ascii.is_alphabetic(), ch.is_alphabetic());
978            assert_eq!(ascii.is_ascii_alphabetic(), ch.is_ascii_alphabetic());
979            assert_eq!(ascii.is_alphanumeric(), ch.is_alphanumeric());
980            assert_eq!(ascii.is_ascii_alphanumeric(), ch.is_ascii_alphanumeric());
981            assert_eq!(ascii.is_digit(8), ch.is_digit(8), "is_digit(8) {:?}", ch);
982            assert_eq!(ascii.is_digit(10), ch.is_digit(10), "is_digit(10) {:?}", ch);
983            assert_eq!(ascii.is_digit(16), ch.is_digit(16), "is_digit(16) {:?}", ch);
984            assert_eq!(ascii.is_digit(36), ch.is_digit(36), "is_digit(36) {:?}", ch);
985            assert_eq!(ascii.is_ascii_digit(), ch.is_ascii_digit());
986            assert_eq!(ascii.is_ascii_hexdigit(), ch.is_ascii_hexdigit());
987            assert_eq!(ascii.is_ascii_control(), ch.is_ascii_control());
988            assert_eq!(ascii.is_ascii_graphic(), ch.is_ascii_graphic());
989            assert_eq!(ascii.is_ascii_punctuation(), ch.is_ascii_punctuation());
990            assert_eq!(
991                ascii.is_whitespace(),
992                ch.is_whitespace(),
993                "{:?} ({:#04x})",
994                ch,
995                byte
996            );
997            assert_eq!(
998                ascii.is_ascii_whitespace(),
999                ch.is_ascii_whitespace(),
1000                "{:?} ({:#04x})",
1001                ch,
1002                byte
1003            );
1004            assert_eq!(ascii.is_uppercase(), ch.is_uppercase());
1005            assert_eq!(ascii.is_ascii_uppercase(), ch.is_ascii_uppercase());
1006            assert_eq!(ascii.is_lowercase(), ch.is_lowercase());
1007            assert_eq!(ascii.is_ascii_lowercase(), ch.is_ascii_lowercase());
1008            assert_eq!(ascii.to_ascii_uppercase(), ch.to_ascii_uppercase());
1009            assert_eq!(ascii.to_ascii_lowercase(), ch.to_ascii_lowercase());
1010        }
1011    }
1012
1013    #[test]
1014    fn is_digit_strange_radixes() {
1015        assert_eq!(AsciiChar::_0.is_digit(0), '0'.is_digit(0));
1016        assert_eq!(AsciiChar::_0.is_digit(1), '0'.is_digit(1));
1017        assert_eq!(AsciiChar::_5.is_digit(5), '5'.is_digit(5));
1018        assert_eq!(AsciiChar::z.is_digit(35), 'z'.is_digit(35));
1019    }
1020
1021    #[test]
1022    #[should_panic]
1023    fn is_digit_bad_radix() {
1024        let _ = AsciiChar::_7.is_digit(37);
1025    }
1026
1027    #[test]
1028    fn cmp_wider() {
1029        assert_eq!(AsciiChar::A, 'A');
1030        assert_eq!(b'b', AsciiChar::b);
1031        assert!(AsciiChar::a < 'z');
1032    }
1033
1034    #[test]
1035    fn ascii_case() {
1036        assert_eq!(AsciiChar::At.to_ascii_lowercase(), AsciiChar::At);
1037        assert_eq!(AsciiChar::At.to_ascii_uppercase(), AsciiChar::At);
1038        assert_eq!(AsciiChar::A.to_ascii_lowercase(), AsciiChar::a);
1039        assert_eq!(AsciiChar::A.to_ascii_uppercase(), AsciiChar::A);
1040        assert_eq!(AsciiChar::a.to_ascii_lowercase(), AsciiChar::a);
1041        assert_eq!(AsciiChar::a.to_ascii_uppercase(), AsciiChar::A);
1042
1043        let mut mutable = (AsciiChar::A, AsciiChar::a);
1044        mutable.0.make_ascii_lowercase();
1045        mutable.1.make_ascii_uppercase();
1046        assert_eq!(mutable.0, AsciiChar::a);
1047        assert_eq!(mutable.1, AsciiChar::A);
1048
1049        assert!(AsciiChar::LineFeed.eq_ignore_ascii_case(&AsciiChar::LineFeed));
1050        assert!(!AsciiChar::LineFeed.eq_ignore_ascii_case(&AsciiChar::CarriageReturn));
1051        assert!(AsciiChar::z.eq_ignore_ascii_case(&AsciiChar::Z));
1052        assert!(AsciiChar::Z.eq_ignore_ascii_case(&AsciiChar::z));
1053        assert!(AsciiChar::A.eq_ignore_ascii_case(&AsciiChar::a));
1054        assert!(!AsciiChar::K.eq_ignore_ascii_case(&AsciiChar::C));
1055        assert!(!AsciiChar::Z.eq_ignore_ascii_case(&AsciiChar::DEL));
1056        assert!(!AsciiChar::BracketOpen.eq_ignore_ascii_case(&AsciiChar::CurlyBraceOpen));
1057        assert!(!AsciiChar::Grave.eq_ignore_ascii_case(&AsciiChar::At));
1058        assert!(!AsciiChar::Grave.eq_ignore_ascii_case(&AsciiChar::DEL));
1059    }
1060
1061    #[test]
1062    #[cfg(feature = "std")]
1063    fn fmt_ascii() {
1064        assert_eq!(format!("{}", AsciiChar::t), "t");
1065        assert_eq!(format!("{:?}", AsciiChar::t), "'t'");
1066        assert_eq!(format!("{}", AsciiChar::LineFeed), "\n");
1067        assert_eq!(format!("{:?}", AsciiChar::LineFeed), "'\\n'");
1068    }
1069}