1#![no_std]
37#![allow(clippy::precedence, clippy::match_overlapping_arm)]
38
39extern crate alloc;
40
41use alloc::borrow::{Cow, ToOwned};
42use alloc::boxed::Box;
43use alloc::collections::TryReserveError;
44use alloc::string::String;
45use alloc::vec::Vec;
46use core::borrow::Borrow;
47use core::fmt;
48use core::hash::{Hash, Hasher};
49use core::iter::FusedIterator;
50use core::mem;
51use core::ops;
52use core::slice;
53use core::str;
54use core_char::MAX_LEN_UTF8;
55use core_char::{MAX_LEN_UTF16, encode_utf8_raw, encode_utf16_raw, len_utf8};
56use core_str::{next_code_point, next_code_point_reverse};
57use itertools::{Either, Itertools};
58
59use bstr::{ByteSlice, ByteVec};
60
61mod core_char;
62mod core_str;
63mod core_str_count;
64
65const UTF8_REPLACEMENT_CHARACTER: &str = "\u{FFFD}";
66
67#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
73pub struct CodePoint {
74 value: u32,
75}
76
77impl fmt::Debug for CodePoint {
80 #[inline]
81 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(formatter, "U+{:04X}", self.value)
83 }
84}
85
86impl fmt::Display for CodePoint {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 self.to_char_lossy().fmt(f)
89 }
90}
91
92impl CodePoint {
93 #[inline]
99 #[must_use]
100 pub const unsafe fn from_u32_unchecked(value: u32) -> Self {
101 Self { value }
102 }
103
104 #[inline]
108 #[must_use]
109 pub const fn from_u32(value: u32) -> Option<Self> {
110 match value {
111 0..=0x10FFFF => Some(Self { value }),
112 _ => None,
113 }
114 }
115
116 #[inline]
120 #[must_use]
121 pub const fn from_char(value: char) -> Self {
122 Self {
123 value: value as u32,
124 }
125 }
126
127 #[inline]
129 #[must_use]
130 pub const fn to_u32(self) -> u32 {
131 self.value
132 }
133
134 #[inline]
136 #[must_use]
137 pub const fn to_lead_surrogate(self) -> Option<LeadSurrogate> {
138 match self.value {
139 lead @ 0xD800..=0xDBFF => Some(LeadSurrogate(lead as u16)),
140 _ => None,
141 }
142 }
143
144 #[inline]
146 #[must_use]
147 pub const fn to_trail_surrogate(self) -> Option<TrailSurrogate> {
148 match self.value {
149 trail @ 0xDC00..=0xDFFF => Some(TrailSurrogate(trail as u16)),
150 _ => None,
151 }
152 }
153
154 #[inline]
158 #[must_use]
159 pub const fn to_char(self) -> Option<char> {
160 match self.value {
161 0xD800..=0xDFFF => None,
162 _ => Some(unsafe { char::from_u32_unchecked(self.value) }),
163 }
164 }
165
166 #[inline]
171 #[must_use]
172 pub fn to_char_lossy(self) -> char {
173 self.to_char().unwrap_or('\u{FFFD}')
174 }
175
176 pub fn is_char_and(self, f: impl FnOnce(char) -> bool) -> bool {
177 self.to_char().is_some_and(f)
178 }
179
180 pub fn encode_wtf8(self, dst: &mut [u8]) -> &mut Wtf8 {
181 unsafe { Wtf8::from_mut_bytes_unchecked(encode_utf8_raw(self.value, dst)) }
182 }
183
184 #[must_use]
185 pub const fn len_wtf8(&self) -> usize {
186 len_utf8(self.value)
187 }
188
189 #[must_use]
190 pub fn is_ascii(&self) -> bool {
191 self.is_char_and(|c| c.is_ascii())
192 }
193}
194
195impl From<u16> for CodePoint {
196 fn from(value: u16) -> Self {
197 unsafe { Self::from_u32_unchecked(value.into()) }
198 }
199}
200
201impl From<u8> for CodePoint {
202 fn from(value: u8) -> Self {
203 char::from(value).into()
204 }
205}
206
207impl From<char> for CodePoint {
208 fn from(value: char) -> Self {
209 Self::from_char(value)
210 }
211}
212
213impl From<ascii::AsciiChar> for CodePoint {
214 fn from(value: ascii::AsciiChar) -> Self {
215 Self::from_char(value.into())
216 }
217}
218
219impl From<CodePoint> for Wtf8Buf {
220 fn from(ch: CodePoint) -> Self {
221 ch.encode_wtf8(&mut [0; MAX_LEN_UTF8]).to_owned()
222 }
223}
224
225impl PartialEq<char> for CodePoint {
226 fn eq(&self, other: &char) -> bool {
227 self.to_u32() == *other as u32
228 }
229}
230impl PartialEq<CodePoint> for char {
231 fn eq(&self, other: &CodePoint) -> bool {
232 *self as u32 == other.to_u32()
233 }
234}
235
236#[derive(Clone, Copy)]
237pub struct LeadSurrogate(u16);
238
239#[derive(Clone, Copy)]
240pub struct TrailSurrogate(u16);
241
242impl LeadSurrogate {
243 #[must_use]
244 pub const fn merge(self, trail: TrailSurrogate) -> char {
245 decode_surrogate_pair(self.0, trail.0)
246 }
247}
248
249#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Default)]
254pub struct Wtf8Buf {
255 bytes: Vec<u8>,
256}
257
258impl ops::Deref for Wtf8Buf {
259 type Target = Wtf8;
260
261 fn deref(&self) -> &Wtf8 {
262 self.as_slice()
263 }
264}
265
266impl ops::DerefMut for Wtf8Buf {
267 fn deref_mut(&mut self) -> &mut Wtf8 {
268 self.as_mut_slice()
269 }
270}
271
272impl Borrow<Wtf8> for Wtf8Buf {
273 fn borrow(&self) -> &Wtf8 {
274 self
275 }
276}
277
278impl fmt::Debug for Wtf8Buf {
285 #[inline]
286 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
287 fmt::Debug::fmt(&**self, formatter)
288 }
289}
290
291impl fmt::Display for Wtf8Buf {
294 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
295 fmt::Display::fmt(&**self, formatter)
296 }
297}
298
299impl Wtf8Buf {
300 #[inline]
302 #[must_use]
303 pub fn new() -> Self {
304 Self::default()
305 }
306
307 #[inline]
309 #[must_use]
310 pub fn with_capacity(capacity: usize) -> Self {
311 Self {
312 bytes: Vec::with_capacity(capacity),
313 }
314 }
315
316 #[inline]
322 #[must_use]
323 pub const unsafe fn from_bytes_unchecked(value: Vec<u8>) -> Self {
324 Self { bytes: value }
325 }
326
327 pub fn from_bytes(value: Vec<u8>) -> Result<Self, Vec<u8>> {
329 match Wtf8::from_bytes(&value) {
330 Some(_) => Ok(unsafe { Self::from_bytes_unchecked(value) }),
331 None => Err(value),
332 }
333 }
334
335 #[inline]
341 #[must_use]
342 pub fn from_string(string: String) -> Self {
343 Self {
344 bytes: string.into_bytes(),
345 }
346 }
347
348 pub fn join<I, S>(sep: impl AsRef<Wtf8>, iter: I) -> Self
349 where
350 I: IntoIterator<Item = S>,
351 S: AsRef<Wtf8>,
352 {
353 let sep = sep.as_ref();
354 let mut iter = iter.into_iter();
355 let mut buf = match iter.next() {
356 Some(first) => first.as_ref().to_owned(),
357 None => return Self::new(),
358 };
359 for part in iter {
360 buf.push_wtf8(sep);
361 buf.push_wtf8(part.as_ref());
362 }
363 buf
364 }
365
366 pub fn clear(&mut self) {
367 self.bytes.clear();
368 }
369
370 #[must_use]
375 pub fn from_wide(v: &[u16]) -> Self {
376 let mut string = Self::with_capacity(v.len());
377 for item in char::decode_utf16(v.iter().copied()) {
378 match item {
379 Ok(ch) => string.push_char(ch),
380 Err(surrogate) => {
381 let surrogate = surrogate.unpaired_surrogate();
382 let code_point = CodePoint::from(surrogate);
384 string.push(code_point);
387 }
388 }
389 }
390 string
391 }
392
393 #[inline]
394 #[must_use]
395 pub fn as_slice(&self) -> &Wtf8 {
396 unsafe { Wtf8::from_bytes_unchecked(&self.bytes) }
397 }
398
399 #[inline]
400 pub fn as_mut_slice(&mut self) -> &mut Wtf8 {
401 unsafe { Wtf8::from_mut_bytes_unchecked(&mut self.bytes) }
405 }
406
407 #[inline]
415 pub fn reserve(&mut self, additional: usize) {
416 self.bytes.reserve(additional)
417 }
418
419 #[inline]
431 pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
432 self.bytes.try_reserve(additional)
433 }
434
435 #[inline]
436 pub fn reserve_exact(&mut self, additional: usize) {
437 self.bytes.reserve_exact(additional)
438 }
439
440 #[inline]
457 pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
458 self.bytes.try_reserve_exact(additional)
459 }
460
461 #[inline]
462 pub fn shrink_to_fit(&mut self) {
463 self.bytes.shrink_to_fit()
464 }
465
466 #[inline]
467 pub fn shrink_to(&mut self, min_capacity: usize) {
468 self.bytes.shrink_to(min_capacity)
469 }
470
471 #[inline]
472 #[must_use]
473 pub fn leak<'a>(self) -> &'a mut Wtf8 {
474 unsafe { Wtf8::from_mut_bytes_unchecked(self.bytes.leak()) }
475 }
476
477 #[inline]
479 #[must_use]
480 pub const fn capacity(&self) -> usize {
481 self.bytes.capacity()
482 }
483
484 #[inline]
486 pub fn push_str(&mut self, other: &str) {
487 self.bytes.extend_from_slice(other.as_bytes())
488 }
489
490 #[inline]
492 pub fn push_wtf8(&mut self, other: &Wtf8) {
493 self.bytes.extend_from_slice(&other.bytes);
494 }
495
496 #[inline]
498 pub fn push_char(&mut self, c: char) {
499 self.push(CodePoint::from_char(c))
500 }
501
502 #[inline]
504 pub fn push(&mut self, code_point: CodePoint) {
505 self.push_wtf8(code_point.encode_wtf8(&mut [0; MAX_LEN_UTF8]))
506 }
507
508 pub fn pop(&mut self) -> Option<CodePoint> {
509 let ch = self.code_points().next_back()?;
510 let new_len = self.len() - ch.len_wtf8();
511 self.bytes.truncate(new_len);
512 Some(ch)
513 }
514
515 #[inline]
522 pub fn truncate(&mut self, new_len: usize) {
523 assert!(is_code_point_boundary(self, new_len));
524 self.bytes.truncate(new_len)
525 }
526
527 #[inline]
529 pub fn insert(&mut self, idx: usize, c: CodePoint) {
530 self.insert_wtf8(idx, c.encode_wtf8(&mut [0; MAX_LEN_UTF8]))
531 }
532
533 #[inline]
535 pub fn insert_wtf8(&mut self, idx: usize, w: &Wtf8) {
536 assert!(is_code_point_boundary(self, idx));
537
538 self.bytes.insert_str(idx, w)
539 }
540
541 #[inline]
543 #[must_use]
544 pub fn into_bytes(self) -> Vec<u8> {
545 self.bytes
546 }
547
548 pub fn into_string(self) -> Result<String, Self> {
556 if self.is_utf8() {
557 Ok(unsafe { String::from_utf8_unchecked(self.bytes) })
558 } else {
559 Err(self)
560 }
561 }
562
563 #[must_use]
569 pub fn into_string_lossy(mut self) -> String {
570 let mut pos = 0;
571 while let Some((surrogate_pos, _)) = self.next_surrogate(pos) {
572 pos = surrogate_pos + 3;
573 self.bytes[surrogate_pos..pos].copy_from_slice(UTF8_REPLACEMENT_CHARACTER.as_bytes());
576 }
577 unsafe { String::from_utf8_unchecked(self.bytes) }
578 }
579
580 #[inline]
582 #[must_use]
583 pub fn into_box(self) -> Box<Wtf8> {
584 unsafe { mem::transmute(self.bytes.into_boxed_slice()) }
586 }
587
588 #[must_use]
590 pub fn from_box(boxed: Box<Wtf8>) -> Self {
591 let bytes: Box<[u8]> = unsafe { mem::transmute(boxed) };
592 Self {
593 bytes: bytes.into_vec(),
594 }
595 }
596}
597
598impl FromIterator<CodePoint> for Wtf8Buf {
603 fn from_iter<T: IntoIterator<Item = CodePoint>>(iter: T) -> Self {
604 let mut string = Self::new();
605 string.extend(iter);
606 string
607 }
608}
609
610impl Extend<CodePoint> for Wtf8Buf {
615 fn extend<T: IntoIterator<Item = CodePoint>>(&mut self, iter: T) {
616 let iterator = iter.into_iter();
617 let (low, _high) = iterator.size_hint();
618 self.bytes.reserve(low);
620 iterator.for_each(move |code_point| self.push(code_point));
621 }
622}
623
624impl Extend<char> for Wtf8Buf {
625 fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
626 self.extend(iter.into_iter().map(CodePoint::from))
627 }
628}
629
630impl<W: AsRef<Wtf8>> Extend<W> for Wtf8Buf {
631 fn extend<T: IntoIterator<Item = W>>(&mut self, iter: T) {
632 iter.into_iter()
633 .for_each(move |w| self.push_wtf8(w.as_ref()));
634 }
635}
636
637impl<W: AsRef<Wtf8>> FromIterator<W> for Wtf8Buf {
638 fn from_iter<T: IntoIterator<Item = W>>(iter: T) -> Self {
639 let mut buf = Self::new();
640 iter.into_iter().for_each(|w| buf.push_wtf8(w.as_ref()));
641 buf
642 }
643}
644
645impl Hash for Wtf8Buf {
646 fn hash<H: Hasher>(&self, state: &mut H) {
647 Wtf8::hash(self, state)
648 }
649}
650
651impl AsRef<Wtf8> for Wtf8Buf {
652 fn as_ref(&self) -> &Wtf8 {
653 self
654 }
655}
656
657impl From<String> for Wtf8Buf {
658 fn from(s: String) -> Self {
659 Self::from_string(s)
660 }
661}
662
663impl From<&str> for Wtf8Buf {
664 fn from(s: &str) -> Self {
665 Self::from_string(s.to_owned())
666 }
667}
668
669impl From<ascii::AsciiString> for Wtf8Buf {
670 fn from(s: ascii::AsciiString) -> Self {
671 Self::from_string(s.into())
672 }
673}
674
675#[derive(PartialEq, Eq, PartialOrd, Ord)]
680pub struct Wtf8 {
681 bytes: [u8],
682}
683
684impl AsRef<Self> for Wtf8 {
685 fn as_ref(&self) -> &Self {
686 self
687 }
688}
689
690impl ToOwned for Wtf8 {
691 type Owned = Wtf8Buf;
692
693 fn to_owned(&self) -> Self::Owned {
694 self.to_wtf8_buf()
695 }
696
697 fn clone_into(&self, buf: &mut Self::Owned) {
698 self.bytes.clone_into(&mut buf.bytes);
699 }
700}
701
702impl PartialEq<str> for Wtf8 {
703 fn eq(&self, other: &str) -> bool {
704 self.as_bytes().eq(other.as_bytes())
705 }
706}
707
708impl fmt::Debug for Wtf8 {
712 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
713 fn write_str_escaped(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
714 use core::fmt::Write;
715 for c in s.chars().flat_map(|c| c.escape_debug()) {
716 f.write_char(c)?
717 }
718 Ok(())
719 }
720
721 formatter.write_str("\"")?;
722 let mut pos = 0;
723 while let Some((surrogate_pos, surrogate)) = self.next_surrogate(pos) {
724 write_str_escaped(formatter, unsafe {
725 str::from_utf8_unchecked(&self.bytes[pos..surrogate_pos])
726 })?;
727 write!(formatter, "\\u{{{surrogate:x}}}")?;
728 pos = surrogate_pos + 3;
729 }
730 write_str_escaped(formatter, unsafe {
731 str::from_utf8_unchecked(&self.bytes[pos..])
732 })?;
733 formatter.write_str("\"")
734 }
735}
736
737impl fmt::Display for Wtf8 {
740 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
741 let wtf8_bytes = &self.bytes;
742 let mut pos = 0;
743 loop {
744 match self.next_surrogate(pos) {
745 Some((surrogate_pos, _)) => {
746 formatter.write_str(unsafe {
747 str::from_utf8_unchecked(&wtf8_bytes[pos..surrogate_pos])
748 })?;
749 formatter.write_str(UTF8_REPLACEMENT_CHARACTER)?;
750 pos = surrogate_pos + 3;
751 }
752 None => {
753 let s = unsafe { str::from_utf8_unchecked(&wtf8_bytes[pos..]) };
754 if pos == 0 {
755 return s.fmt(formatter);
756 }
757 return formatter.write_str(s);
758 }
759 }
760 }
761 }
762}
763
764impl Default for &Wtf8 {
765 fn default() -> Self {
766 unsafe { Wtf8::from_bytes_unchecked(&[]) }
767 }
768}
769
770impl Hash for Wtf8 {
771 fn hash<H: Hasher>(&self, state: &mut H) {
772 state.write(self.as_bytes());
773 state.write_u8(0xff);
774 }
775}
776
777impl Wtf8 {
778 #[inline]
782 pub fn new<S: AsRef<Self> + ?Sized>(value: &S) -> &Self {
783 value.as_ref()
784 }
785
786 #[inline]
792 #[must_use]
793 pub const unsafe fn from_bytes_unchecked(value: &[u8]) -> &Self {
794 unsafe { &*(value as *const [u8] as *const Self) }
796 }
797
798 #[inline]
803 const unsafe fn from_mut_bytes_unchecked(value: &mut [u8]) -> &mut Self {
804 unsafe { &mut *(value as *mut [u8] as *mut Self) }
806 }
807
808 #[inline]
812 #[must_use]
813 pub fn from_bytes(b: &[u8]) -> Option<&Self> {
814 let mut rest = b;
815 while let Err(e) = core::str::from_utf8(rest) {
816 rest = &rest[e.valid_up_to()..];
817 let _ = Self::decode_surrogate(rest)?;
818 rest = &rest[3..];
819 }
820 Some(unsafe { Self::from_bytes_unchecked(b) })
821 }
822
823 fn decode_surrogate(b: &[u8]) -> Option<CodePoint> {
824 let [0xed, b2 @ (0xa0..), b3, ..] = *b else {
825 return None;
826 };
827 Some(decode_surrogate(b2, b3).into())
828 }
829
830 #[inline]
832 #[must_use]
833 pub const fn len(&self) -> usize {
834 self.bytes.len()
835 }
836
837 #[inline]
838 #[must_use]
839 pub const fn is_empty(&self) -> bool {
840 self.bytes.is_empty()
841 }
842
843 #[inline]
850 #[must_use]
851 pub const fn ascii_byte_at(&self, position: usize) -> u8 {
852 match self.bytes[position] {
853 ascii_byte @ 0x00..=0x7F => ascii_byte,
854 _ => 0xFF,
855 }
856 }
857
858 #[inline]
860 #[must_use]
861 pub fn code_points(&self) -> Wtf8CodePoints<'_> {
862 Wtf8CodePoints {
863 bytes: self.bytes.iter(),
864 }
865 }
866
867 #[inline]
869 #[must_use]
870 pub fn code_point_indices(&self) -> Wtf8CodePointIndices<'_> {
871 Wtf8CodePointIndices {
872 front_offset: 0,
873 iter: self.code_points(),
874 }
875 }
876
877 #[inline]
879 #[must_use]
880 pub const fn as_bytes(&self) -> &[u8] {
881 &self.bytes
882 }
883
884 #[inline]
890 pub const fn as_str(&self) -> Result<&str, str::Utf8Error> {
891 str::from_utf8(&self.bytes)
892 }
893
894 #[must_use]
896 pub fn to_wtf8_buf(&self) -> Wtf8Buf {
897 Wtf8Buf {
898 bytes: self.bytes.to_vec(),
899 }
900 }
901
902 #[must_use]
909 pub fn to_string_lossy(&self) -> Cow<'_, str> {
910 let Some((surrogate_pos, _)) = self.next_surrogate(0) else {
911 return Cow::Borrowed(unsafe { str::from_utf8_unchecked(&self.bytes) });
912 };
913 let wtf8_bytes = &self.bytes;
914 let mut utf8_bytes = Vec::with_capacity(self.len());
915 utf8_bytes.extend_from_slice(&wtf8_bytes[..surrogate_pos]);
916 utf8_bytes.extend_from_slice(UTF8_REPLACEMENT_CHARACTER.as_bytes());
917 let mut pos = surrogate_pos + 3;
918 loop {
919 match self.next_surrogate(pos) {
920 Some((surrogate_pos, _)) => {
921 utf8_bytes.extend_from_slice(&wtf8_bytes[pos..surrogate_pos]);
922 utf8_bytes.extend_from_slice(UTF8_REPLACEMENT_CHARACTER.as_bytes());
923 pos = surrogate_pos + 3;
924 }
925 None => {
926 utf8_bytes.extend_from_slice(&wtf8_bytes[pos..]);
927 return Cow::Owned(unsafe { String::from_utf8_unchecked(utf8_bytes) });
928 }
929 }
930 }
931 }
932
933 #[inline]
940 #[must_use]
941 pub fn encode_wide(&self) -> EncodeWide<'_> {
942 EncodeWide {
943 code_points: self.code_points(),
944 extra: 0,
945 }
946 }
947
948 #[must_use]
949 pub const fn chunks(&self) -> Wtf8Chunks<'_> {
950 Wtf8Chunks { wtf8: self }
951 }
952
953 pub fn map_utf8<'a, I>(&'a self, f: impl Fn(&'a str) -> I) -> impl Iterator<Item = CodePoint>
954 where
955 I: Iterator<Item = char>,
956 {
957 self.chunks().flat_map(move |chunk| match chunk {
958 Wtf8Chunk::Utf8(s) => Either::Left(f(s).map_into()),
959 Wtf8Chunk::Surrogate(c) => Either::Right(core::iter::once(c)),
960 })
961 }
962
963 #[inline]
964 fn next_surrogate(&self, mut pos: usize) -> Option<(usize, u16)> {
965 let mut iter = self.bytes[pos..].iter();
966 loop {
967 let b = *iter.next()?;
968 if b < 0x80 {
969 pos += 1;
970 } else if b < 0xE0 {
971 iter.next();
972 pos += 2;
973 } else if b == 0xED {
974 match (iter.next(), iter.next()) {
975 (Some(&b2), Some(&b3)) if b2 >= 0xA0 => {
976 return Some((pos, decode_surrogate(b2, b3)));
977 }
978 _ => pos += 3,
979 }
980 } else if b < 0xF0 {
981 iter.next();
982 iter.next();
983 pos += 3;
984 } else {
985 iter.next();
986 iter.next();
987 iter.next();
988 pos += 4;
989 }
990 }
991 }
992
993 #[must_use]
994 pub fn is_code_point_boundary(&self, index: usize) -> bool {
995 is_code_point_boundary(self, index)
996 }
997
998 #[inline]
1000 #[must_use]
1001 pub fn into_box(&self) -> Box<Self> {
1002 let boxed: Box<[u8]> = self.bytes.into();
1003 unsafe { mem::transmute(boxed) }
1004 }
1005
1006 #[must_use]
1008 pub fn empty_box() -> Box<Self> {
1009 let boxed: Box<[u8]> = Default::default();
1010 unsafe { mem::transmute(boxed) }
1011 }
1012
1013 #[inline]
1014 pub fn make_ascii_lowercase(&mut self) {
1015 self.bytes.make_ascii_lowercase()
1016 }
1017
1018 #[inline]
1019 pub fn make_ascii_uppercase(&mut self) {
1020 self.bytes.make_ascii_uppercase()
1021 }
1022
1023 #[inline]
1024 #[must_use]
1025 pub fn to_ascii_lowercase(&self) -> Wtf8Buf {
1026 Wtf8Buf {
1027 bytes: self.bytes.to_ascii_lowercase(),
1028 }
1029 }
1030
1031 #[inline]
1032 #[must_use]
1033 pub fn to_ascii_uppercase(&self) -> Wtf8Buf {
1034 Wtf8Buf {
1035 bytes: self.bytes.to_ascii_uppercase(),
1036 }
1037 }
1038
1039 #[must_use]
1040 pub fn to_lowercase(&self) -> Wtf8Buf {
1041 let mut buf = Wtf8Buf::with_capacity(self.len());
1042 for chunk in self.chunks() {
1043 match chunk {
1044 Wtf8Chunk::Utf8(s) => buf.push_str(&s.to_lowercase()),
1045 Wtf8Chunk::Surrogate(c) => buf.push(c),
1046 }
1047 }
1048 buf
1049 }
1050
1051 #[must_use]
1052 pub fn to_uppercase(&self) -> Wtf8Buf {
1053 let mut buf = Wtf8Buf::with_capacity(self.len());
1054 for chunk in self.chunks() {
1055 match chunk {
1056 Wtf8Chunk::Utf8(s) => buf.push_str(&s.to_uppercase()),
1057 Wtf8Chunk::Surrogate(c) => buf.push(c),
1058 }
1059 }
1060 buf
1061 }
1062
1063 #[inline]
1064 #[must_use]
1065 pub const fn is_ascii(&self) -> bool {
1066 self.bytes.is_ascii()
1067 }
1068
1069 #[inline]
1070 #[must_use]
1071 pub fn is_utf8(&self) -> bool {
1072 self.next_surrogate(0).is_none()
1073 }
1074
1075 #[inline]
1076 #[must_use]
1077 pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
1078 self.bytes.eq_ignore_ascii_case(&other.bytes)
1079 }
1080
1081 pub fn split(&self, pat: &Self) -> impl Iterator<Item = &Self> {
1082 self.as_bytes()
1083 .split_str(pat)
1084 .map(|w| unsafe { Self::from_bytes_unchecked(w) })
1085 }
1086
1087 pub fn splitn(&self, n: usize, pat: &Self) -> impl Iterator<Item = &Self> {
1088 self.as_bytes()
1089 .splitn_str(n, pat)
1090 .map(|w| unsafe { Self::from_bytes_unchecked(w) })
1091 }
1092
1093 pub fn rsplit(&self, pat: &Self) -> impl Iterator<Item = &Self> {
1094 self.as_bytes()
1095 .rsplit_str(pat)
1096 .map(|w| unsafe { Self::from_bytes_unchecked(w) })
1097 }
1098
1099 pub fn rsplitn(&self, n: usize, pat: &Self) -> impl Iterator<Item = &Self> {
1100 self.as_bytes()
1101 .rsplitn_str(n, pat)
1102 .map(|w| unsafe { Self::from_bytes_unchecked(w) })
1103 }
1104
1105 #[must_use]
1106 pub fn trim(&self) -> &Self {
1107 let w = self.bytes.trim();
1108 unsafe { Self::from_bytes_unchecked(w) }
1109 }
1110
1111 #[must_use]
1112 pub fn trim_start(&self) -> &Self {
1113 let w = self.bytes.trim_start();
1114 unsafe { Self::from_bytes_unchecked(w) }
1115 }
1116
1117 #[must_use]
1118 pub fn trim_end(&self) -> &Self {
1119 let w = self.bytes.trim_end();
1120 unsafe { Self::from_bytes_unchecked(w) }
1121 }
1122
1123 pub fn trim_start_matches(&self, f: impl Fn(CodePoint) -> bool) -> &Self {
1124 let mut iter = self.code_points();
1125 loop {
1126 let old = iter.clone();
1127 match iter.next().map(&f) {
1128 Some(true) => continue,
1129 Some(false) => {
1130 iter = old;
1131 break;
1132 }
1133 None => return iter.as_wtf8(),
1134 }
1135 }
1136 iter.as_wtf8()
1137 }
1138
1139 pub fn trim_end_matches(&self, f: impl Fn(CodePoint) -> bool) -> &Self {
1140 let mut iter = self.code_points();
1141 loop {
1142 let old = iter.clone();
1143 match iter.next_back().map(&f) {
1144 Some(true) => continue,
1145 Some(false) => {
1146 iter = old;
1147 break;
1148 }
1149 None => return iter.as_wtf8(),
1150 }
1151 }
1152 iter.as_wtf8()
1153 }
1154
1155 pub fn trim_matches(&self, f: impl Fn(CodePoint) -> bool) -> &Self {
1156 self.trim_start_matches(&f).trim_end_matches(&f)
1157 }
1158
1159 #[must_use]
1160 pub fn find(&self, pat: &Self) -> Option<usize> {
1161 memchr::memmem::find(self.as_bytes(), pat.as_bytes())
1162 }
1163
1164 #[must_use]
1165 pub fn rfind(&self, pat: &Self) -> Option<usize> {
1166 memchr::memmem::rfind(self.as_bytes(), pat.as_bytes())
1167 }
1168
1169 pub fn find_iter(&self, pat: &Self) -> impl Iterator<Item = usize> {
1170 memchr::memmem::find_iter(self.as_bytes(), pat.as_bytes())
1171 }
1172
1173 pub fn rfind_iter(&self, pat: &Self) -> impl Iterator<Item = usize> {
1174 memchr::memmem::rfind_iter(self.as_bytes(), pat.as_bytes())
1175 }
1176
1177 #[must_use]
1178 pub fn contains(&self, pat: &Self) -> bool {
1179 self.bytes.contains_str(pat)
1180 }
1181
1182 #[must_use]
1183 pub fn contains_code_point(&self, pat: CodePoint) -> bool {
1184 self.bytes
1185 .contains_str(pat.encode_wtf8(&mut [0; MAX_LEN_UTF8]))
1186 }
1187
1188 pub fn get(&self, range: impl ops::RangeBounds<usize>) -> Option<&Self> {
1189 let start = match range.start_bound() {
1190 ops::Bound::Included(&i) => i,
1191 ops::Bound::Excluded(&i) => i.saturating_add(1),
1192 ops::Bound::Unbounded => 0,
1193 };
1194 let end = match range.end_bound() {
1195 ops::Bound::Included(&i) => i.saturating_add(1),
1196 ops::Bound::Excluded(&i) => i,
1197 ops::Bound::Unbounded => self.len(),
1198 };
1199 if start <= end && is_code_point_boundary(self, start) && is_code_point_boundary(self, end)
1201 {
1202 Some(unsafe { slice_unchecked(self, start, end) })
1203 } else {
1204 None
1205 }
1206 }
1207
1208 pub fn ends_with(&self, w: impl AsRef<Self>) -> bool {
1209 self.bytes.ends_with_str(w.as_ref())
1210 }
1211
1212 pub fn starts_with(&self, w: impl AsRef<Self>) -> bool {
1213 self.bytes.starts_with_str(w.as_ref())
1214 }
1215
1216 pub fn strip_prefix(&self, w: impl AsRef<Self>) -> Option<&Self> {
1217 self.bytes
1218 .strip_prefix(w.as_ref().as_bytes())
1219 .map(|w| unsafe { Self::from_bytes_unchecked(w) })
1220 }
1221
1222 pub fn strip_suffix(&self, w: impl AsRef<Self>) -> Option<&Self> {
1223 self.bytes
1224 .strip_suffix(w.as_ref().as_bytes())
1225 .map(|w| unsafe { Self::from_bytes_unchecked(w) })
1226 }
1227
1228 #[must_use]
1229 pub fn replace(&self, from: &Self, to: &Self) -> Wtf8Buf {
1230 if from.is_empty() {
1231 return self.insert_at_boundaries(to, usize::MAX);
1232 }
1233 let w = self.bytes.replace(from, to);
1234 unsafe { Wtf8Buf::from_bytes_unchecked(w) }
1235 }
1236
1237 #[must_use]
1238 pub fn replacen(&self, from: &Self, to: &Self, n: usize) -> Wtf8Buf {
1239 if from.is_empty() {
1240 return self.insert_at_boundaries(to, n);
1241 }
1242 let w = self.bytes.replacen(from, to, n);
1243 unsafe { Wtf8Buf::from_bytes_unchecked(w) }
1244 }
1245
1246 fn insert_at_boundaries(&self, to: &Self, limit: usize) -> Wtf8Buf {
1255 let mut result = Wtf8Buf::with_capacity(self.len());
1256 let mut inserted = 0;
1257
1258 for code_point in self.code_points() {
1259 if inserted < limit {
1260 result.push_wtf8(to);
1261 inserted += 1;
1262 }
1263 result.push(code_point);
1264 }
1265
1266 if inserted < limit {
1267 result.push_wtf8(to);
1268 }
1269
1270 result
1271 }
1272}
1273
1274impl AsRef<Wtf8> for str {
1275 fn as_ref(&self) -> &Wtf8 {
1276 unsafe { Wtf8::from_bytes_unchecked(self.as_bytes()) }
1277 }
1278}
1279
1280impl AsRef<[u8]> for Wtf8 {
1281 fn as_ref(&self) -> &[u8] {
1282 self.as_bytes()
1283 }
1284}
1285
1286impl ops::Index<ops::Range<usize>> for Wtf8 {
1293 type Output = Self;
1294
1295 #[inline]
1296 #[track_caller]
1297 fn index(&self, range: ops::Range<usize>) -> &Self {
1298 if range.start <= range.end
1300 && is_code_point_boundary(self, range.start)
1301 && is_code_point_boundary(self, range.end)
1302 {
1303 unsafe { slice_unchecked(self, range.start, range.end) }
1304 } else {
1305 slice_error_fail(self, range.start, range.end)
1306 }
1307 }
1308}
1309
1310impl ops::Index<ops::RangeFrom<usize>> for Wtf8 {
1317 type Output = Self;
1318
1319 #[inline]
1320 #[track_caller]
1321 fn index(&self, range: ops::RangeFrom<usize>) -> &Self {
1322 if is_code_point_boundary(self, range.start) {
1324 unsafe { slice_unchecked(self, range.start, self.len()) }
1325 } else {
1326 slice_error_fail(self, range.start, self.len())
1327 }
1328 }
1329}
1330
1331impl ops::Index<ops::RangeTo<usize>> for Wtf8 {
1338 type Output = Self;
1339
1340 #[inline]
1341 #[track_caller]
1342 fn index(&self, range: ops::RangeTo<usize>) -> &Self {
1343 if is_code_point_boundary(self, range.end) {
1345 unsafe { slice_unchecked(self, 0, range.end) }
1346 } else {
1347 slice_error_fail(self, 0, range.end)
1348 }
1349 }
1350}
1351
1352impl ops::Index<ops::RangeFull> for Wtf8 {
1353 type Output = Self;
1354
1355 #[inline]
1356 fn index(&self, _range: ops::RangeFull) -> &Self {
1357 self
1358 }
1359}
1360
1361#[inline]
1362const fn decode_surrogate(second_byte: u8, third_byte: u8) -> u16 {
1363 0xD800 | (second_byte as u16 & 0x3F) << 6 | third_byte as u16 & 0x3F
1365}
1366
1367#[inline]
1368const fn decode_surrogate_pair(lead: u16, trail: u16) -> char {
1369 let code_point = 0x10000 + ((((lead - 0xD800) as u32) << 10) | (trail - 0xDC00) as u32);
1370 unsafe { char::from_u32_unchecked(code_point) }
1371}
1372
1373#[inline]
1375fn is_code_point_boundary(slice: &Wtf8, index: usize) -> bool {
1376 if index == 0 {
1377 return true;
1378 }
1379 match slice.bytes.get(index) {
1380 None => index == slice.len(),
1381 Some(&b) => (b as i8) >= -0x40,
1382 }
1383}
1384
1385#[track_caller]
1393#[inline]
1394pub fn check_utf8_boundary(slice: &Wtf8, index: usize) {
1395 if index == 0 {
1396 return;
1397 }
1398 match slice.bytes.get(index) {
1399 Some(0xED) => (), Some(&b) if (b as i8) >= -0x40 => return,
1401 Some(_) => panic!("byte index {index} is not a codepoint boundary"),
1402 None if index == slice.len() => return,
1403 None => panic!("byte index {index} is out of bounds"),
1404 }
1405 if slice.bytes[index + 1] >= 0xA0 {
1406 assert!(
1408 !(index >= 3 && slice.bytes[index - 3] == 0xED && slice.bytes[index - 2] >= 0xA0),
1409 "byte index {index} lies between surrogate codepoints"
1410 );
1411 }
1412}
1413
1414#[inline]
1420#[must_use]
1421pub const unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 {
1422 unsafe {
1424 let len = end - begin;
1425 let start = s.as_bytes().as_ptr().add(begin);
1426 Wtf8::from_bytes_unchecked(slice::from_raw_parts(start, len))
1427 }
1428}
1429
1430#[inline(never)]
1432#[track_caller]
1433pub fn slice_error_fail(s: &Wtf8, begin: usize, end: usize) -> ! {
1434 assert!(begin <= end);
1435 panic!("index {begin} and/or {end} in `{s:?}` do not lie on character boundary");
1436}
1437
1438#[must_use]
1441pub const fn is_py_ascii_whitespace(b: u8) -> bool {
1442 matches!(b, b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r' | b' ')
1443}
1444
1445#[derive(Clone)]
1449pub struct Wtf8CodePoints<'a> {
1450 bytes: slice::Iter<'a, u8>,
1451}
1452
1453impl Iterator for Wtf8CodePoints<'_> {
1454 type Item = CodePoint;
1455
1456 #[inline]
1457 fn next(&mut self) -> Option<CodePoint> {
1458 unsafe { next_code_point(&mut self.bytes).map(|c| CodePoint { value: c }) }
1460 }
1461
1462 #[inline]
1463 fn size_hint(&self) -> (usize, Option<usize>) {
1464 let len = self.bytes.len();
1465 (len.saturating_add(3) / 4, Some(len))
1466 }
1467
1468 fn last(mut self) -> Option<Self::Item> {
1469 self.next_back()
1470 }
1471
1472 fn count(self) -> usize {
1473 core_str_count::count_chars(self.as_wtf8())
1474 }
1475}
1476
1477impl DoubleEndedIterator for Wtf8CodePoints<'_> {
1478 #[inline]
1479 fn next_back(&mut self) -> Option<CodePoint> {
1480 unsafe {
1483 next_code_point_reverse(&mut self.bytes).map(|ch| CodePoint::from_u32_unchecked(ch))
1484 }
1485 }
1486}
1487
1488impl<'a> Wtf8CodePoints<'a> {
1489 #[must_use]
1490 pub fn as_wtf8(&self) -> &'a Wtf8 {
1491 unsafe { Wtf8::from_bytes_unchecked(self.bytes.as_slice()) }
1492 }
1493}
1494
1495#[derive(Clone)]
1496pub struct Wtf8CodePointIndices<'a> {
1497 front_offset: usize,
1498 iter: Wtf8CodePoints<'a>,
1499}
1500
1501impl Iterator for Wtf8CodePointIndices<'_> {
1502 type Item = (usize, CodePoint);
1503
1504 #[inline]
1505 fn next(&mut self) -> Option<(usize, CodePoint)> {
1506 let pre_len = self.iter.bytes.len();
1507 match self.iter.next() {
1508 None => None,
1509 Some(ch) => {
1510 let index = self.front_offset;
1511 let len = self.iter.bytes.len();
1512 self.front_offset += pre_len - len;
1513 Some((index, ch))
1514 }
1515 }
1516 }
1517
1518 #[inline]
1519 fn size_hint(&self) -> (usize, Option<usize>) {
1520 self.iter.size_hint()
1521 }
1522
1523 #[inline]
1524 fn last(mut self) -> Option<(usize, CodePoint)> {
1525 self.next_back()
1527 }
1528
1529 #[inline]
1530 fn count(self) -> usize {
1531 self.iter.count()
1532 }
1533}
1534
1535impl DoubleEndedIterator for Wtf8CodePointIndices<'_> {
1536 #[inline]
1537 fn next_back(&mut self) -> Option<(usize, CodePoint)> {
1538 self.iter.next_back().map(|ch| {
1539 let index = self.front_offset + self.iter.bytes.len();
1540 (index, ch)
1541 })
1542 }
1543}
1544
1545impl FusedIterator for Wtf8CodePointIndices<'_> {}
1546
1547#[derive(Clone)]
1549pub struct EncodeWide<'a> {
1550 code_points: Wtf8CodePoints<'a>,
1551 extra: u16,
1552}
1553
1554impl Iterator for EncodeWide<'_> {
1556 type Item = u16;
1557
1558 #[inline]
1559 fn next(&mut self) -> Option<u16> {
1560 if self.extra != 0 {
1561 let tmp = self.extra;
1562 self.extra = 0;
1563 return Some(tmp);
1564 }
1565
1566 let mut buf = [0; MAX_LEN_UTF16];
1567 self.code_points.next().map(|code_point| {
1568 let n = encode_utf16_raw(code_point.value, &mut buf).len();
1569 if n == 2 {
1570 self.extra = buf[1];
1571 }
1572 buf[0]
1573 })
1574 }
1575
1576 #[inline]
1577 fn size_hint(&self) -> (usize, Option<usize>) {
1578 let (low, high) = self.code_points.size_hint();
1579 let ext = (self.extra != 0) as usize;
1580 (
1584 low + ext,
1585 high.and_then(|n| n.checked_mul(2))
1586 .and_then(|n| n.checked_add(ext)),
1587 )
1588 }
1589}
1590
1591impl FusedIterator for EncodeWide<'_> {}
1592
1593pub struct Wtf8Chunks<'a> {
1594 wtf8: &'a Wtf8,
1595}
1596
1597impl<'a> Iterator for Wtf8Chunks<'a> {
1598 type Item = Wtf8Chunk<'a>;
1599
1600 fn next(&mut self) -> Option<Self::Item> {
1601 match self.wtf8.next_surrogate(0) {
1602 Some((0, surrogate)) => {
1603 self.wtf8 = &self.wtf8[3..];
1604 Some(Wtf8Chunk::Surrogate(surrogate.into()))
1605 }
1606 Some((n, _)) => {
1607 let s = unsafe { str::from_utf8_unchecked(&self.wtf8.as_bytes()[..n]) };
1608 self.wtf8 = &self.wtf8[n..];
1609 Some(Wtf8Chunk::Utf8(s))
1610 }
1611 None => {
1612 let s =
1613 unsafe { str::from_utf8_unchecked(core::mem::take(&mut self.wtf8).as_bytes()) };
1614 (!s.is_empty()).then_some(Wtf8Chunk::Utf8(s))
1615 }
1616 }
1617 }
1618}
1619
1620pub enum Wtf8Chunk<'a> {
1621 Utf8(&'a str),
1622 Surrogate(CodePoint),
1623}
1624
1625impl Hash for CodePoint {
1626 #[inline]
1627 fn hash<H: Hasher>(&self, state: &mut H) {
1628 self.value.hash(state)
1629 }
1630}
1631
1632#[must_use]
1638pub unsafe fn from_boxed_wtf8_unchecked(value: Box<[u8]>) -> Box<Wtf8> {
1639 unsafe { Box::from_raw(Box::into_raw(value) as *mut Wtf8) }
1640}
1641
1642impl Clone for Box<Wtf8> {
1643 fn clone(&self) -> Self {
1644 (&**self).into()
1645 }
1646}
1647
1648impl Default for Box<Wtf8> {
1649 fn default() -> Self {
1650 unsafe { from_boxed_wtf8_unchecked(Box::default()) }
1651 }
1652}
1653
1654impl From<&Wtf8> for Box<Wtf8> {
1655 fn from(w: &Wtf8) -> Self {
1656 w.into_box()
1657 }
1658}
1659
1660impl<'a> From<&'a str> for &'a Wtf8 {
1661 #[inline]
1662 fn from(s: &'a str) -> &'a Wtf8 {
1663 unsafe { Wtf8::from_bytes_unchecked(s.as_bytes()) }
1665 }
1666}
1667
1668impl From<&str> for Box<Wtf8> {
1669 fn from(s: &str) -> Self {
1670 Box::<str>::from(s).into()
1671 }
1672}
1673
1674impl From<Box<str>> for Box<Wtf8> {
1675 fn from(s: Box<str>) -> Self {
1676 unsafe { from_boxed_wtf8_unchecked(s.into_boxed_bytes()) }
1677 }
1678}
1679
1680impl From<Box<ascii::AsciiStr>> for Box<Wtf8> {
1681 fn from(s: Box<ascii::AsciiStr>) -> Self {
1682 <Box<str>>::from(s).into()
1683 }
1684}
1685
1686impl From<Box<Wtf8>> for Box<[u8]> {
1687 fn from(w: Box<Wtf8>) -> Self {
1688 unsafe { Self::from_raw(Box::into_raw(w) as *mut [u8]) }
1689 }
1690}
1691
1692impl From<Wtf8Buf> for Box<Wtf8> {
1693 fn from(w: Wtf8Buf) -> Self {
1694 w.into_box()
1695 }
1696}
1697
1698impl From<Box<Wtf8>> for Wtf8Buf {
1699 fn from(w: Box<Wtf8>) -> Self {
1700 Self::from_box(w)
1701 }
1702}
1703
1704impl From<String> for Box<Wtf8> {
1705 fn from(s: String) -> Self {
1706 s.into_boxed_str().into()
1707 }
1708}
1709
1710mod concat;
1711pub use concat::Wtf8Concat;
1712
1713#[cfg(test)]
1714mod tests {
1715 use super::{String, Wtf8, Wtf8Buf};
1716
1717 fn w(s: &str) -> &Wtf8 {
1718 Wtf8::new(s)
1719 }
1720
1721 fn buf(s: &str) -> Wtf8Buf {
1722 Wtf8Buf::from_string(String::from(s))
1723 }
1724
1725 #[test]
1726 fn replace_empty_needle_splits_on_code_points() {
1727 assert_eq!(w("abc").replace(w(""), w("-")), buf("-a-b-c-"));
1728 assert_eq!(w("ábç").replace(w(""), w("#")), buf("#á#b#ç#"));
1729 assert_eq!(w("😀").replace(w(""), w("-")), buf("-😀-"));
1730 assert_eq!(w("").replace(w(""), w("-")), buf("-"));
1731 assert_eq!(w("abc").replace(w(""), w("")), buf("abc"));
1732 }
1733
1734 #[test]
1735 fn replacen_empty_needle_counts_insertions() {
1736 assert_eq!(w("abc").replacen(w(""), w("-"), 0), buf("abc"));
1737 assert_eq!(w("abc").replacen(w(""), w("-"), 1), buf("-abc"));
1738 assert_eq!(w("abc").replacen(w(""), w("-"), 3), buf("-a-b-c"));
1739 assert_eq!(w("abc").replacen(w(""), w("-"), 4), buf("-a-b-c-"));
1740 assert_eq!(w("abc").replacen(w(""), w("-"), 99), buf("-a-b-c-"));
1741 assert_eq!(w("ábç").replacen(w(""), w("#"), 2), buf("#á#bç"));
1742 }
1743
1744 #[test]
1745 fn replace_result_stays_valid_wtf8() {
1746 for subject in ["abc", "á", "ábç", "😀", "a😀b", ""] {
1751 for insert in ["-", "", "ç", "😀"] {
1752 let replaced = Wtf8::new(subject).replace(w(""), w(insert));
1753 assert!(
1754 Wtf8::from_bytes(replaced.as_bytes()).is_some(),
1755 "{subject:?} with {insert:?} produced bytes that are not WTF-8"
1756 );
1757 }
1758 }
1759 }
1760
1761 #[test]
1762 fn replace_non_empty_needle_is_unchanged() {
1763 assert_eq!(w("ábç").replace(w("b"), w("#")), buf("á#ç"));
1764 assert_eq!(w("ábç").replace(w("á"), w("#")), buf("#bç"));
1765 assert_eq!(w("aaa").replacen(w("a"), w("b"), 2), buf("bba"));
1766 }
1767}