Skip to main content

qubit_text_codec/charset/
ascii.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use crate::charset::ascii_folding;
11
12/// Namespace for ASCII character and code point helpers.
13pub enum Ascii {}
14
15impl Ascii {
16    /// Maximum valid ASCII character.
17    pub const MAX_CHAR: char = '\u{007f}';
18
19    /// Maximum valid ASCII byte.
20    pub const MAX_BYTE: u8 = Self::MAX_CHAR as u8;
21
22    /// Maximum number of ASCII characters emitted by [`Self::fold`].
23    pub const MAX_FOLDING_COUNT: usize = 4;
24
25    /// ASCII NUL.
26    pub const NULL_CHAR: char = '\0';
27
28    /// ASCII NUL as a byte.
29    pub const NULL_BYTE: u8 = Self::NULL_CHAR as u8;
30
31    /// ASCII SOH.
32    pub const START_OF_HEADER_CHAR: char = '\u{0001}';
33
34    /// ASCII SOH as a byte.
35    pub const START_OF_HEADER_BYTE: u8 = Self::START_OF_HEADER_CHAR as u8;
36
37    /// ASCII STX.
38    pub const START_OF_TEXT_CHAR: char = '\u{0002}';
39
40    /// ASCII STX as a byte.
41    pub const START_OF_TEXT_BYTE: u8 = Self::START_OF_TEXT_CHAR as u8;
42
43    /// ASCII ETX.
44    pub const END_OF_TEXT_CHAR: char = '\u{0003}';
45
46    /// ASCII ETX as a byte.
47    pub const END_OF_TEXT_BYTE: u8 = Self::END_OF_TEXT_CHAR as u8;
48
49    /// ASCII EOT.
50    pub const START_OF_TRANSMISSION_CHAR: char = '\u{0004}';
51
52    /// ASCII EOT as a byte.
53    pub const START_OF_TRANSMISSION_BYTE: u8 = Self::START_OF_TRANSMISSION_CHAR as u8;
54
55    /// ASCII ENQ.
56    pub const ENQUIRY_CHAR: char = '\u{0005}';
57
58    /// ASCII ENQ as a byte.
59    pub const ENQUIRY_BYTE: u8 = Self::ENQUIRY_CHAR as u8;
60
61    /// ASCII ACK.
62    pub const ACKNOWLEDGMENT_CHAR: char = '\u{0006}';
63
64    /// ASCII ACK as a byte.
65    pub const ACKNOWLEDGMENT_BYTE: u8 = Self::ACKNOWLEDGMENT_CHAR as u8;
66
67    /// ASCII BEL.
68    pub const BELL_CHAR: char = '\u{0007}';
69
70    /// ASCII BEL as a byte.
71    pub const BELL_BYTE: u8 = Self::BELL_CHAR as u8;
72
73    /// ASCII BS.
74    pub const BACKSPACE_CHAR: char = '\u{0008}';
75
76    /// ASCII BS as a byte.
77    pub const BACKSPACE_BYTE: u8 = Self::BACKSPACE_CHAR as u8;
78
79    /// ASCII HT.
80    pub const HORIZONTAL_TAB_CHAR: char = '\t';
81
82    /// ASCII HT as a byte.
83    pub const HORIZONTAL_TAB_BYTE: u8 = Self::HORIZONTAL_TAB_CHAR as u8;
84
85    /// ASCII LF.
86    pub const LINE_FEED_CHAR: char = '\n';
87
88    /// ASCII LF as a byte.
89    pub const LINE_FEED_BYTE: u8 = Self::LINE_FEED_CHAR as u8;
90
91    /// ASCII VT.
92    pub const VERTICAL_TAB_CHAR: char = '\u{000b}';
93
94    /// ASCII VT as a byte.
95    pub const VERTICAL_TAB_BYTE: u8 = Self::VERTICAL_TAB_CHAR as u8;
96
97    /// ASCII FF.
98    pub const FORM_FEED_CHAR: char = '\u{000c}';
99
100    /// ASCII FF as a byte.
101    pub const FORM_FEED_BYTE: u8 = Self::FORM_FEED_CHAR as u8;
102
103    /// ASCII CR.
104    pub const CARRIAGE_RETURN_CHAR: char = '\r';
105
106    /// ASCII CR as a byte.
107    pub const CARRIAGE_RETURN_BYTE: u8 = Self::CARRIAGE_RETURN_CHAR as u8;
108
109    /// ASCII SO.
110    pub const SHIFT_OUT_CHAR: char = '\u{000e}';
111
112    /// ASCII SO as a byte.
113    pub const SHIFT_OUT_BYTE: u8 = Self::SHIFT_OUT_CHAR as u8;
114
115    /// ASCII SI.
116    pub const SHIFT_IN_CHAR: char = '\u{000f}';
117
118    /// ASCII SI as a byte.
119    pub const SHIFT_IN_BYTE: u8 = Self::SHIFT_IN_CHAR as u8;
120
121    /// ASCII DLE.
122    pub const DATA_LINK_ESCAPE_CHAR: char = '\u{0010}';
123
124    /// ASCII DLE as a byte.
125    pub const DATA_LINK_ESCAPE_BYTE: u8 = Self::DATA_LINK_ESCAPE_CHAR as u8;
126
127    /// ASCII DC1.
128    pub const DEVICE_CONTROL_1_CHAR: char = '\u{0011}';
129
130    /// ASCII DC1 as a byte.
131    pub const DEVICE_CONTROL_1_BYTE: u8 = Self::DEVICE_CONTROL_1_CHAR as u8;
132
133    /// ASCII DC2.
134    pub const DEVICE_CONTROL_2_CHAR: char = '\u{0012}';
135
136    /// ASCII DC2 as a byte.
137    pub const DEVICE_CONTROL_2_BYTE: u8 = Self::DEVICE_CONTROL_2_CHAR as u8;
138
139    /// ASCII DC3.
140    pub const DEVICE_CONTROL_3_CHAR: char = '\u{0013}';
141
142    /// ASCII DC3 as a byte.
143    pub const DEVICE_CONTROL_3_BYTE: u8 = Self::DEVICE_CONTROL_3_CHAR as u8;
144
145    /// ASCII DC4.
146    pub const DEVICE_CONTROL_4_CHAR: char = '\u{0014}';
147
148    /// ASCII DC4 as a byte.
149    pub const DEVICE_CONTROL_4_BYTE: u8 = Self::DEVICE_CONTROL_4_CHAR as u8;
150
151    /// ASCII NAK.
152    pub const NEGATIVE_ACKNOWLEDGEMENT_CHAR: char = '\u{0015}';
153
154    /// ASCII NAK as a byte.
155    pub const NEGATIVE_ACKNOWLEDGEMENT_BYTE: u8 = Self::NEGATIVE_ACKNOWLEDGEMENT_CHAR as u8;
156
157    /// ASCII SYN.
158    pub const SYNCHRONOUS_IDLE_CHAR: char = '\u{0016}';
159
160    /// ASCII SYN as a byte.
161    pub const SYNCHRONOUS_IDLE_BYTE: u8 = Self::SYNCHRONOUS_IDLE_CHAR as u8;
162
163    /// ASCII ETB.
164    pub const END_OF_TRANS_BLOCK_CHAR: char = '\u{0017}';
165
166    /// ASCII ETB as a byte.
167    pub const END_OF_TRANS_BLOCK_BYTE: u8 = Self::END_OF_TRANS_BLOCK_CHAR as u8;
168
169    /// ASCII CAN.
170    pub const CANCEL_CHAR: char = '\u{0018}';
171
172    /// ASCII CAN as a byte.
173    pub const CANCEL_BYTE: u8 = Self::CANCEL_CHAR as u8;
174
175    /// ASCII EM.
176    pub const END_OF_MEDIUM_CHAR: char = '\u{0019}';
177
178    /// ASCII EM as a byte.
179    pub const END_OF_MEDIUM_BYTE: u8 = Self::END_OF_MEDIUM_CHAR as u8;
180
181    /// ASCII SUB.
182    pub const SUBSTITUTE_CHAR: char = '\u{001a}';
183
184    /// ASCII SUB as a byte.
185    pub const SUBSTITUTE_BYTE: u8 = Self::SUBSTITUTE_CHAR as u8;
186
187    /// ASCII ESC.
188    pub const ESCAPE_CHAR: char = '\u{001b}';
189
190    /// ASCII ESC as a byte.
191    pub const ESCAPE_BYTE: u8 = Self::ESCAPE_CHAR as u8;
192
193    /// ASCII FS.
194    pub const FILE_SEPARATOR_CHAR: char = '\u{001c}';
195
196    /// ASCII FS as a byte.
197    pub const FILE_SEPARATOR_BYTE: u8 = Self::FILE_SEPARATOR_CHAR as u8;
198
199    /// ASCII GS.
200    pub const GROUP_SEPARATOR_CHAR: char = '\u{001d}';
201
202    /// ASCII GS as a byte.
203    pub const GROUP_SEPARATOR_BYTE: u8 = Self::GROUP_SEPARATOR_CHAR as u8;
204
205    /// ASCII RS.
206    pub const RECORD_SEPARATOR_CHAR: char = '\u{001e}';
207
208    /// ASCII RS as a byte.
209    pub const RECORD_SEPARATOR_BYTE: u8 = Self::RECORD_SEPARATOR_CHAR as u8;
210
211    /// ASCII US.
212    pub const UNIT_SEPARATOR_CHAR: char = '\u{001f}';
213
214    /// ASCII US as a byte.
215    pub const UNIT_SEPARATOR_BYTE: u8 = Self::UNIT_SEPARATOR_CHAR as u8;
216
217    /// ASCII DEL.
218    pub const DELETE_CHAR: char = '\u{007f}';
219
220    /// ASCII DEL as a byte.
221    pub const DELETE_BYTE: u8 = Self::DELETE_CHAR as u8;
222
223    /// ASCII space.
224    pub const SPACE_CHAR: char = ' ';
225
226    /// ASCII space as a byte.
227    pub const SPACE_BYTE: u8 = Self::SPACE_CHAR as u8;
228
229    /// ASCII exclamation mark.
230    pub const EXCLAMATION_CHAR: char = '!';
231
232    /// ASCII exclamation mark as a byte.
233    pub const EXCLAMATION_BYTE: u8 = Self::EXCLAMATION_CHAR as u8;
234
235    /// ASCII double quote.
236    pub const DOUBLE_QUOTE_CHAR: char = '"';
237
238    /// ASCII double quote as a byte.
239    pub const DOUBLE_QUOTE_BYTE: u8 = Self::DOUBLE_QUOTE_CHAR as u8;
240
241    /// ASCII number sign.
242    pub const SHARP_CHAR: char = '#';
243
244    /// ASCII number sign as a byte.
245    pub const SHARP_BYTE: u8 = Self::SHARP_CHAR as u8;
246
247    /// ASCII dollar sign.
248    pub const DOLLAR_CHAR: char = '$';
249
250    /// ASCII dollar sign as a byte.
251    pub const DOLLAR_BYTE: u8 = Self::DOLLAR_CHAR as u8;
252
253    /// ASCII percent sign.
254    pub const PERCENT_CHAR: char = '%';
255
256    /// ASCII percent sign as a byte.
257    pub const PERCENT_BYTE: u8 = Self::PERCENT_CHAR as u8;
258
259    /// ASCII ampersand.
260    pub const AMPERSAND_CHAR: char = '&';
261
262    /// ASCII ampersand as a byte.
263    pub const AMPERSAND_BYTE: u8 = Self::AMPERSAND_CHAR as u8;
264
265    /// ASCII tab.
266    pub const TAB_CHAR: char = '\t';
267
268    /// ASCII tab as a byte.
269    pub const TAB_BYTE: u8 = Self::TAB_CHAR as u8;
270
271    /// ASCII backslash.
272    pub const BACKSLASH_CHAR: char = '\\';
273
274    /// ASCII backslash as a byte.
275    pub const BACKSLASH_BYTE: u8 = Self::BACKSLASH_CHAR as u8;
276
277    /// ASCII single quote.
278    pub const SINGLE_QUOTE_CHAR: char = '\'';
279
280    /// ASCII single quote as a byte.
281    pub const SINGLE_QUOTE_BYTE: u8 = Self::SINGLE_QUOTE_CHAR as u8;
282
283    /// ASCII back quote.
284    pub const BACK_QUOTE_CHAR: char = '`';
285
286    /// ASCII back quote as a byte.
287    pub const BACK_QUOTE_BYTE: u8 = Self::BACK_QUOTE_CHAR as u8;
288
289    /// ASCII comma.
290    pub const COMMA_CHAR: char = ',';
291
292    /// ASCII comma as a byte.
293    pub const COMMA_BYTE: u8 = Self::COMMA_CHAR as u8;
294
295    /// ASCII period.
296    pub const PERIOD_CHAR: char = '.';
297
298    /// ASCII period as a byte.
299    pub const PERIOD_BYTE: u8 = Self::PERIOD_CHAR as u8;
300
301    /// Minimum printable ASCII character.
302    pub const MIN_PRINTABLE_CHAR: char = ' ';
303
304    /// Minimum printable ASCII character as a byte.
305    pub const MIN_PRINTABLE_BYTE: u8 = Self::MIN_PRINTABLE_CHAR as u8;
306
307    /// Maximum printable ASCII character.
308    pub const MAX_PRINTABLE_CHAR: char = '~';
309
310    /// Maximum printable ASCII character as a byte.
311    pub const MAX_PRINTABLE_BYTE: u8 = Self::MAX_PRINTABLE_CHAR as u8;
312
313    /// All printable ASCII characters.
314    pub const PRINTABLE_CHARS: [char; 95] = [
315        ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5',
316        '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
317        'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a',
318        'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
319        'x', 'y', 'z', '{', '|', '}', '~',
320    ];
321
322    /// All printable ASCII characters as bytes.
323    pub const PRINTABLE_BYTES: [u8; 95] =
324        *b" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
325
326    /// All ASCII letter characters.
327    pub const LETTER_CHARS: [char; 52] = [
328        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
329        'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
330        's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
331    ];
332
333    /// All ASCII letter characters as bytes.
334    pub const LETTER_BYTES: [u8; 52] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
335
336    /// All ASCII letter and digit characters.
337    pub const LETTER_DIGIT_CHARS: [char; 62] = [
338        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
339        'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
340        'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
341    ];
342
343    /// All ASCII letter and digit characters as bytes.
344    pub const LETTER_DIGIT_BYTES: [u8; 62] = *b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
345
346    /// ASCII digit characters.
347    pub const DIGIT_CHARS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
348
349    /// ASCII digit bytes.
350    pub const DIGIT_BYTES: [u8; 10] = *b"0123456789";
351
352    /// Lowercase ASCII hexadecimal digit characters.
353    pub const LOWERCASE_HEX_DIGIT_CHARS: [char; 16] = [
354        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
355    ];
356
357    /// Lowercase ASCII hexadecimal digit bytes.
358    pub const LOWERCASE_HEX_DIGIT_BYTES: [u8; 16] = *b"0123456789abcdef";
359
360    /// Uppercase ASCII hexadecimal digit characters.
361    pub const UPPERCASE_HEX_DIGIT_CHARS: [char; 16] = [
362        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
363    ];
364
365    /// Uppercase ASCII hexadecimal digit bytes.
366    pub const UPPERCASE_HEX_DIGIT_BYTES: [u8; 16] = *b"0123456789ABCDEF";
367
368    const CASE_DIFFERENCE: u32 = ('a' as u32) - ('A' as u32);
369
370    /// Tests whether a byte is an ASCII byte.
371    ///
372    /// # Parameters
373    ///
374    /// - `ch`: The byte to test.
375    ///
376    /// # Returns
377    ///
378    /// Returns `true` if `ch` is in the ASCII range `0x00..=0x7F`.
379    #[inline]
380    #[must_use]
381    pub const fn is_ascii_byte(ch: u8) -> bool {
382        ch <= Self::MAX_BYTE
383    }
384
385    /// Tests whether a character is an ASCII character.
386    ///
387    /// # Parameters
388    ///
389    /// - `ch`: The character to test.
390    ///
391    /// # Returns
392    ///
393    /// Returns `true` if `ch` is in the ASCII range `U+0000..=U+007F`.
394    #[inline]
395    #[must_use]
396    pub const fn is_ascii_char(ch: char) -> bool {
397        ch <= Self::MAX_CHAR
398    }
399
400    /// Tests whether an integer value is an ASCII code point.
401    ///
402    /// # Parameters
403    ///
404    /// - `ch`: The raw code point value to test.
405    ///
406    /// # Returns
407    ///
408    /// Returns `true` if `ch` is in the ASCII range `0x00..=0x7F`.
409    /// Negative values and values above `0x7F` return `false`.
410    #[inline]
411    #[must_use]
412    pub const fn is_ascii_code_point(ch: u32) -> bool {
413        ch <= Self::MAX_CHAR as u32
414    }
415
416    /// Tests whether a byte is Java-style ASCII whitespace.
417    ///
418    /// # Parameters
419    ///
420    /// - `ch`: The byte to test.
421    ///
422    /// # Returns
423    ///
424    /// Returns `true` for tab, line feed, form feed, carriage return, or space.
425    #[inline]
426    #[must_use]
427    pub const fn is_whitespace_byte(ch: u8) -> bool {
428        ch == b'\t' || ch == b'\n' || ch == b'\x0c' || ch == b'\r' || ch == b' '
429    }
430
431    /// Tests whether a character is Java-style ASCII whitespace.
432    ///
433    /// # Parameters
434    ///
435    /// - `ch`: The character to test.
436    ///
437    /// # Returns
438    ///
439    /// Returns `true` for tab, line feed, form feed, carriage return, or space.
440    #[inline]
441    #[must_use]
442    pub const fn is_whitespace_char(ch: char) -> bool {
443        ch == '\t' || ch == '\n' || ch == '\u{000c}' || ch == '\r' || ch == ' '
444    }
445
446    /// Tests whether a raw code point is Java-style ASCII whitespace.
447    ///
448    /// # Parameters
449    ///
450    /// - `ch`: The raw code point value to test.
451    ///
452    /// # Returns
453    ///
454    /// Returns `true` for tab, line feed, form feed, carriage return, or space.
455    /// Other values, including negative values, return `false`.
456    #[inline]
457    #[must_use]
458    pub const fn is_whitespace_code_point(ch: u32) -> bool {
459        ch == '\t' as u32 || ch == '\n' as u32 || ch == '\u{000c}' as u32 || ch == '\r' as u32 || ch == ' ' as u32
460    }
461
462    /// Tests whether a byte is an ASCII letter.
463    ///
464    /// # Parameters
465    ///
466    /// - `ch`: The byte to test.
467    ///
468    /// # Returns
469    ///
470    /// Returns `true` if `ch` is in `A..=Z` or `a..=z`.
471    #[inline]
472    #[must_use]
473    pub const fn is_letter_byte(ch: u8) -> bool {
474        Self::is_uppercase_letter_byte(ch) || Self::is_lowercase_letter_byte(ch)
475    }
476
477    /// Tests whether a character is an ASCII letter.
478    ///
479    /// # Parameters
480    ///
481    /// - `ch`: The character to test.
482    ///
483    /// # Returns
484    ///
485    /// Returns `true` if `ch` is in `A..=Z` or `a..=z`.
486    #[inline]
487    #[must_use]
488    pub const fn is_letter_char(ch: char) -> bool {
489        Self::is_uppercase_letter_char(ch) || Self::is_lowercase_letter_char(ch)
490    }
491
492    /// Tests whether a raw code point is an ASCII letter.
493    ///
494    /// # Parameters
495    ///
496    /// - `ch`: The raw code point value to test.
497    ///
498    /// # Returns
499    ///
500    /// Returns `true` if `ch` is in `A..=Z` or `a..=z`.
501    #[inline]
502    #[must_use]
503    pub const fn is_letter_code_point(ch: u32) -> bool {
504        Self::is_uppercase_letter_code_point(ch) || Self::is_lowercase_letter_code_point(ch)
505    }
506
507    /// Tests whether a byte is an uppercase ASCII letter.
508    ///
509    /// # Parameters
510    ///
511    /// - `ch`: The byte to test.
512    ///
513    /// # Returns
514    ///
515    /// Returns `true` if `ch` is in `A..=Z`.
516    #[inline]
517    #[must_use]
518    pub const fn is_uppercase_letter_byte(ch: u8) -> bool {
519        ch >= b'A' && ch <= b'Z'
520    }
521
522    /// Tests whether a character is an uppercase ASCII letter.
523    ///
524    /// # Parameters
525    ///
526    /// - `ch`: The character to test.
527    ///
528    /// # Returns
529    ///
530    /// Returns `true` if `ch` is in `A..=Z`.
531    #[inline]
532    #[must_use]
533    pub const fn is_uppercase_letter_char(ch: char) -> bool {
534        ch >= 'A' && ch <= 'Z'
535    }
536
537    /// Tests whether a raw code point is an uppercase ASCII letter.
538    ///
539    /// # Parameters
540    ///
541    /// - `ch`: The raw code point value to test.
542    ///
543    /// # Returns
544    ///
545    /// Returns `true` if `ch` is in `A..=Z`.
546    #[inline]
547    #[must_use]
548    pub const fn is_uppercase_letter_code_point(ch: u32) -> bool {
549        ch >= 'A' as u32 && ch <= 'Z' as u32
550    }
551
552    /// Tests whether a byte is a lowercase ASCII letter.
553    ///
554    /// # Parameters
555    ///
556    /// - `ch`: The byte to test.
557    ///
558    /// # Returns
559    ///
560    /// Returns `true` if `ch` is in `a..=z`.
561    #[inline]
562    #[must_use]
563    pub const fn is_lowercase_letter_byte(ch: u8) -> bool {
564        ch >= b'a' && ch <= b'z'
565    }
566
567    /// Tests whether a character is a lowercase ASCII letter.
568    ///
569    /// # Parameters
570    ///
571    /// - `ch`: The character to test.
572    ///
573    /// # Returns
574    ///
575    /// Returns `true` if `ch` is in `a..=z`.
576    #[inline]
577    #[must_use]
578    pub const fn is_lowercase_letter_char(ch: char) -> bool {
579        ch >= 'a' && ch <= 'z'
580    }
581
582    /// Tests whether a raw code point is a lowercase ASCII letter.
583    ///
584    /// # Parameters
585    ///
586    /// - `ch`: The raw code point value to test.
587    ///
588    /// # Returns
589    ///
590    /// Returns `true` if `ch` is in `a..=z`.
591    #[inline]
592    #[must_use]
593    pub const fn is_lowercase_letter_code_point(ch: u32) -> bool {
594        ch >= 'a' as u32 && ch <= 'z' as u32
595    }
596
597    /// Tests whether a byte is an ASCII decimal digit.
598    ///
599    /// # Parameters
600    ///
601    /// - `ch`: The byte to test.
602    ///
603    /// # Returns
604    ///
605    /// Returns `true` if `ch` is in `0..=9`.
606    #[inline]
607    #[must_use]
608    pub const fn is_digit_byte(ch: u8) -> bool {
609        ch >= b'0' && ch <= b'9'
610    }
611
612    /// Tests whether a character is an ASCII decimal digit.
613    ///
614    /// # Parameters
615    ///
616    /// - `ch`: The character to test.
617    ///
618    /// # Returns
619    ///
620    /// Returns `true` if `ch` is in `0..=9`.
621    #[inline]
622    #[must_use]
623    pub const fn is_digit_char(ch: char) -> bool {
624        ch >= '0' && ch <= '9'
625    }
626
627    /// Tests whether a raw code point is an ASCII decimal digit.
628    ///
629    /// # Parameters
630    ///
631    /// - `ch`: The raw code point value to test.
632    ///
633    /// # Returns
634    ///
635    /// Returns `true` if `ch` is in `0..=9`.
636    #[inline]
637    #[must_use]
638    pub const fn is_digit_code_point(ch: u32) -> bool {
639        ch >= '0' as u32 && ch <= '9' as u32
640    }
641
642    /// Tests whether a byte is an ASCII hexadecimal digit.
643    ///
644    /// # Parameters
645    ///
646    /// - `ch`: The byte to test.
647    ///
648    /// # Returns
649    ///
650    /// Returns `true` if `ch` is in `0..=9`, `A..=F`, or `a..=f`.
651    #[inline]
652    #[must_use]
653    pub const fn is_hex_digit_byte(ch: u8) -> bool {
654        Self::is_digit_byte(ch) || (ch >= b'a' && ch <= b'f') || (ch >= b'A' && ch <= b'F')
655    }
656
657    /// Tests whether a character is an ASCII hexadecimal digit.
658    ///
659    /// # Parameters
660    ///
661    /// - `ch`: The character to test.
662    ///
663    /// # Returns
664    ///
665    /// Returns `true` if `ch` is in `0..=9`, `A..=F`, or `a..=f`.
666    #[inline]
667    #[must_use]
668    pub const fn is_hex_digit_char(ch: char) -> bool {
669        Self::is_digit_char(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
670    }
671
672    /// Tests whether a raw code point is an ASCII hexadecimal digit.
673    ///
674    /// # Parameters
675    ///
676    /// - `ch`: The raw code point value to test.
677    ///
678    /// # Returns
679    ///
680    /// Returns `true` if `ch` is in `0..=9`, `A..=F`, or `a..=f`.
681    #[inline]
682    #[must_use]
683    pub const fn is_hex_digit_code_point(ch: u32) -> bool {
684        Self::is_digit_code_point(ch)
685            || (ch >= 'a' as u32 && ch <= 'f' as u32)
686            || (ch >= 'A' as u32 && ch <= 'F' as u32)
687    }
688
689    /// Tests whether a byte is an ASCII octal digit.
690    ///
691    /// # Parameters
692    ///
693    /// - `ch`: The byte to test.
694    ///
695    /// # Returns
696    ///
697    /// Returns `true` if `ch` is in `0..=7`.
698    #[inline]
699    #[must_use]
700    pub const fn is_octal_digit_byte(ch: u8) -> bool {
701        ch >= b'0' && ch <= b'7'
702    }
703
704    /// Tests whether a character is an ASCII octal digit.
705    ///
706    /// # Parameters
707    ///
708    /// - `ch`: The character to test.
709    ///
710    /// # Returns
711    ///
712    /// Returns `true` if `ch` is in `0..=7`.
713    #[inline]
714    #[must_use]
715    pub const fn is_octal_digit_char(ch: char) -> bool {
716        ch >= '0' && ch <= '7'
717    }
718
719    /// Tests whether a raw code point is an ASCII octal digit.
720    ///
721    /// # Parameters
722    ///
723    /// - `ch`: The raw code point value to test.
724    ///
725    /// # Returns
726    ///
727    /// Returns `true` if `ch` is in `0..=7`.
728    #[inline]
729    #[must_use]
730    pub const fn is_octal_digit_code_point(ch: u32) -> bool {
731        ch >= '0' as u32 && ch <= '7' as u32
732    }
733
734    /// Tests whether a byte is an ASCII letter or digit.
735    ///
736    /// # Parameters
737    ///
738    /// - `ch`: The byte to test.
739    ///
740    /// # Returns
741    ///
742    /// Returns `true` if `ch` is an ASCII letter or decimal digit.
743    #[inline]
744    #[must_use]
745    pub const fn is_letter_or_digit_byte(ch: u8) -> bool {
746        Self::is_letter_byte(ch) || Self::is_digit_byte(ch)
747    }
748
749    /// Tests whether a character is an ASCII letter or digit.
750    ///
751    /// # Parameters
752    ///
753    /// - `ch`: The character to test.
754    ///
755    /// # Returns
756    ///
757    /// Returns `true` if `ch` is an ASCII letter or decimal digit.
758    #[inline]
759    #[must_use]
760    pub const fn is_letter_or_digit_char(ch: char) -> bool {
761        Self::is_letter_char(ch) || Self::is_digit_char(ch)
762    }
763
764    /// Tests whether a raw code point is an ASCII letter or digit.
765    ///
766    /// # Parameters
767    ///
768    /// - `ch`: The raw code point value to test.
769    ///
770    /// # Returns
771    ///
772    /// Returns `true` if `ch` is an ASCII letter or decimal digit.
773    #[inline]
774    #[must_use]
775    pub const fn is_letter_or_digit_code_point(ch: u32) -> bool {
776        Self::is_letter_code_point(ch) || Self::is_digit_code_point(ch)
777    }
778
779    /// Tests whether a byte is a printable ASCII character.
780    ///
781    /// # Parameters
782    ///
783    /// - `ch`: The byte to test.
784    ///
785    /// # Returns
786    ///
787    /// Returns `true` if `ch` is in the printable ASCII range `0x20..=0x7E`.
788    #[inline]
789    #[must_use]
790    pub const fn is_printable_byte(ch: u8) -> bool {
791        ch >= Self::MIN_PRINTABLE_BYTE && ch <= Self::MAX_PRINTABLE_BYTE
792    }
793
794    /// Tests whether a character is a printable ASCII character.
795    ///
796    /// # Parameters
797    ///
798    /// - `ch`: The character to test.
799    ///
800    /// # Returns
801    ///
802    /// Returns `true` if `ch` is in the printable ASCII range `U+0020..=U+007E`.
803    #[inline]
804    #[must_use]
805    pub const fn is_printable_char(ch: char) -> bool {
806        ch >= Self::MIN_PRINTABLE_CHAR && ch <= Self::MAX_PRINTABLE_CHAR
807    }
808
809    /// Tests whether a raw code point is a printable ASCII character.
810    ///
811    /// # Parameters
812    ///
813    /// - `ch`: The raw code point value to test.
814    ///
815    /// # Returns
816    ///
817    /// Returns `true` if `ch` is in the printable ASCII range `0x20..=0x7E`.
818    #[inline]
819    #[must_use]
820    pub const fn is_printable_code_point(ch: u32) -> bool {
821        ch >= Self::MIN_PRINTABLE_CHAR as u32 && ch <= Self::MAX_PRINTABLE_CHAR as u32
822    }
823
824    /// Tests whether a byte is an ASCII control character.
825    ///
826    /// # Parameters
827    ///
828    /// - `ch`: The byte to test.
829    ///
830    /// # Returns
831    ///
832    /// Returns `true` for `0x00..=0x1F` or `0x7F`.
833    #[inline]
834    #[must_use]
835    pub const fn is_control_byte(ch: u8) -> bool {
836        ch < Self::MIN_PRINTABLE_BYTE || ch == Self::DELETE_BYTE
837    }
838
839    /// Tests whether a character is an ASCII control character.
840    ///
841    /// # Parameters
842    ///
843    /// - `ch`: The character to test.
844    ///
845    /// # Returns
846    ///
847    /// Returns `true` for `U+0000..=U+001F` or `U+007F`.
848    #[inline]
849    #[must_use]
850    pub const fn is_control_char(ch: char) -> bool {
851        (ch < Self::MIN_PRINTABLE_CHAR) || ch == Self::DELETE_CHAR
852    }
853
854    /// Tests whether a raw code point is an ASCII control character.
855    ///
856    /// # Parameters
857    ///
858    /// - `ch`: The raw code point value to test.
859    ///
860    /// # Returns
861    ///
862    /// Returns `true` for `0x00..=0x1F` or `0x7F`.
863    #[inline]
864    #[must_use]
865    pub const fn is_control_code_point(ch: u32) -> bool {
866        (ch < Self::MIN_PRINTABLE_CHAR as u32) || ch == Self::DELETE_CHAR as u32
867    }
868
869    /// Compares two bytes while ignoring ASCII case.
870    ///
871    /// # Parameters
872    ///
873    /// - `ch1`: The first byte to compare.
874    /// - `ch2`: The second byte to compare.
875    ///
876    /// # Returns
877    ///
878    /// Returns `true` if the bytes are equal after converting ASCII uppercase
879    /// letters to lowercase. Non-ASCII bytes are compared unchanged.
880    #[inline]
881    #[must_use]
882    pub const fn equals_ignore_case_byte(ch1: u8, ch2: u8) -> bool {
883        if ch1 == ch2 {
884            true
885        } else {
886            Self::byte_to_lowercase(ch1) == Self::byte_to_lowercase(ch2)
887        }
888    }
889
890    /// Compares two characters while ignoring ASCII case.
891    ///
892    /// # Parameters
893    ///
894    /// - `ch1`: The first character to compare.
895    /// - `ch2`: The second character to compare.
896    ///
897    /// # Returns
898    ///
899    /// Returns `true` if the characters are equal after converting ASCII
900    /// uppercase letters to lowercase. Non-ASCII characters are compared
901    /// unchanged.
902    #[inline]
903    #[must_use]
904    pub const fn equals_ignore_case_char(ch1: char, ch2: char) -> bool {
905        if ch1 == ch2 {
906            true
907        } else {
908            Self::char_to_lowercase(ch1) == Self::char_to_lowercase(ch2)
909        }
910    }
911
912    /// Compares two raw code points while ignoring ASCII case.
913    ///
914    /// # Parameters
915    ///
916    /// - `ch1`: The first raw code point value to compare.
917    /// - `ch2`: The second raw code point value to compare.
918    ///
919    /// # Returns
920    ///
921    /// Returns `true` if the values are equal after converting ASCII uppercase
922    /// letters to lowercase. Values outside ASCII are compared unchanged.
923    #[inline]
924    #[must_use]
925    pub const fn equals_ignore_case_code_point(ch1: u32, ch2: u32) -> bool {
926        if ch1 == ch2 {
927            true
928        } else {
929            Self::code_point_to_lowercase(ch1) == Self::code_point_to_lowercase(ch2)
930        }
931    }
932
933    /// Converts a byte to uppercase using ASCII case rules.
934    ///
935    /// # Parameters
936    ///
937    /// - `ch`: The byte to convert.
938    ///
939    /// # Returns
940    ///
941    /// Returns the uppercase ASCII equivalent for `a..=z`; all other bytes are
942    /// returned unchanged.
943    #[inline]
944    #[must_use]
945    pub const fn byte_to_uppercase(ch: u8) -> u8 {
946        if ch >= b'a' && ch <= b'z' {
947            ch - (Self::CASE_DIFFERENCE as u8)
948        } else {
949            ch
950        }
951    }
952
953    /// Converts a character to uppercase using ASCII case rules.
954    ///
955    /// # Parameters
956    ///
957    /// - `ch`: The character to convert.
958    ///
959    /// # Returns
960    ///
961    /// Returns the uppercase ASCII equivalent for `a..=z`; all other characters
962    /// are returned unchanged.
963    #[inline]
964    #[must_use]
965    pub const fn char_to_uppercase(ch: char) -> char {
966        if ch >= 'a' && ch <= 'z' {
967            ((ch as u8) - (Self::CASE_DIFFERENCE as u8)) as char
968        } else {
969            ch
970        }
971    }
972
973    /// Converts a raw code point to uppercase using ASCII case rules.
974    ///
975    /// # Parameters
976    ///
977    /// - `ch`: The raw code point value to convert.
978    ///
979    /// # Returns
980    ///
981    /// Returns the uppercase ASCII equivalent for `a..=z`; all other values are
982    /// returned unchanged.
983    #[inline]
984    #[must_use]
985    pub const fn code_point_to_uppercase(ch: u32) -> u32 {
986        if ch >= 'a' as u32 && ch <= 'z' as u32 {
987            ch - Self::CASE_DIFFERENCE
988        } else {
989            ch
990        }
991    }
992
993    /// Converts a byte to lowercase using ASCII case rules.
994    ///
995    /// # Parameters
996    ///
997    /// - `ch`: The byte to convert.
998    ///
999    /// # Returns
1000    ///
1001    /// Returns the lowercase ASCII equivalent for `A..=Z`; all other bytes are
1002    /// returned unchanged.
1003    #[inline]
1004    #[must_use]
1005    pub const fn byte_to_lowercase(ch: u8) -> u8 {
1006        if ch >= b'A' && ch <= b'Z' {
1007            ch + (Self::CASE_DIFFERENCE as u8)
1008        } else {
1009            ch
1010        }
1011    }
1012
1013    /// Converts a character to lowercase using ASCII case rules.
1014    ///
1015    /// # Parameters
1016    ///
1017    /// - `ch`: The character to convert.
1018    ///
1019    /// # Returns
1020    ///
1021    /// Returns the lowercase ASCII equivalent for `A..=Z`; all other characters
1022    /// are returned unchanged.
1023    #[inline]
1024    #[must_use]
1025    pub const fn char_to_lowercase(ch: char) -> char {
1026        if ch >= 'A' && ch <= 'Z' {
1027            ((ch as u8) + (Self::CASE_DIFFERENCE as u8)) as char
1028        } else {
1029            ch
1030        }
1031    }
1032
1033    /// Converts a raw code point to lowercase using ASCII case rules.
1034    ///
1035    /// # Parameters
1036    ///
1037    /// - `ch`: The raw code point value to convert.
1038    ///
1039    /// # Returns
1040    ///
1041    /// Returns the lowercase ASCII equivalent for `A..=Z`; all other values are
1042    /// returned unchanged.
1043    #[inline]
1044    #[must_use]
1045    pub const fn code_point_to_lowercase(ch: u32) -> u32 {
1046        if ch >= 'A' as u32 && ch <= 'Z' as u32 {
1047            ch + Self::CASE_DIFFERENCE
1048        } else {
1049            ch
1050        }
1051    }
1052
1053    /// Converts an ASCII decimal digit byte into its numeric value.
1054    ///
1055    /// # Parameters
1056    ///
1057    /// - `ch`: The byte to convert.
1058    ///
1059    /// # Returns
1060    ///
1061    /// Returns `Some(0..=9)` for `0..=9`; returns `None` otherwise.
1062    #[inline]
1063    #[must_use]
1064    pub const fn byte_to_digit(ch: u8) -> Option<u8> {
1065        if Self::is_digit_byte(ch) { Some(ch - b'0') } else { None }
1066    }
1067
1068    /// Converts an ASCII decimal digit character into its numeric value.
1069    ///
1070    /// # Parameters
1071    ///
1072    /// - `ch`: The character to convert.
1073    ///
1074    /// # Returns
1075    ///
1076    /// Returns `Some(0..=9)` for `0..=9`; returns `None` otherwise.
1077    #[inline]
1078    #[must_use]
1079    pub const fn char_to_digit(ch: char) -> Option<u8> {
1080        if Self::is_digit_char(ch) {
1081            Some((ch as u8) - b'0')
1082        } else {
1083            None
1084        }
1085    }
1086
1087    /// Converts an ASCII decimal digit code point into its numeric value.
1088    ///
1089    /// # Parameters
1090    ///
1091    /// - `ch`: The raw code point value to convert.
1092    ///
1093    /// # Returns
1094    ///
1095    /// Returns `Some(0..=9)` for `0..=9`; returns `None` otherwise.
1096    #[inline]
1097    #[must_use]
1098    pub const fn code_point_to_digit(ch: u32) -> Option<u8> {
1099        if Self::is_digit_code_point(ch) {
1100            Some((ch - '0' as u32) as u8)
1101        } else {
1102            None
1103        }
1104    }
1105
1106    /// Converts an ASCII hexadecimal digit byte into its numeric value.
1107    ///
1108    /// # Parameters
1109    ///
1110    /// - `ch`: The byte to convert.
1111    ///
1112    /// # Returns
1113    ///
1114    /// Returns `Some(0..=15)` for `0..=9`, `A..=F`, or `a..=f`; returns `None`
1115    /// otherwise.
1116    #[inline]
1117    #[must_use]
1118    pub const fn byte_to_hex_digit(ch: u8) -> Option<u8> {
1119        if ch >= b'0' && ch <= b'9' {
1120            Some(ch - b'0')
1121        } else if ch >= b'A' && ch <= b'F' {
1122            Some(ch - (b'A' - 10))
1123        } else if ch >= b'a' && ch <= b'f' {
1124            Some(ch - (b'a' - 10))
1125        } else {
1126            None
1127        }
1128    }
1129
1130    /// Converts an ASCII hexadecimal digit character into its numeric value.
1131    ///
1132    /// # Parameters
1133    ///
1134    /// - `ch`: The character to convert.
1135    ///
1136    /// # Returns
1137    ///
1138    /// Returns `Some(0..=15)` for `0..=9`, `A..=F`, or `a..=f`; returns `None`
1139    /// otherwise.
1140    #[inline]
1141    #[must_use]
1142    pub const fn char_to_hex_digit(ch: char) -> Option<u8> {
1143        if ch >= '0' && ch <= '9' {
1144            Some((ch as u8) - b'0')
1145        } else if ch >= 'A' && ch <= 'F' {
1146            Some((ch as u8) - (b'A' - 10))
1147        } else if ch >= 'a' && ch <= 'f' {
1148            Some((ch as u8) - (b'a' - 10))
1149        } else {
1150            None
1151        }
1152    }
1153
1154    /// Converts an ASCII hexadecimal digit code point into its numeric value.
1155    ///
1156    /// # Parameters
1157    ///
1158    /// - `ch`: The raw code point value to convert.
1159    ///
1160    /// # Returns
1161    ///
1162    /// Returns `Some(0..=15)` for `0..=9`, `A..=F`, or `a..=f`; returns `None`
1163    /// otherwise.
1164    #[inline]
1165    #[must_use]
1166    pub const fn code_point_to_hex_digit(ch: u32) -> Option<u8> {
1167        if ch >= '0' as u32 && ch <= '9' as u32 {
1168            Some((ch - '0' as u32) as u8)
1169        } else if ch >= 'A' as u32 && ch <= 'F' as u32 {
1170            Some((ch - ('A' as u32 - 10)) as u8)
1171        } else if ch >= 'a' as u32 && ch <= 'f' as u32 {
1172            Some((ch - ('a' as u32 - 10)) as u8)
1173        } else {
1174            None
1175        }
1176    }
1177
1178    /// Folds a Unicode character to its ASCII replacement.
1179    ///
1180    /// # Parameters
1181    ///
1182    /// - `ch`: The character to fold.
1183    /// - `result`: The caller-provided output buffer that receives the folded
1184    ///   characters.
1185    /// - `offset`: The starting index in `result` at which folded characters are
1186    ///   written.
1187    ///
1188    /// # Returns
1189    ///
1190    /// Returns the number of characters written to `result` starting at
1191    /// `offset`. ASCII characters and unmapped non-ASCII characters write one
1192    /// character. Mapped characters write up to [`Self::MAX_FOLDING_COUNT`] ASCII
1193    /// characters.
1194    ///
1195    /// # Panics
1196    ///
1197    /// Panics if `result` has fewer than [`Self::MAX_FOLDING_COUNT`] writable slots
1198    /// after `offset`.
1199    #[inline]
1200    pub fn fold(ch: char, result: &mut [char], offset: usize) -> usize {
1201        assert!(
1202            result.len().saturating_sub(offset) >= Self::MAX_FOLDING_COUNT,
1203            "ASCII folding output needs at least MAX_FOLDING_COUNT slots"
1204        );
1205        if ch.is_ascii() {
1206            result[offset] = ch;
1207            return 1;
1208        }
1209        match ascii_folding::fold_replacement(ch) {
1210            Some(replacement) => {
1211                for (index, replacement_char) in replacement.chars().enumerate() {
1212                    result[offset + index] = replacement_char;
1213                }
1214                replacement.len()
1215            }
1216            None => {
1217                result[offset] = ch;
1218                1
1219            }
1220        }
1221    }
1222
1223    /// Folds a Unicode character into an owned string.
1224    ///
1225    /// # Parameters
1226    ///
1227    /// - `ch`: The character to fold.
1228    ///
1229    /// # Returns
1230    ///
1231    /// Returns the folded ASCII replacement as a `String`. If `ch` has no
1232    /// folding mapping, the returned string contains `ch` unchanged.
1233    #[inline]
1234    #[must_use]
1235    pub fn fold_to_string(ch: char) -> String {
1236        let mut buffer = ['\0'; Self::MAX_FOLDING_COUNT];
1237        let count = Self::fold(ch, &mut buffer, 0);
1238        buffer[..count].iter().collect()
1239    }
1240}