Skip to main content

rustpython_wtf8/
lib.rs

1// spell-checker:disable
2
3//! An implementation of [WTF-8], a utf8-compatible encoding that allows for
4//! unpaired surrogate codepoints. This implementation additionally allows for
5//! paired surrogates that are nonetheless treated as two separate codepoints.
6//!
7//!
8//! RustPython uses this because CPython internally uses a variant of UCS-1/2/4
9//! as its string storage, which treats each `u8`/`u16`/`u32` value (depending
10//! on the highest codepoint value in the string) as simply integers, unlike
11//! UTF-8 or UTF-16 where some characters are encoded using multi-byte
12//! sequences. CPython additionally doesn't disallow the use of surrogates in
13//! `str`s (which in UTF-16 pair together to represent codepoints with a value
14//! higher than `u16::MAX`) and in fact takes quite extensive advantage of the
15//! fact that they're allowed. The `surrogateescape` codec-error handler uses
16//! them to represent byte sequences which are invalid in the given codec (e.g.
17//! bytes with their high bit set in ASCII or UTF-8) by mapping them into the
18//! surrogate range. `surrogateescape` is the default error handler in Python
19//! for interacting with the filesystem, and thus if RustPython is to properly
20//! support `surrogateescape`, its `str`s must be able to represent surrogates.
21//!
22//! We use WTF-8 over something more similar to CPython's string implementation
23//! because of its compatibility with UTF-8, meaning that in the case where a
24//! string has no surrogates, it can be viewed as a UTF-8 Rust [`prim@str`] without
25//! needing any copies or re-encoding.
26//!
27//! This implementation is mostly copied from the WTF-8 implementation in the
28//! Rust 1.85 standard library, which is used as the backing for [`OsStr`] on
29//! Windows targets. As previously mentioned, however, it is modified to not
30//! join two surrogates into one codepoint when concatenating strings, in order
31//! to match CPython's behavior.
32//!
33//! [WTF-8]: https://simonsapin.github.io/wtf-8
34//! [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
35
36#![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/// A Unicode code point: from U+0000 to U+10FFFF.
68///
69/// Compares with the `char` type,
70/// which represents a Unicode scalar value:
71/// a code point that is not a surrogate (U+D800 to U+DFFF).
72#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
73pub struct CodePoint {
74    value: u32,
75}
76
77/// Format the code point as `U+` followed by four to six hexadecimal digits.
78/// Example: `U+1F4A9`
79impl 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    /// Unsafely creates a new `CodePoint` without checking the value.
94    ///
95    /// # Safety
96    ///
97    /// `value` must be less than or equal to 0x10FFFF.
98    #[inline]
99    #[must_use]
100    pub const unsafe fn from_u32_unchecked(value: u32) -> Self {
101        Self { value }
102    }
103
104    /// Creates a new `CodePoint` if the value is a valid code point.
105    ///
106    /// Returns `None` if `value` is above 0x10FFFF.
107    #[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    /// Creates a new `CodePoint` from a `char`.
117    ///
118    /// Since all Unicode scalar values are code points, this always succeeds.
119    #[inline]
120    #[must_use]
121    pub const fn from_char(value: char) -> Self {
122        Self {
123            value: value as u32,
124        }
125    }
126
127    /// Returns the numeric value of the code point.
128    #[inline]
129    #[must_use]
130    pub const fn to_u32(self) -> u32 {
131        self.value
132    }
133
134    /// Returns the numeric value of the code point if it is a leading surrogate.
135    #[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    /// Returns the numeric value of the code point if it is a trailing surrogate.
145    #[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    /// Optionally returns a Unicode scalar value for the code point.
155    ///
156    /// Returns `None` if the code point is a surrogate (from U+D800 to U+DFFF).
157    #[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    /// Returns a Unicode scalar value for the code point.
167    ///
168    /// Returns `'\u{FFFD}'` (the replacement character “�”)
169    /// if the code point is a surrogate (from U+D800 to U+DFFF).
170    #[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/// An owned, growable string of well-formed WTF-8 data.
250///
251/// Similar to `String`, but can additionally contain surrogate code points
252/// if they’re not in a surrogate pair.
253#[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
278/// Formats the string in double quotes, with characters escaped according to
279/// [`char::escape_debug`] and unpaired surrogates represented as `\u{xxxx}`,
280/// where each `x` is a hexadecimal digit.
281///
282/// For example, the code units [U+0061, U+D800, U+000A] are formatted as
283/// `"a\u{D800}\n"`.
284impl 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
291/// Formats the string with unpaired surrogates substituted with the replacement
292/// character, U+FFFD.
293impl 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    /// Creates a new, empty WTF-8 string.
301    #[inline]
302    #[must_use]
303    pub fn new() -> Self {
304        Self::default()
305    }
306
307    /// Creates a new, empty WTF-8 string with pre-allocated capacity for `capacity` bytes.
308    #[inline]
309    #[must_use]
310    pub fn with_capacity(capacity: usize) -> Self {
311        Self {
312            bytes: Vec::with_capacity(capacity),
313        }
314    }
315
316    /// Creates a WTF-8 string from a WTF-8 byte vec.
317    ///
318    /// # Safety
319    ///
320    /// `value` must contain valid WTF-8.
321    #[inline]
322    #[must_use]
323    pub const unsafe fn from_bytes_unchecked(value: Vec<u8>) -> Self {
324        Self { bytes: value }
325    }
326
327    /// Create a WTF-8 string from a WTF-8 byte vec.
328    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    /// Creates a WTF-8 string from a UTF-8 `String`.
336    ///
337    /// This takes ownership of the `String` and does not copy.
338    ///
339    /// Since WTF-8 is a superset of UTF-8, this always succeeds.
340    #[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    /// Creates a WTF-8 string from a potentially ill-formed UTF-16 slice of 16-bit code units.
371    ///
372    /// This is lossless: calling `.encode_wide()` on the resulting string
373    /// will always return the original code units.
374    #[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                    // Surrogates are known to be in the code point range.
383                    let code_point = CodePoint::from(surrogate);
384                    // Skip the WTF-8 concatenation check,
385                    // surrogate pairs are already decoded by decode_utf16
386                    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        // Safety: `Wtf8` doesn't expose any way to mutate the bytes that would
402        // cause them to change from well-formed UTF-8 to ill-formed UTF-8,
403        // which would break the assumptions of the `is_known_utf8` field.
404        unsafe { Wtf8::from_mut_bytes_unchecked(&mut self.bytes) }
405    }
406
407    /// Reserves capacity for at least `additional` more bytes to be inserted
408    /// in the given `Wtf8Buf`.
409    /// The collection may reserve more space to avoid frequent reallocations.
410    ///
411    /// # Panics
412    ///
413    /// Panics if the new capacity exceeds `isize::MAX` bytes.
414    #[inline]
415    pub fn reserve(&mut self, additional: usize) {
416        self.bytes.reserve(additional)
417    }
418
419    /// Tries to reserve capacity for at least `additional` more bytes to be
420    /// inserted in the given `Wtf8Buf`. The `Wtf8Buf` may reserve more space to
421    /// avoid frequent reallocations. After calling `try_reserve`, capacity will
422    /// be greater than or equal to `self.len() + additional`. Does nothing if
423    /// capacity is already sufficient. This method preserves the contents even
424    /// if an error occurs.
425    ///
426    /// # Errors
427    ///
428    /// If the capacity overflows, or the allocator reports a failure, then an error
429    /// is returned.
430    #[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    /// Tries to reserve the minimum capacity for exactly `additional` more
441    /// bytes to be inserted in the given `Wtf8Buf`. After calling
442    /// `try_reserve_exact`, capacity will be greater than or equal to
443    /// `self.len() + additional` if it returns `Ok(())`.
444    /// Does nothing if the capacity is already sufficient.
445    ///
446    /// Note that the allocator may give the `Wtf8Buf` more space than it
447    /// requests. Therefore, capacity can not be relied upon to be precisely
448    /// minimal. Prefer [`try_reserve`] if future insertions are expected.
449    ///
450    /// [`try_reserve`]: Wtf8Buf::try_reserve
451    ///
452    /// # Errors
453    ///
454    /// If the capacity overflows, or the allocator reports a failure, then an error
455    /// is returned.
456    #[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    /// Returns the number of bytes that this string buffer can hold without reallocating.
478    #[inline]
479    #[must_use]
480    pub const fn capacity(&self) -> usize {
481        self.bytes.capacity()
482    }
483
484    /// Append a UTF-8 slice at the end of the string.
485    #[inline]
486    pub fn push_str(&mut self, other: &str) {
487        self.bytes.extend_from_slice(other.as_bytes())
488    }
489
490    /// Append a WTF-8 slice at the end of the string.
491    #[inline]
492    pub fn push_wtf8(&mut self, other: &Wtf8) {
493        self.bytes.extend_from_slice(&other.bytes);
494    }
495
496    /// Append a Unicode scalar value at the end of the string.
497    #[inline]
498    pub fn push_char(&mut self, c: char) {
499        self.push(CodePoint::from_char(c))
500    }
501
502    /// Append a code point at the end of the string.
503    #[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    /// Shortens a string to the specified length.
516    ///
517    /// # Panics
518    ///
519    /// Panics if `new_len` > current length,
520    /// or if `new_len` is not a code point boundary.
521    #[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    /// Inserts a codepoint into this `Wtf8Buf` at a byte position.
528    #[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    /// Inserts a WTF-8 slice into this `Wtf8Buf` at a byte position.
534    #[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    /// Consumes the WTF-8 string and tries to convert it to a vec of bytes.
542    #[inline]
543    #[must_use]
544    pub fn into_bytes(self) -> Vec<u8> {
545        self.bytes
546    }
547
548    /// Consumes the WTF-8 string and tries to convert it to UTF-8.
549    ///
550    /// This does not copy the data.
551    ///
552    /// If the contents are not well-formed UTF-8
553    /// (that is, if the string contains surrogates),
554    /// the original WTF-8 string is returned instead.
555    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    /// Consumes the WTF-8 string and converts it lossily to UTF-8.
564    ///
565    /// This does not copy the data (but may overwrite parts of it in place).
566    ///
567    /// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”)
568    #[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            // Surrogates and the replacement character are all 3 bytes, so
574            // they can substituted in-place.
575            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    /// Converts this `Wtf8Buf` into a boxed `Wtf8`.
581    #[inline]
582    #[must_use]
583    pub fn into_box(self) -> Box<Wtf8> {
584        // SAFETY: relies on `Wtf8` being `repr(transparent)`.
585        unsafe { mem::transmute(self.bytes.into_boxed_slice()) }
586    }
587
588    /// Converts a `Box<Wtf8>` into a `Wtf8Buf`.
589    #[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
598/// Creates a new WTF-8 string from an iterator of code points.
599///
600/// This replaces surrogate code point pairs with supplementary code points,
601/// like concatenating ill-formed UTF-16 strings effectively would.
602impl 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
610/// Append code points from an iterator to the string.
611///
612/// This replaces surrogate code point pairs with supplementary code points,
613/// like concatenating ill-formed UTF-16 strings effectively would.
614impl 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        // Lower bound of one byte per code point (ASCII only)
619        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/// A borrowed slice of well-formed WTF-8 data.
676///
677/// Similar to `&str`, but can additionally contain surrogate code points
678/// if they’re not in a surrogate pair.
679#[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
708/// Formats the string in double quotes, with characters escaped according to
709/// [`char::escape_debug`] and unpaired surrogates represented as `\u{xxxx}`,
710/// where each `x` is a hexadecimal digit.
711impl 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
737/// Formats the string with unpaired surrogates substituted with the replacement
738/// character, U+FFFD.
739impl 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    /// Creates a WTF-8 slice from a UTF-8 `&str` slice.
779    ///
780    /// Since WTF-8 is a superset of UTF-8, this always succeeds.
781    #[inline]
782    pub fn new<S: AsRef<Self> + ?Sized>(value: &S) -> &Self {
783        value.as_ref()
784    }
785
786    /// Creates a WTF-8 slice from a WTF-8 byte slice.
787    ///
788    /// # Safety
789    ///
790    /// `value` must contain valid WTF-8.
791    #[inline]
792    #[must_use]
793    pub const unsafe fn from_bytes_unchecked(value: &[u8]) -> &Self {
794        // SAFETY: start with &[u8], end with fancy &[u8]
795        unsafe { &*(value as *const [u8] as *const Self) }
796    }
797
798    /// Creates a mutable WTF-8 slice from a mutable WTF-8 byte slice.
799    ///
800    /// Since the byte slice is not checked for valid WTF-8, this functions is
801    /// marked unsafe.
802    #[inline]
803    const unsafe fn from_mut_bytes_unchecked(value: &mut [u8]) -> &mut Self {
804        // SAFETY: start with &mut [u8], end with fancy &mut [u8]
805        unsafe { &mut *(value as *mut [u8] as *mut Self) }
806    }
807
808    /// Create a WTF-8 slice from a WTF-8 byte slice.
809    //
810    // whoops! using WTF-8 for interchange!
811    #[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    /// Returns the length, in WTF-8 bytes.
831    #[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    /// Returns the code point at `position` if it is in the ASCII range,
844    /// or `b'\xFF'` otherwise.
845    ///
846    /// # Panics
847    ///
848    /// Panics if `position` is beyond the end of the string.
849    #[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    /// Returns an iterator for the string’s code points.
859    #[inline]
860    #[must_use]
861    pub fn code_points(&self) -> Wtf8CodePoints<'_> {
862        Wtf8CodePoints {
863            bytes: self.bytes.iter(),
864        }
865    }
866
867    /// Returns an iterator for the string’s code points and their indices.
868    #[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    /// Access raw bytes of WTF-8 data
878    #[inline]
879    #[must_use]
880    pub const fn as_bytes(&self) -> &[u8] {
881        &self.bytes
882    }
883
884    /// Tries to convert the string to UTF-8 and return a `&str` slice.
885    ///
886    /// Returns `None` if the string contains surrogates.
887    ///
888    /// This does not copy the data.
889    #[inline]
890    pub const fn as_str(&self) -> Result<&str, str::Utf8Error> {
891        str::from_utf8(&self.bytes)
892    }
893
894    /// Creates an owned `Wtf8Buf` from a borrowed `Wtf8`.
895    #[must_use]
896    pub fn to_wtf8_buf(&self) -> Wtf8Buf {
897        Wtf8Buf {
898            bytes: self.bytes.to_vec(),
899        }
900    }
901
902    /// Lossily converts the string to UTF-8.
903    /// Returns a UTF-8 `&str` slice if the contents are well-formed in UTF-8.
904    ///
905    /// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”).
906    ///
907    /// This only copies the data if necessary (if it contains any surrogate).
908    #[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    /// Converts the WTF-8 string to potentially ill-formed UTF-16
934    /// and return an iterator of 16-bit code units.
935    ///
936    /// This is lossless:
937    /// calling `Wtf8Buf::from_ill_formed_utf16` on the resulting code units
938    /// would always return the original WTF-8 string.
939    #[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    /// Boxes this `Wtf8`.
999    #[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    /// Creates a boxed, empty `Wtf8`.
1007    #[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        // is_code_point_boundary checks that the index is in [0, .len()]
1200        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    /// Inserts `to` before every code point and once after the last one,
1247    /// stopping after `limit` insertions.
1248    ///
1249    /// This is what an empty needle asks for. It cannot go through the byte
1250    /// search the other cases use: that one matches an empty needle at every
1251    /// *byte*, so it splits a multi-byte code point down the middle and leaves
1252    /// bytes that are no longer WTF-8 at all. A needle that is not empty is
1253    /// safe there, because a WTF-8 sequence never starts inside another one.
1254    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
1286/// Returns a slice of the given string for the byte range \[`begin`..`end`).
1287///
1288/// # Panics
1289///
1290/// Panics when `begin` and `end` do not point to code point boundaries,
1291/// or point beyond the end of the string.
1292impl 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        // is_code_point_boundary checks that the index is in [0, .len()]
1299        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
1310/// Returns a slice of the given string from byte `begin` to its end.
1311///
1312/// # Panics
1313///
1314/// Panics when `begin` is not at a code point boundary,
1315/// or is beyond the end of the string.
1316impl 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        // is_code_point_boundary checks that the index is in [0, .len()]
1323        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
1331/// Returns a slice of the given string from its beginning to byte `end`.
1332///
1333/// # Panics
1334///
1335/// Panics when `end` is not at a code point boundary,
1336/// or is beyond the end of the string.
1337impl 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        // is_code_point_boundary checks that the index is in [0, .len()]
1344        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    // The first byte is assumed to be 0xED
1364    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/// Copied from str::is_char_boundary
1374#[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/// Verify that `index` is at the edge of either a valid UTF-8 codepoint
1386/// (i.e. a codepoint that's not a surrogate) or of the whole string.
1387///
1388/// These are the cases currently permitted by `OsStr::slice_encoded_bytes`.
1389/// Splitting between surrogates is valid as far as WTF-8 is concerned, but
1390/// we do not permit it in the public API because WTF-8 is considered an
1391/// implementation detail.
1392#[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) => (), // Might be a surrogate
1400        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        // There's a surrogate after index. Now check before index.
1407        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/// Copied from core::str::raw::slice_unchecked
1415///
1416/// # Safety
1417///
1418/// `begin` and `end` must be within bounds and on codepoint boundaries.
1419#[inline]
1420#[must_use]
1421pub const unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 {
1422    // SAFETY: memory layout of a &[u8] and &Wtf8 are the same
1423    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/// Copied from core::str::raw::slice_error_fail
1431#[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/// True for the ASCII bytes Python treats as whitespace in numeric parsing
1439/// (`\t \n \x0b \x0c \r` and space).
1440#[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/// Iterator for the code points of a WTF-8 string.
1446///
1447/// Created with the method `.code_points()`.
1448#[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        // SAFETY: `self.bytes` has been created from a WTF-8 string
1459        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        // SAFETY: `str` invariant says `self.iter` is a valid WTF-8 string and
1481        // the resulting `ch` is a valid Unicode Code Point.
1482        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        // No need to go through the entire string.
1526        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/// Generates a wide character sequence for potentially ill-formed UTF-16.
1548#[derive(Clone)]
1549pub struct EncodeWide<'a> {
1550    code_points: Wtf8CodePoints<'a>,
1551    extra: u16,
1552}
1553
1554// Copied from libunicode/u_str.rs
1555impl 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        // every code point gets either one u16 or two u16,
1581        // so this iterator is between 1 or 2 times as
1582        // long as the underlying iterator.
1583        (
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// == BOX IMPLS ==
1633
1634/// # Safety
1635///
1636/// `value` must be valid WTF-8.
1637#[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        // Valid UTF-8 is always valid WTF-8
1664        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        // The byte search behind the non-empty cases matches an empty needle at
1747        // every byte, which used to leave a replacement inside a multi-byte code
1748        // point. What came out was not WTF-8, and every later read of it was
1749        // reading something else.
1750        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}