Skip to main content

java_string/
char.rs

1use std::char::ParseCharError;
2use std::cmp::Ordering;
3use std::fmt;
4use std::fmt::{Debug, Display, Formatter, Write};
5use std::hash::{Hash, Hasher};
6use std::iter::{FusedIterator, Once, once};
7use std::ops::Range;
8use std::str::FromStr;
9
10use crate::validations::{TAG_CONT, TAG_FOUR_B, TAG_THREE_B, TAG_TWO_B};
11
12// JavaCodePoint is guaranteed to have the same repr as a u32, with valid values
13// of between 0 and 0x10FFFF, the same as a unicode code point. Surrogate code
14// points are valid values of this type.
15#[derive(Copy, Clone, PartialEq, Eq)]
16#[repr(C)]
17pub struct JavaCodePoint {
18    #[cfg(target_endian = "little")]
19    lower: u16,
20    upper: SeventeenValues,
21    #[cfg(target_endian = "big")]
22    lower: u16,
23}
24
25#[repr(u16)]
26#[derive(Copy, Clone, PartialEq, Eq)]
27#[allow(unused)]
28enum SeventeenValues {
29    V0,
30    V1,
31    V2,
32    V3,
33    V4,
34    V5,
35    V6,
36    V7,
37    V8,
38    V9,
39    V10,
40    V11,
41    V12,
42    V13,
43    V14,
44    V15,
45    V16,
46}
47
48impl JavaCodePoint {
49    pub const MAX: JavaCodePoint = JavaCodePoint::from_char(char::MAX);
50    pub const REPLACEMENT_CHARACTER: JavaCodePoint =
51        JavaCodePoint::from_char(char::REPLACEMENT_CHARACTER);
52
53    /// See [`char::from_u32`]
54    ///
55    /// ```
56    /// # use java_string::JavaCodePoint;
57    /// let c = JavaCodePoint::from_u32(0x2764);
58    /// assert_eq!(Some(JavaCodePoint::from_char('❤')), c);
59    ///
60    /// assert_eq!(None, JavaCodePoint::from_u32(0x110000));
61    /// ```
62    #[inline]
63    #[must_use]
64    pub const fn from_u32(i: u32) -> Option<JavaCodePoint> {
65        if i <= 0x10ffff {
66            unsafe { Some(Self::from_u32_unchecked(i)) }
67        } else {
68            None
69        }
70    }
71
72    /// # Safety
73    /// The argument must be within the valid Unicode code point range of 0 to
74    /// 0x10FFFF inclusive. Surrogate code points are allowed.
75    #[inline]
76    #[must_use]
77    pub const unsafe fn from_u32_unchecked(i: u32) -> JavaCodePoint {
78        unsafe {
79            // SAFETY: the caller checks that the argument can be represented by this type
80            std::mem::transmute(i)
81        }
82    }
83
84    /// Converts a `char` to a code point.
85    #[inline]
86    #[must_use]
87    pub const fn from_char(char: char) -> JavaCodePoint {
88        unsafe {
89            // SAFETY: all chars are valid code points
90            JavaCodePoint::from_u32_unchecked(char as u32)
91        }
92    }
93
94    /// Converts this code point to a `u32`.
95    ///
96    /// ```
97    /// # use java_string::JavaCodePoint;
98    /// assert_eq!(65, JavaCodePoint::from_char('A').as_u32());
99    /// assert_eq!(0xd800, JavaCodePoint::from_u32(0xd800).unwrap().as_u32());
100    /// ```
101    #[inline]
102    #[must_use]
103    pub const fn as_u32(self) -> u32 {
104        unsafe {
105            // SAFETY: JavaCodePoint has the same repr as a u32
106            let result = std::mem::transmute::<Self, u32>(self);
107
108            if result > 0x10ffff {
109                // SAFETY: JavaCodePoint can never have a value > 0x10FFFF.
110                // This statement may allow the optimizer to remove branches in the calling code
111                // associated with out of bounds chars.
112                std::hint::unreachable_unchecked();
113            }
114
115            result
116        }
117    }
118
119    /// Converts this code point to a `char`.
120    ///
121    /// ```
122    /// # use java_string::JavaCodePoint;
123    /// assert_eq!(Some('a'), JavaCodePoint::from_char('a').as_char());
124    /// assert_eq!(None, JavaCodePoint::from_u32(0xd800).unwrap().as_char());
125    /// ```
126    #[inline]
127    #[must_use]
128    pub const fn as_char(self) -> Option<char> {
129        char::from_u32(self.as_u32())
130    }
131
132    /// # Safety
133    /// The caller must ensure that this code point is not a surrogate code
134    /// point.
135    #[inline]
136    #[must_use]
137    pub unsafe fn as_char_unchecked(self) -> char {
138        unsafe {
139            // SAFETY: the caller checks that this code point is not a surrogate code point.
140            char::from_u32_unchecked(self.as_u32())
141        }
142    }
143
144    /// See [`char::encode_utf16`]
145    ///
146    /// ```
147    /// # use java_string::JavaCodePoint;
148    /// assert_eq!(
149    ///     2,
150    ///     JavaCodePoint::from_char('𝕊')
151    ///         .encode_utf16(&mut [0; 2])
152    ///         .len()
153    /// );
154    /// assert_eq!(
155    ///     1,
156    ///     JavaCodePoint::from_u32(0xd800)
157    ///         .unwrap()
158    ///         .encode_utf16(&mut [0; 2])
159    ///         .len()
160    /// );
161    /// ```
162    /// ```should_panic
163    /// # use java_string::JavaCodePoint;
164    /// // Should panic
165    /// JavaCodePoint::from_char('𝕊').encode_utf16(&mut [0; 1]);
166    /// ```
167    #[inline]
168    pub fn encode_utf16(self, dst: &mut [u16]) -> &mut [u16] {
169        if let Some(char) = self.as_char() {
170            char.encode_utf16(dst)
171        } else {
172            dst[0] = self.as_u32() as u16;
173            &mut dst[..1]
174        }
175    }
176
177    /// Encodes this `JavaCodePoint` into semi UTF-8, that is, UTF-8 with
178    /// surrogate code points. See also [`char::encode_utf8`].
179    ///
180    /// ```
181    /// # use java_string::JavaCodePoint;
182    /// assert_eq!(
183    ///     2,
184    ///     JavaCodePoint::from_char('ß')
185    ///         .encode_semi_utf8(&mut [0; 4])
186    ///         .len()
187    /// );
188    /// assert_eq!(
189    ///     3,
190    ///     JavaCodePoint::from_u32(0xd800)
191    ///         .unwrap()
192    ///         .encode_semi_utf8(&mut [0; 4])
193    ///         .len()
194    /// );
195    /// ```
196    /// ```should_panic
197    /// # use java_string::JavaCodePoint;
198    /// // Should panic
199    /// JavaCodePoint::from_char('ß').encode_semi_utf8(&mut [0; 1]);
200    /// ```
201    #[inline]
202    pub fn encode_semi_utf8(self, dst: &mut [u8]) -> &mut [u8] {
203        let len = self.len_utf8();
204        let code = self.as_u32();
205        match (len, &mut dst[..]) {
206            (1, [a, ..]) => {
207                *a = code as u8;
208            }
209            (2, [a, b, ..]) => {
210                *a = ((code >> 6) & 0x1f) as u8 | TAG_TWO_B;
211                *b = (code & 0x3f) as u8 | TAG_CONT;
212            }
213            (3, [a, b, c, ..]) => {
214                *a = ((code >> 12) & 0x0f) as u8 | TAG_THREE_B;
215                *b = ((code >> 6) & 0x3f) as u8 | TAG_CONT;
216                *c = (code & 0x3f) as u8 | TAG_CONT;
217            }
218            (4, [a, b, c, d, ..]) => {
219                *a = ((code >> 18) & 0x07) as u8 | TAG_FOUR_B;
220                *b = ((code >> 12) & 0x3f) as u8 | TAG_CONT;
221                *c = ((code >> 6) & 0x3f) as u8 | TAG_CONT;
222                *d = (code & 0x3f) as u8 | TAG_CONT;
223            }
224            _ => panic!(
225                "encode_utf8: need {} bytes to encode U+{:X}, but the buffer has {}",
226                len,
227                code,
228                dst.len()
229            ),
230        }
231        &mut dst[..len]
232    }
233
234    /// See [`char::eq_ignore_ascii_case`].
235    #[inline]
236    pub fn eq_ignore_ascii_case(&self, other: &JavaCodePoint) -> bool {
237        match (self.as_char(), other.as_char()) {
238            (Some(char1), Some(char2)) => char1.eq_ignore_ascii_case(&char2),
239            (None, None) => self == other,
240            _ => false,
241        }
242    }
243
244    /// See [`char::escape_debug`].
245    ///
246    /// ```
247    /// # use java_string::JavaCodePoint;
248    /// assert_eq!(
249    ///     "a",
250    ///     JavaCodePoint::from_char('a').escape_debug().to_string()
251    /// );
252    /// assert_eq!(
253    ///     "\\n",
254    ///     JavaCodePoint::from_char('\n').escape_debug().to_string()
255    /// );
256    /// assert_eq!(
257    ///     "\\u{d800}",
258    ///     JavaCodePoint::from_u32(0xd800)
259    ///         .unwrap()
260    ///         .escape_debug()
261    ///         .to_string()
262    /// );
263    /// ```
264    #[inline]
265    #[must_use]
266    pub fn escape_debug(self) -> CharEscapeIter {
267        self.escape_debug_ext(EscapeDebugExtArgs::ESCAPE_ALL)
268    }
269
270    #[inline]
271    #[must_use]
272    pub(crate) fn escape_debug_ext(self, args: EscapeDebugExtArgs) -> CharEscapeIter {
273        const NULL: u32 = '\0' as u32;
274        const TAB: u32 = '\t' as u32;
275        const CARRIAGE_RETURN: u32 = '\r' as u32;
276        const LINE_FEED: u32 = '\n' as u32;
277        const SINGLE_QUOTE: u32 = '\'' as u32;
278        const DOUBLE_QUOTE: u32 = '"' as u32;
279        const BACKSLASH: u32 = '\\' as u32;
280
281        unsafe {
282            // SAFETY: all characters specified are in ascii range
283            match self.as_u32() {
284                NULL => CharEscapeIter::new(*b"\\0"),
285                TAB => CharEscapeIter::new(*b"\\t"),
286                CARRIAGE_RETURN => CharEscapeIter::new(*b"\\r"),
287                LINE_FEED => CharEscapeIter::new(*b"\\n"),
288                SINGLE_QUOTE if args.escape_single_quote => CharEscapeIter::new(*b"\\'"),
289                DOUBLE_QUOTE if args.escape_double_quote => CharEscapeIter::new(*b"\\\""),
290                BACKSLASH => CharEscapeIter::new(*b"\\\\"),
291                _ if self.is_printable() => {
292                    // SAFETY: surrogate code points are not printable
293                    CharEscapeIter::printable(self.as_char_unchecked())
294                }
295                _ => self.escape_unicode(),
296            }
297        }
298    }
299
300    #[inline]
301    fn is_printable(self) -> bool {
302        let Some(char) = self.as_char() else {
303            return false;
304        };
305        if matches!(char, '\\' | '\'' | '"') {
306            return true;
307        }
308        char.escape_debug().next() != Some('\\')
309    }
310
311    /// See [`char::escape_default`].
312    ///
313    /// ```
314    /// # use java_string::JavaCodePoint;
315    /// assert_eq!(
316    ///     "a",
317    ///     JavaCodePoint::from_char('a').escape_default().to_string()
318    /// );
319    /// assert_eq!(
320    ///     "\\n",
321    ///     JavaCodePoint::from_char('\n').escape_default().to_string()
322    /// );
323    /// assert_eq!(
324    ///     "\\u{d800}",
325    ///     JavaCodePoint::from_u32(0xd800)
326    ///         .unwrap()
327    ///         .escape_default()
328    ///         .to_string()
329    /// );
330    /// ```
331    #[inline]
332    #[must_use]
333    pub fn escape_default(self) -> CharEscapeIter {
334        const TAB: u32 = '\t' as u32;
335        const CARRIAGE_RETURN: u32 = '\r' as u32;
336        const LINE_FEED: u32 = '\n' as u32;
337        const SINGLE_QUOTE: u32 = '\'' as u32;
338        const DOUBLE_QUOTE: u32 = '"' as u32;
339        const BACKSLASH: u32 = '\\' as u32;
340
341        unsafe {
342            // SAFETY: all characters specified are in ascii range
343            match self.as_u32() {
344                TAB => CharEscapeIter::new(*b"\\t"),
345                CARRIAGE_RETURN => CharEscapeIter::new(*b"\\r"),
346                LINE_FEED => CharEscapeIter::new(*b"\\n"),
347                SINGLE_QUOTE => CharEscapeIter::new(*b"\\'"),
348                DOUBLE_QUOTE => CharEscapeIter::new(*b"\\\""),
349                BACKSLASH => CharEscapeIter::new(*b"\\\\"),
350                0x20..=0x7e => CharEscapeIter::new([self.as_u32() as u8]),
351                _ => self.escape_unicode(),
352            }
353        }
354    }
355
356    /// See [`char::escape_unicode`].
357    ///
358    /// ```
359    /// # use java_string::JavaCodePoint;
360    /// assert_eq!(
361    ///     "\\u{2764}",
362    ///     JavaCodePoint::from_char('❤').escape_unicode().to_string()
363    /// );
364    /// assert_eq!(
365    ///     "\\u{d800}",
366    ///     JavaCodePoint::from_u32(0xd800)
367    ///         .unwrap()
368    ///         .escape_unicode()
369    ///         .to_string()
370    /// );
371    /// ```
372    #[inline]
373    #[must_use]
374    pub fn escape_unicode(self) -> CharEscapeIter {
375        let x = self.as_u32();
376
377        let mut arr = [0; 10];
378        arr[0] = b'\\';
379        arr[1] = b'u';
380        arr[2] = b'{';
381
382        let number_len = if x == 0 {
383            1
384        } else {
385            ((x.ilog2() >> 2) + 1) as usize
386        };
387        arr[3 + number_len] = b'}';
388        for hexit in 0..number_len {
389            arr[2 + number_len - hexit] = b"0123456789abcdef"[((x >> (hexit << 2)) & 15) as usize];
390        }
391
392        CharEscapeIter {
393            inner: EscapeIterInner::Escaped(EscapeIterEscaped {
394                bytes: arr,
395                range: 0..number_len + 4,
396            }),
397        }
398    }
399
400    /// See [`char::is_alphabetic`].
401    #[inline]
402    #[must_use]
403    pub fn is_alphabetic(self) -> bool {
404        self.as_char().is_some_and(|char| char.is_alphabetic())
405    }
406
407    /// See [`char::is_alphanumeric`].
408    #[inline]
409    #[must_use]
410    pub fn is_alphanumeric(self) -> bool {
411        self.as_char().is_some_and(|char| char.is_alphanumeric())
412    }
413
414    /// See [`char::is_ascii`].
415    #[inline]
416    #[must_use]
417    pub fn is_ascii(self) -> bool {
418        self.as_u32() <= 0x7f
419    }
420
421    /// See [`char::is_ascii_alphabetic`].
422    #[inline]
423    #[must_use]
424    pub const fn is_ascii_alphabetic(self) -> bool {
425        self.is_ascii_lowercase() || self.is_ascii_uppercase()
426    }
427
428    /// See [`char::is_ascii_alphanumeric`].
429    #[inline]
430    #[must_use]
431    pub const fn is_ascii_alphanumeric(self) -> bool {
432        self.is_ascii_alphabetic() || self.is_ascii_digit()
433    }
434
435    /// See [`char::is_ascii_control`].
436    #[inline]
437    #[must_use]
438    pub const fn is_ascii_control(self) -> bool {
439        matches!(self.as_u32(), 0..=0x1f | 0x7f)
440    }
441
442    /// See [`char::is_ascii_digit`].
443    #[inline]
444    #[must_use]
445    pub const fn is_ascii_digit(self) -> bool {
446        const ZERO: u32 = '0' as u32;
447        const NINE: u32 = '9' as u32;
448        matches!(self.as_u32(), ZERO..=NINE)
449    }
450
451    /// See [`char::is_ascii_graphic`].
452    #[inline]
453    #[must_use]
454    pub const fn is_ascii_graphic(self) -> bool {
455        matches!(self.as_u32(), 0x21..=0x7e)
456    }
457
458    /// See [`char::is_ascii_hexdigit`].
459    #[inline]
460    #[must_use]
461    pub const fn is_ascii_hexdigit(self) -> bool {
462        const LOWER_A: u32 = 'a' as u32;
463        const LOWER_F: u32 = 'f' as u32;
464        const UPPER_A: u32 = 'A' as u32;
465        const UPPER_F: u32 = 'F' as u32;
466        self.is_ascii_digit() || matches!(self.as_u32(), (LOWER_A..=LOWER_F) | (UPPER_A..=UPPER_F))
467    }
468
469    /// See [`char::is_ascii_lowercase`].
470    #[inline]
471    #[must_use]
472    pub const fn is_ascii_lowercase(self) -> bool {
473        const A: u32 = 'a' as u32;
474        const Z: u32 = 'z' as u32;
475        matches!(self.as_u32(), A..=Z)
476    }
477
478    /// See [`char::is_ascii_octdigit`].
479    #[inline]
480    #[must_use]
481    pub const fn is_ascii_octdigit(self) -> bool {
482        const ZERO: u32 = '0' as u32;
483        const SEVEN: u32 = '7' as u32;
484        matches!(self.as_u32(), ZERO..=SEVEN)
485    }
486
487    /// See [`char::is_ascii_punctuation`].
488    #[inline]
489    #[must_use]
490    pub const fn is_ascii_punctuation(self) -> bool {
491        matches!(
492            self.as_u32(),
493            (0x21..=0x2f) | (0x3a..=0x40) | (0x5b..=0x60) | (0x7b..=0x7e)
494        )
495    }
496
497    /// See [`char::is_ascii_uppercase`].
498    #[inline]
499    #[must_use]
500    pub const fn is_ascii_uppercase(self) -> bool {
501        const A: u32 = 'A' as u32;
502        const Z: u32 = 'Z' as u32;
503        matches!(self.as_u32(), A..=Z)
504    }
505
506    /// See [`char::is_ascii_whitespace`].
507    #[inline]
508    #[must_use]
509    pub const fn is_ascii_whitespace(self) -> bool {
510        const SPACE: u32 = ' ' as u32;
511        const HORIZONTAL_TAB: u32 = '\t' as u32;
512        const LINE_FEED: u32 = '\n' as u32;
513        const FORM_FEED: u32 = 0xc;
514        const CARRIAGE_RETURN: u32 = '\r' as u32;
515        matches!(
516            self.as_u32(),
517            SPACE | HORIZONTAL_TAB | LINE_FEED | FORM_FEED | CARRIAGE_RETURN
518        )
519    }
520
521    /// See [`char::is_control`].
522    #[inline]
523    #[must_use]
524    pub fn is_control(self) -> bool {
525        self.as_char().is_some_and(|char| char.is_control())
526    }
527
528    /// See [`char::is_digit`].
529    #[inline]
530    #[must_use]
531    pub fn is_digit(self, radix: u32) -> bool {
532        self.to_digit(radix).is_some()
533    }
534
535    /// See [`char::is_lowercase`].
536    #[inline]
537    #[must_use]
538    pub fn is_lowercase(self) -> bool {
539        self.as_char().is_some_and(|char| char.is_lowercase())
540    }
541
542    /// See [`char::is_numeric`].
543    #[inline]
544    #[must_use]
545    pub fn is_numeric(self) -> bool {
546        self.as_char().is_some_and(|char| char.is_numeric())
547    }
548
549    /// See [`char::is_uppercase`].
550    #[inline]
551    #[must_use]
552    pub fn is_uppercase(self) -> bool {
553        self.as_char().is_some_and(|char| char.is_uppercase())
554    }
555
556    /// See [`char::is_whitespace`].
557    #[inline]
558    #[must_use]
559    pub fn is_whitespace(self) -> bool {
560        self.as_char().is_some_and(|char| char.is_whitespace())
561    }
562
563    /// See [`char::len_utf16`]. Surrogate code points return 1.
564    ///
565    /// ```
566    /// # use java_string::JavaCodePoint;
567    ///
568    /// let n = JavaCodePoint::from_char('ß').len_utf16();
569    /// assert_eq!(n, 1);
570    ///
571    /// let len = JavaCodePoint::from_char('💣').len_utf16();
572    /// assert_eq!(len, 2);
573    ///
574    /// assert_eq!(1, JavaCodePoint::from_u32(0xd800).unwrap().len_utf16());
575    /// ```
576    #[inline]
577    #[must_use]
578    pub const fn len_utf16(self) -> usize {
579        if let Some(char) = self.as_char() {
580            char.len_utf16()
581        } else {
582            1 // invalid code points are encoded as 1 utf16 code point anyway
583        }
584    }
585
586    /// See [`char::len_utf8`]. Surrogate code points return 3.
587    ///
588    /// ```
589    /// # use java_string::JavaCodePoint;
590    ///
591    /// let len = JavaCodePoint::from_char('A').len_utf8();
592    /// assert_eq!(len, 1);
593    ///
594    /// let len = JavaCodePoint::from_char('ß').len_utf8();
595    /// assert_eq!(len, 2);
596    ///
597    /// let len = JavaCodePoint::from_char('ℝ').len_utf8();
598    /// assert_eq!(len, 3);
599    ///
600    /// let len = JavaCodePoint::from_char('💣').len_utf8();
601    /// assert_eq!(len, 4);
602    ///
603    /// let len = JavaCodePoint::from_u32(0xd800).unwrap().len_utf8();
604    /// assert_eq!(len, 3);
605    /// ```
606    #[inline]
607    #[must_use]
608    pub const fn len_utf8(self) -> usize {
609        if let Some(char) = self.as_char() {
610            char.len_utf8()
611        } else {
612            3 // invalid code points are all length 3 in semi-valid utf8
613        }
614    }
615
616    /// See [`char::make_ascii_lowercase`].
617    #[inline]
618    pub fn make_ascii_lowercase(&mut self) {
619        *self = self.to_ascii_lowercase();
620    }
621
622    /// See [`char::make_ascii_uppercase`].
623    #[inline]
624    pub fn make_ascii_uppercase(&mut self) {
625        *self = self.to_ascii_uppercase();
626    }
627
628    /// See [`char::to_ascii_lowercase`].
629    ///
630    /// ```
631    /// # use java_string::JavaCodePoint;
632    ///
633    /// let ascii = JavaCodePoint::from_char('A');
634    /// let non_ascii = JavaCodePoint::from_char('❤');
635    ///
636    /// assert_eq!('a', ascii.to_ascii_lowercase());
637    /// assert_eq!('❤', non_ascii.to_ascii_lowercase());
638    /// ```
639    #[inline]
640    #[must_use]
641    pub const fn to_ascii_lowercase(self) -> JavaCodePoint {
642        if self.is_ascii_uppercase() {
643            unsafe {
644                // SAFETY: all lowercase chars are valid chars
645                Self::from_u32_unchecked(self.as_u32() + 32)
646            }
647        } else {
648            self
649        }
650    }
651
652    /// See [`char::to_ascii_uppercase`].
653    ///
654    /// ```
655    /// # use java_string::JavaCodePoint;
656    ///
657    /// let ascii = JavaCodePoint::from_char('a');
658    /// let non_ascii = JavaCodePoint::from_char('❤');
659    ///
660    /// assert_eq!('A', ascii.to_ascii_uppercase());
661    /// assert_eq!('❤', non_ascii.to_ascii_uppercase());
662    /// ```
663    #[inline]
664    #[must_use]
665    pub const fn to_ascii_uppercase(self) -> JavaCodePoint {
666        if self.is_ascii_lowercase() {
667            unsafe {
668                // SAFETY: all uppercase chars are valid chars
669                Self::from_u32_unchecked(self.as_u32() - 32)
670            }
671        } else {
672            self
673        }
674    }
675
676    /// See [`char::to_digit`].
677    #[inline]
678    #[must_use]
679    pub const fn to_digit(self, radix: u32) -> Option<u32> {
680        if let Some(char) = self.as_char() {
681            char.to_digit(radix)
682        } else {
683            None
684        }
685    }
686
687    /// See [`char::to_lowercase`].
688    #[inline]
689    #[must_use]
690    pub fn to_lowercase(self) -> ToLowercase {
691        match self.as_char() {
692            Some(char) => ToLowercase::char(char.to_lowercase()),
693            None => ToLowercase::invalid(self),
694        }
695    }
696
697    /// See [`char::to_uppercase`].
698    #[inline]
699    #[must_use]
700    pub fn to_uppercase(self) -> ToUppercase {
701        match self.as_char() {
702            Some(char) => ToUppercase::char(char.to_uppercase()),
703            None => ToUppercase::invalid(self),
704        }
705    }
706}
707
708impl Debug for JavaCodePoint {
709    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
710        f.write_char('\'')?;
711        for c in self.escape_debug_ext(EscapeDebugExtArgs {
712            escape_single_quote: true,
713            escape_double_quote: false,
714        }) {
715            f.write_char(c)?;
716        }
717        f.write_char('\'')
718    }
719}
720
721impl Default for JavaCodePoint {
722    #[inline]
723    fn default() -> Self {
724        JavaCodePoint::from_char('\0')
725    }
726}
727
728impl Display for JavaCodePoint {
729    #[inline]
730    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
731        Display::fmt(&self.as_char().unwrap_or(char::REPLACEMENT_CHARACTER), f)
732    }
733}
734
735impl From<JavaCodePoint> for u32 {
736    #[inline]
737    fn from(value: JavaCodePoint) -> Self {
738        value.as_u32()
739    }
740}
741
742impl From<u8> for JavaCodePoint {
743    #[inline]
744    fn from(value: u8) -> Self {
745        JavaCodePoint::from_char(char::from(value))
746    }
747}
748
749impl FromStr for JavaCodePoint {
750    type Err = ParseCharError;
751
752    #[inline]
753    fn from_str(s: &str) -> Result<Self, Self::Err> {
754        char::from_str(s).map(JavaCodePoint::from_char)
755    }
756}
757
758impl Hash for JavaCodePoint {
759    #[inline]
760    fn hash<H: Hasher>(&self, state: &mut H) {
761        self.as_u32().hash(state)
762    }
763}
764
765impl Ord for JavaCodePoint {
766    #[inline]
767    fn cmp(&self, other: &Self) -> Ordering {
768        self.as_u32().cmp(&other.as_u32())
769    }
770}
771
772impl PartialOrd for JavaCodePoint {
773    #[inline]
774    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
775        Some(self.cmp(other))
776    }
777}
778
779impl PartialOrd<char> for JavaCodePoint {
780    #[inline]
781    fn partial_cmp(&self, other: &char) -> Option<Ordering> {
782        self.partial_cmp(&JavaCodePoint::from_char(*other))
783    }
784}
785
786impl PartialOrd<JavaCodePoint> for char {
787    #[inline]
788    fn partial_cmp(&self, other: &JavaCodePoint) -> Option<Ordering> {
789        JavaCodePoint::from_char(*self).partial_cmp(other)
790    }
791}
792
793impl PartialEq<char> for JavaCodePoint {
794    #[inline]
795    fn eq(&self, other: &char) -> bool {
796        self == &JavaCodePoint::from_char(*other)
797    }
798}
799
800impl PartialEq<JavaCodePoint> for char {
801    #[inline]
802    fn eq(&self, other: &JavaCodePoint) -> bool {
803        &JavaCodePoint::from_char(*self) == other
804    }
805}
806
807pub(crate) struct EscapeDebugExtArgs {
808    pub(crate) escape_single_quote: bool,
809    pub(crate) escape_double_quote: bool,
810}
811
812impl EscapeDebugExtArgs {
813    pub(crate) const ESCAPE_ALL: Self = Self {
814        escape_single_quote: true,
815        escape_double_quote: true,
816    };
817}
818
819#[derive(Clone, Debug)]
820pub struct CharEscapeIter {
821    inner: EscapeIterInner,
822}
823
824#[derive(Clone, Debug)]
825enum EscapeIterInner {
826    Printable(Once<char>),
827    Escaped(EscapeIterEscaped),
828}
829
830impl Display for EscapeIterInner {
831    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
832        match self {
833            EscapeIterInner::Printable(char) => char.clone().try_for_each(|ch| f.write_char(ch)),
834            EscapeIterInner::Escaped(escaped) => Display::fmt(escaped, f),
835        }
836    }
837}
838
839impl CharEscapeIter {
840    #[inline]
841    fn printable(char: char) -> Self {
842        CharEscapeIter {
843            inner: EscapeIterInner::Printable(once(char)),
844        }
845    }
846
847    /// # Safety
848    /// Assumes that the input byte array is ASCII
849    #[inline]
850    unsafe fn new<const N: usize>(bytes: [u8; N]) -> Self {
851        assert!(N <= 10, "Too many bytes in escape iter");
852        let mut ten_bytes = [0; 10];
853        ten_bytes[..N].copy_from_slice(&bytes);
854        CharEscapeIter {
855            inner: EscapeIterInner::Escaped(EscapeIterEscaped {
856                bytes: ten_bytes,
857                range: 0..N,
858            }),
859        }
860    }
861}
862
863impl Iterator for CharEscapeIter {
864    type Item = char;
865
866    #[inline]
867    fn next(&mut self) -> Option<Self::Item> {
868        match &mut self.inner {
869            EscapeIterInner::Printable(printable) => printable.next(),
870            EscapeIterInner::Escaped(escaped) => escaped.next(),
871        }
872    }
873
874    #[inline]
875    fn size_hint(&self) -> (usize, Option<usize>) {
876        match &self.inner {
877            EscapeIterInner::Printable(printable) => printable.size_hint(),
878            EscapeIterInner::Escaped(escaped) => escaped.size_hint(),
879        }
880    }
881}
882
883impl ExactSizeIterator for CharEscapeIter {
884    #[inline]
885    fn len(&self) -> usize {
886        match &self.inner {
887            EscapeIterInner::Printable(printable) => printable.len(),
888            EscapeIterInner::Escaped(escaped) => escaped.len(),
889        }
890    }
891}
892
893impl FusedIterator for CharEscapeIter {}
894
895impl Display for CharEscapeIter {
896    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
897        Display::fmt(&self.inner, f)
898    }
899}
900
901#[derive(Clone, Debug)]
902struct EscapeIterEscaped {
903    // SAFETY: all values must be in the ASCII range
904    bytes: [u8; 10],
905    // SAFETY: range must not be out of bounds for length 10
906    range: Range<usize>,
907}
908
909impl Iterator for EscapeIterEscaped {
910    type Item = char;
911
912    #[inline]
913    fn next(&mut self) -> Option<Self::Item> {
914        self.range.next().map(|index| unsafe {
915            // SAFETY: the range is never out of bounds for length 10
916            char::from(*self.bytes.get_unchecked(index))
917        })
918    }
919
920    #[inline]
921    fn size_hint(&self) -> (usize, Option<usize>) {
922        self.range.size_hint()
923    }
924
925    #[inline]
926    fn count(self) -> usize {
927        self.range.len()
928    }
929}
930
931impl ExactSizeIterator for EscapeIterEscaped {
932    #[inline]
933    fn len(&self) -> usize {
934        self.range.len()
935    }
936}
937
938impl FusedIterator for EscapeIterEscaped {}
939
940impl Display for EscapeIterEscaped {
941    #[inline]
942    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
943        let str = unsafe {
944            // SAFETY: all bytes are in ASCII range, and range is in bounds for length 10
945            std::str::from_utf8_unchecked(self.bytes.get_unchecked(self.range.clone()))
946        };
947        f.write_str(str)
948    }
949}
950
951pub type ToLowercase = CharIterDelegate<std::char::ToLowercase>;
952pub type ToUppercase = CharIterDelegate<std::char::ToUppercase>;
953
954#[derive(Debug, Clone)]
955pub struct CharIterDelegate<I>(CharIterDelegateInner<I>);
956
957impl<I> CharIterDelegate<I> {
958    #[inline]
959    fn char(iter: I) -> CharIterDelegate<I> {
960        CharIterDelegate(CharIterDelegateInner::Char(iter))
961    }
962
963    #[inline]
964    fn invalid(code_point: JavaCodePoint) -> CharIterDelegate<I> {
965        CharIterDelegate(CharIterDelegateInner::Invalid(Some(code_point).into_iter()))
966    }
967}
968
969#[derive(Debug, Clone)]
970enum CharIterDelegateInner<I> {
971    Char(I),
972    Invalid(std::option::IntoIter<JavaCodePoint>),
973}
974
975impl<I> Iterator for CharIterDelegate<I>
976where
977    I: Iterator<Item = char>,
978{
979    type Item = JavaCodePoint;
980
981    #[inline]
982    fn next(&mut self) -> Option<Self::Item> {
983        match &mut self.0 {
984            CharIterDelegateInner::Char(char_iter) => {
985                char_iter.next().map(JavaCodePoint::from_char)
986            }
987            CharIterDelegateInner::Invalid(code_point) => code_point.next(),
988        }
989    }
990
991    #[inline]
992    fn size_hint(&self) -> (usize, Option<usize>) {
993        match &self.0 {
994            CharIterDelegateInner::Char(char_iter) => char_iter.size_hint(),
995            CharIterDelegateInner::Invalid(code_point) => code_point.size_hint(),
996        }
997    }
998}
999
1000impl<I> DoubleEndedIterator for CharIterDelegate<I>
1001where
1002    I: Iterator<Item = char> + DoubleEndedIterator,
1003{
1004    #[inline]
1005    fn next_back(&mut self) -> Option<Self::Item> {
1006        match &mut self.0 {
1007            CharIterDelegateInner::Char(char_iter) => {
1008                char_iter.next_back().map(JavaCodePoint::from_char)
1009            }
1010            CharIterDelegateInner::Invalid(code_point) => code_point.next_back(),
1011        }
1012    }
1013}
1014
1015impl<I> ExactSizeIterator for CharIterDelegate<I> where I: Iterator<Item = char> + ExactSizeIterator {}
1016
1017impl<I> FusedIterator for CharIterDelegate<I> where I: Iterator<Item = char> + FusedIterator {}