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}