Skip to main content

icu_collections/codepointinvlist/
cpinvlist.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5#[cfg(feature = "serde")]
6use alloc::format;
7#[cfg(feature = "serde")]
8use alloc::string::String;
9#[cfg(feature = "alloc")]
10use alloc::vec::Vec;
11use core::{char, ops::RangeBounds, ops::RangeInclusive};
12use potential_utf::PotentialCodePoint;
13use yoke::Yokeable;
14use zerofrom::ZeroFrom;
15use zerovec::{ZeroVec, ule::AsULE, zerovec};
16
17use super::InvalidSetError;
18use crate::codepointinvlist::utils::{deconstruct_range, is_valid_zv};
19
20/// The exclusive end value of the the Unicode code point range.
21const SENTINEL: u32 = char::MAX as u32 + 1;
22
23/// A membership wrapper for [`CodePointInversionList`].
24///
25/// Provides exposure to membership functions and constructors from serialized `CodePointSet`s (sets of code points)
26/// and predefined ranges.
27#[repr(C, packed)]
#[doc =
"[`VarULE`](zerovec::ule::VarULE) type for [`CodePointInversionList`]. See [`CodePointInversionList`] for documentation."]
#[allow(missing_docs)]
pub struct CodePointInversionListULE {
    size: <u32 as zerovec::ule::AsULE>::ULE,
    inv_list: zerovec::ZeroSlice<PotentialCodePoint>,
}
unsafe impl<'data> zerovec::ule::EncodeAsVarULE<CodePointInversionListULE> for
    CodePointInversionList<'data> {
    fn encode_var_ule_as_slices<R>(&self, cb: impl FnOnce(&[&[u8]]) -> R)
        -> R {
        {
            ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
                    format_args!("other two methods implemented")));
        }
    }
    fn encode_var_ule_len(&self) -> usize {
        ::core::mem::size_of::<<u32 as zerovec::ule::AsULE>::ULE>() +
            <zerovec::ZeroSlice<PotentialCodePoint> as
                    zerovec::ule::EncodeAsVarULE<zerovec::ZeroSlice<PotentialCodePoint>>>::encode_var_ule_len(&*self.inv_list)
    }
    fn encode_var_ule_write(&self, mut dst: &mut [u8]) {
        if true {
            {
                match (&self.encode_var_ule_len(), &dst.len()) {
                    (left_val, right_val) => {
                        if !(*left_val == *right_val) {
                            let kind = ::core::panicking::AssertKind::Eq;
                            ::core::panicking::assert_failed(kind, &*left_val,
                                &*right_val, ::core::option::Option::None);
                        }
                    }
                }
            };
        };
        const ZERO: usize = 0;
        ;
        const SIZE_0: usize =
            ::core::mem::size_of::<<u32 as zerovec::ule::AsULE>::ULE>();
        const OFFSET_0: usize = ZERO + SIZE_0;
        #[expect(clippy :: indexing_slicing)]
        let out = &mut dst[ZERO..ZERO + SIZE_0];
        let unaligned = zerovec::ule::AsULE::to_unaligned(self.size);
        let unaligned_slice = &[unaligned];
        let src =
            <<u32 as zerovec::ule::AsULE>::ULE as
                    zerovec::ule::ULE>::slice_as_bytes(unaligned_slice);
        out.copy_from_slice(src);
        ;
        #[expect(clippy :: indexing_slicing)]
        let out = &mut dst[OFFSET_0..];
        <zerovec::ZeroSlice<PotentialCodePoint> as
                zerovec::ule::EncodeAsVarULE<zerovec::ZeroSlice<PotentialCodePoint>>>::encode_var_ule_write(&*self.inv_list,
            out)
    }
}
unsafe impl<'data> zerovec::ule::EncodeAsVarULE<CodePointInversionListULE> for
    &'_ CodePointInversionList<'data> {
    fn encode_var_ule_as_slices<R>(&self, cb: impl FnOnce(&[&[u8]]) -> R)
        -> R {
        {
            ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
                    format_args!("other two methods implemented")));
        }
    }
    fn encode_var_ule_len(&self) -> usize { (**self).encode_var_ule_len() }
    fn encode_var_ule_write(&self, mut dst: &mut [u8]) {
        (**self).encode_var_ule_write(dst)
    }
}
impl<'data>
    zerovec::__zerovec_internal_reexport::ZeroFrom<'data,
    CodePointInversionListULE> for CodePointInversionList<'data> {
    fn zero_from(other: &'data CodePointInversionListULE) -> Self {
        Self {
            size: <u32 as zerovec::ule::AsULE>::from_unaligned(other.size),
            inv_list: <ZeroVec<'data, PotentialCodePoint> as
                    zerovec::__zerovec_internal_reexport::ZeroFrom<'data,
                    zerovec::ZeroSlice<PotentialCodePoint>>>::zero_from(&other.inv_list),
        }
    }
}
impl<'data> From<&'data CodePointInversionListULE> for
    CodePointInversionList<'data> {
    fn from(other: &'data CodePointInversionListULE) -> Self {
        <Self as
                zerovec::__zerovec_internal_reexport::ZeroFrom<'data,
                CodePointInversionListULE>>::zero_from(other)
    }
}
const __IMPL_VarULE_FOR_CodePointInversionListULE_ULE_SIZE: usize =
    0 + ::core::mem::size_of::<<u32 as zerovec::ule::AsULE>::ULE>();
unsafe impl zerovec::ule::VarULE for CodePointInversionListULE {
    #[inline]
    fn validate_bytes(bytes_one: &[u8])
        -> Result<(), zerovec::ule::UleError> {
        if true {
            {
                match (&OFFSET_0,
                        &__IMPL_VarULE_FOR_CodePointInversionListULE_ULE_SIZE) {
                    (left_val, right_val) => {
                        if !(*left_val == *right_val) {
                            let kind = ::core::panicking::AssertKind::Eq;
                            ::core::panicking::assert_failed(kind, &*left_val,
                                &*right_val, ::core::option::Option::None);
                        }
                    }
                }
            };
        };
        let Some(last_field_bytes) =
            bytes_one.get(OFFSET_0..) else {
                return Err(zerovec::ule::UleError::parse::<Self>());
            };
        const ZERO: usize = 0;
        ;
        const SIZE_0: usize =
            ::core::mem::size_of::<<u32 as zerovec::ule::AsULE>::ULE>();
        const OFFSET_0: usize = ZERO + SIZE_0;
        if let Some(bytes) = bytes_one.get(ZERO..ZERO + SIZE_0) {
            <<u32 as zerovec::ule::AsULE>::ULE as
                        zerovec::ule::ULE>::validate_bytes(bytes)?;
        } else { return Err(zerovec::ule::UleError::parse::<Self>()); };
        <zerovec::ZeroSlice<PotentialCodePoint> as
                    zerovec::ule::VarULE>::validate_bytes(last_field_bytes)?;
        Ok(())
    }
    #[inline]
    #[allow(clippy :: transmute_undefined_repr, reason =
    "This is the only way to do this until ptr metadata stabilizes")]
    unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
        #[repr(C)]
        struct FatPtr {
            ptr: usize,
            metadata: usize,
        }
        const {
                if !(::core::mem::size_of::<FatPtr>() ==
                            ::core::mem::size_of::<&zerovec::ZeroSlice<PotentialCodePoint>>())
                    {
                    ::core::panicking::panic("assertion failed: ::core::mem::size_of::<FatPtr>() ==\n    ::core::mem::size_of::<&zerovec::ZeroSlice<PotentialCodePoint>>()")
                };
                if !(::core::mem::align_of::<FatPtr>() ==
                            ::core::mem::align_of::<&zerovec::ZeroSlice<PotentialCodePoint>>())
                    {
                    ::core::panicking::panic("assertion failed: ::core::mem::align_of::<FatPtr>() ==\n    ::core::mem::align_of::<&zerovec::ZeroSlice<PotentialCodePoint>>()")
                };
            }
        let unsized_bytes =
            bytes.get_unchecked(__IMPL_VarULE_FOR_CodePointInversionListULE_ULE_SIZE..);
        let unsized_ref =
            <zerovec::ZeroSlice<PotentialCodePoint> as
                    zerovec::ule::VarULE>::from_bytes_unchecked(unsized_bytes);
        let FatPtr { metadata, .. } =
            ::core::mem::transmute::<&zerovec::ZeroSlice<PotentialCodePoint>,
                    FatPtr>(unsized_ref);
        let entire_struct_as_slice: *const [u8] =
            ::core::ptr::slice_from_raw_parts(bytes.as_ptr(), metadata);
        &*(entire_struct_as_slice as *const Self)
    }
}
impl core::cmp::PartialEq for CodePointInversionListULE {
    fn eq(&self, other: &Self) -> bool {
        <Self as zerovec::ule::VarULE>::as_bytes(&self) ==
            <Self as zerovec::ule::VarULE>::as_bytes(&other)
    }
}
impl core::cmp::Eq for CodePointInversionListULE {}
impl core::fmt::Debug for CodePointInversionListULE {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let this =
            <CodePointInversionList as
                    zerovec::__zerovec_internal_reexport::ZeroFrom<CodePointInversionListULE>>::zero_from(self);
        <CodePointInversionList as core::fmt::Debug>::fmt(&this, f)
    }
}#[zerovec::make_varule(CodePointInversionListULE)]
28#[zerovec::skip_derive(Ord)]
29#[zerovec::derive(Debug)]
30#[derive(#[automatically_derived]impl<'data> ::core::fmt::Debug for
    CodePointInversionList<'data> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "CodePointInversionList inv_list size", &self.inv_list,
            &&self.size)
    }
}Debug, #[automatically_derived]impl<'data> ::core::cmp::Eq for
    CodePointInversionList<'data> {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _:
                ::core::cmp::AssertParamIsEq<ZeroVec<'data,
                PotentialCodePoint>>;
        let _: ::core::cmp::AssertParamIsEq<u32>;
    }
}Eq, #[automatically_derived]impl<'data> ::core::marker::StructuralPartialEq for
    CodePointInversionList<'data> {
}
#[automatically_derived]impl<'data> ::core::cmp::PartialEq for
    CodePointInversionList<'data> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.size == other.size && self.inv_list == other.inv_list
    }
}PartialEq, #[automatically_derived]impl<'data> ::core::clone::Clone for
    CodePointInversionList<'data> {
    #[inline]
    fn clone(&self) -> Self {
        CodePointInversionList {
            inv_list: ::core::clone::Clone::clone(&self.inv_list),
            size: ::core::clone::Clone::clone(&self.size),
        }
    }
}Clone, unsafe impl<'yoke> yoke::Yokeable<'yoke> for CodePointInversionList<'static>
    where  {
    type Output = CodePointInversionList<'yoke>;
    #[inline]
    fn transform(&'yoke self) -> &'yoke Self::Output {
        if false { let _: *const &'yoke Self::Output = &raw const self; }
        self
    }
    #[inline]
    fn transform_owned(self) -> Self::Output { self }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        ::core::mem::transmute::<Self::Output, Self>(from)
    }
    #[inline]
    fn transform_mut<F>(&'yoke mut self, f: F) where F: 'static +
        for<'_yoke> FnOnce(&'_yoke mut Self::Output) {
        let y = unsafe { &mut *(self as *mut Self as *mut Self::Output) };
        f(y)
    }
}Yokeable, impl<'zf, 'zf_inner>
    zerofrom::ZeroFrom<'zf, CodePointInversionList<'zf_inner>> for
    CodePointInversionList<'zf> where  {
    fn zero_from(this: &'zf CodePointInversionList<'zf_inner>) -> Self {
        match *this {
            CodePointInversionList {
                inv_list: ref __binding_0, size: ref __binding_1 } => {
                CodePointInversionList {
                    inv_list: <ZeroVec<'zf, PotentialCodePoint> as
                            zerofrom::ZeroFrom<'zf,
                            ZeroVec<'zf_inner,
                            PotentialCodePoint>>>::zero_from(__binding_0),
                    size: *__binding_1,
                }
            }
        }
    }
}ZeroFrom, #[automatically_derived]impl<'data> ::core::hash::Hash for
    CodePointInversionList<'data> {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.inv_list, state);
        ::core::hash::Hash::hash(&self.size, state)
    }
}Hash)]
31#[cfg_attr(not(feature = "alloc"), zerovec::skip_derive(ZeroMapKV, ToOwned))]
32pub struct CodePointInversionList<'data> {
33    // If we wanted to use an array to keep the memory on the stack, there is an unsafe nightly feature
34    // https://doc.rust-lang.org/nightly/core/array/trait.FixedSizeArray.html
35    // Allows for traits of fixed size arrays
36
37    // Implements an [inversion list.](https://en.wikipedia.org/wiki/Inversion_list)
38    inv_list: ZeroVec<'data, PotentialCodePoint>,
39    size: u32,
40}
41
42#[cfg(feature = "serde")]
43impl<'de: 'a, 'a> serde::Deserialize<'de> for CodePointInversionList<'a> {
44    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
45    where
46        D: serde::Deserializer<'de>,
47    {
48        use serde::de::Error;
49
50        let parsed_inv_list = if deserializer.is_human_readable() {
51            let parsed_strings = Vec::<alloc::borrow::Cow<'de, str>>::deserialize(deserializer)?;
52            let mut inv_list = ZeroVec::new_owned(Vec::with_capacity(parsed_strings.len() * 2));
53            for range in parsed_strings {
54                fn internal(range: &str) -> Option<(u32, u32)> {
55                    let (start, range) = UnicodeCodePoint::parse(range)?;
56                    if range.is_empty() {
57                        return Some((start.0, start.0));
58                    }
59                    let (hyphen, range) = UnicodeCodePoint::parse(range)?;
60                    if hyphen.0 != '-' as u32 {
61                        return None;
62                    }
63                    let (end, range) = UnicodeCodePoint::parse(range)?;
64                    range.is_empty().then_some((start.0, end.0))
65                }
66                let (start, end) = internal(&range).ok_or_else(|| Error::custom(format!(
67                    "Cannot deserialize invalid inversion list for CodePointInversionList: {range:?}"
68                )))?;
69                inv_list.with_mut(|v| {
70                    v.push(PotentialCodePoint::from_u24(start).to_unaligned());
71                    v.push(PotentialCodePoint::from_u24(end + 1).to_unaligned());
72                });
73            }
74            inv_list
75        } else {
76            ZeroVec::<PotentialCodePoint>::deserialize(deserializer)?
77        };
78        CodePointInversionList::try_from_inversion_list(parsed_inv_list).map_err(|e| {
79            Error::custom(format!(
80                "Cannot deserialize invalid inversion list for CodePointInversionList: {e:?}"
81            ))
82        })
83    }
84}
85
86#[cfg(feature = "databake")]
87impl databake::Bake for CodePointInversionList<'_> {
88    fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
89        env.insert("icu_collections");
90        let inv_list = self.inv_list.bake(env);
91        let size = self.size.bake(env);
92        // Safe because our parts are safe.
93        databake::quote! { unsafe {
94            #[allow(unused_unsafe)]
95            icu_collections::codepointinvlist::CodePointInversionList::from_parts_unchecked(#inv_list, #size)
96        }}
97    }
98}
99
100#[cfg(feature = "databake")]
101impl databake::BakeSize for CodePointInversionList<'_> {
102    fn borrows_size(&self) -> usize {
103        self.inv_list.borrows_size()
104    }
105}
106
107#[cfg(feature = "serde")]
108#[derive(Debug, Copy, Clone)]
109struct UnicodeCodePoint(u32);
110
111#[cfg(feature = "serde")]
112impl UnicodeCodePoint {
113    fn from_u32(cp: u32) -> Result<Self, String> {
114        if cp < SENTINEL {
115            Ok(Self(cp))
116        } else {
117            Err(format!("Not a Unicode code point {cp}"))
118        }
119    }
120
121    fn parse(value: &str) -> Option<(Self, &str)> {
122        Some(if let Some(hex) = value.strip_prefix("U+") {
123            let (escape, remainder) = (hex.get(..4)?, hex.get(4..)?);
124            (Self(u32::from_str_radix(escape, 16).ok()?), remainder)
125        } else {
126            let c = value.chars().next()?;
127            (Self(c as u32), value.get(c.len_utf8()..)?)
128        })
129    }
130}
131
132#[cfg(feature = "serde")]
133impl core::fmt::Display for UnicodeCodePoint {
134    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135        match self.0 {
136            s @ 0xD800..=0xDFFF => write!(f, "U+{s:X}"),
137            // char should be in range by construction but this code is not so performance-sensitive
138            // so we just use the replacement character
139            c => write!(
140                f,
141                "{}",
142                char::from_u32(c).unwrap_or(char::REPLACEMENT_CHARACTER)
143            ),
144        }
145    }
146}
147
148#[cfg(feature = "serde")]
149impl serde::Serialize for CodePointInversionList<'_> {
150    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151    where
152        S: serde::Serializer,
153    {
154        if serializer.is_human_readable() {
155            use serde::ser::Error;
156            use serde::ser::SerializeSeq;
157            let mut seq = serializer.serialize_seq(Some(self.inv_list.len() / 2))?;
158            for range in self.iter_ranges() {
159                let start = UnicodeCodePoint::from_u32(*range.start()).map_err(S::Error::custom)?;
160                if range.start() == range.end() {
161                    seq.serialize_element(&format!("{start}"))?;
162                } else {
163                    let end = UnicodeCodePoint::from_u32(*range.end()).map_err(S::Error::custom)?;
164                    seq.serialize_element(&format!("{start}-{end}",))?;
165                }
166            }
167            seq.end()
168        } else {
169            // Note: serde(flatten) currently does not promote a struct field of type Vec
170            // to replace the struct when serializing. The error message from the default
171            // serialization is: "can only flatten structs and maps (got a sequence)".
172            self.inv_list.serialize(serializer)
173        }
174    }
175}
176
177impl<'data> CodePointInversionList<'data> {
178    /// Returns a new [`CodePointInversionList`] from an [inversion list](https://en.wikipedia.org/wiki/Inversion_list)
179    /// represented as a [`ZeroVec`]`<`[`PotentialCodePoint`]`>` of code points.
180    ///
181    /// The inversion list must be of even length, sorted ascending non-overlapping,
182    /// and within the bounds of `0x0 -> 0x10FFFF` inclusive, and end points being exclusive.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use icu::collections::codepointinvlist::CodePointInversionList;
188    /// use icu::collections::codepointinvlist::InvalidSetError;
189    /// use potential_utf::PotentialCodePoint;
190    /// use zerovec::ZeroVec;
191    ///
192    /// let valid = [0x0, 0x10000];
193    /// let inv_list: ZeroVec<PotentialCodePoint> = valid
194    ///     .into_iter()
195    ///     .map(PotentialCodePoint::from_u24)
196    ///     .collect();
197    /// let result = CodePointInversionList::try_from_inversion_list(inv_list);
198    /// assert!(matches!(result, CodePointInversionList));
199    ///
200    /// let invalid = vec![0x0, 0x80, 0x3];
201    /// let inv_list: ZeroVec<PotentialCodePoint> = invalid
202    ///     .iter()
203    ///     .copied()
204    ///     .map(PotentialCodePoint::from_u24)
205    ///     .collect();
206    /// let result = CodePointInversionList::try_from_inversion_list(inv_list);
207    /// assert!(matches!(result, Err(InvalidSetError(_))));
208    /// if let Err(InvalidSetError(actual)) = result {
209    ///     assert_eq!(
210    ///         &invalid,
211    ///         &actual.into_iter().map(u32::from).collect::<Vec<_>>()
212    ///     );
213    /// }
214    /// ```
215    pub fn try_from_inversion_list(
216        inv_list: ZeroVec<'data, PotentialCodePoint>,
217    ) -> Result<Self, InvalidSetError> {
218        #[expect(clippy::indexing_slicing)] // chunks
219        if is_valid_zv(&inv_list) {
220            let size = inv_list
221                .as_ule_slice()
222                .chunks(2)
223                .map(|end_points| {
224                    u32::from(<PotentialCodePoint as AsULE>::from_unaligned(end_points[1]))
225                        - u32::from(<PotentialCodePoint as AsULE>::from_unaligned(end_points[0]))
226                })
227                .sum::<u32>();
228            Ok(Self { inv_list, size })
229        } else {
230            Err(InvalidSetError(
231                #[cfg(feature = "alloc")]
232                inv_list.to_vec(),
233            ))
234        }
235    }
236
237    /// Safety: no actual safety invariants, however has correctness invariants
238    #[doc(hidden)] // databake internal
239    pub const unsafe fn from_parts_unchecked(
240        inv_list: ZeroVec<'data, PotentialCodePoint>,
241        size: u32,
242    ) -> Self {
243        Self { inv_list, size }
244    }
245
246    /// Returns a new, fully-owned [`CodePointInversionList`] by cloning an [inversion list](https://en.wikipedia.org/wiki/Inversion_list)
247    /// represented as a slice of [`PotentialCodePoint`] code points.
248    ///
249    /// The inversion list must be of even length, sorted ascending non-overlapping,
250    /// and within the bounds of `0x0 -> 0x10FFFF` inclusive, and end points being exclusive.
251    ///
252    /// ✨ *Enabled with the `alloc` Cargo feature.*
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use icu::collections::codepointinvlist::CodePointInversionList;
258    ///
259    /// let bmp_list = &[0x0, 0x10000];
260    /// let smp_list = &[0x10000, 0x20000];
261    /// let sip_list = &[0x20000, 0x30000];
262    ///
263    /// let lists: Vec<CodePointInversionList> =
264    ///     [&bmp_list[..], smp_list, sip_list]
265    ///         .into_iter()
266    ///         .map(|l| {
267    ///             CodePointInversionList::try_from_u32_inversion_list_slice(l)
268    ///                 .unwrap()
269    ///         })
270    ///         .collect();
271    ///
272    /// let bmp = &lists[0];
273    /// assert!(bmp.contains32(0xFFFF));
274    /// assert!(!bmp.contains32(0x10000));
275    ///
276    /// assert!(!lists.iter().any(|set| set.contains32(0x40000)));
277    /// ```
278    #[cfg(feature = "alloc")]
279    pub fn try_from_u32_inversion_list_slice(inv_list: &[u32]) -> Result<Self, InvalidSetError> {
280        let inv_list_zv: ZeroVec<PotentialCodePoint> = inv_list
281            .iter()
282            .copied()
283            .map(PotentialCodePoint::from_u24)
284            .collect();
285        CodePointInversionList::try_from_inversion_list(inv_list_zv)
286    }
287
288    /// Attempts to convert this list into a fully-owned one. No-op if already fully owned
289    ///
290    /// ✨ *Enabled with the `alloc` Cargo feature.*
291    #[cfg(feature = "alloc")]
292    pub fn into_owned(self) -> CodePointInversionList<'static> {
293        CodePointInversionList {
294            inv_list: self.inv_list.into_owned(),
295            size: self.size,
296        }
297    }
298
299    /// Returns an owned inversion list representing the current [`CodePointInversionList`]
300    ///
301    /// ✨ *Enabled with the `alloc` Cargo feature.*
302    #[cfg(feature = "alloc")]
303    pub fn get_inversion_list_vec(&self) -> Vec<u32> {
304        self.as_inversion_list().iter().map(u32::from).collect()
305    }
306
307    /// Returns [`CodePointInversionList`] spanning entire Unicode range
308    ///
309    /// The range spans from `0x0 -> 0x10FFFF` inclusive.
310    ///  
311    /// # Examples
312    ///
313    /// ```
314    /// use icu::collections::codepointinvlist::CodePointInversionList;
315    ///
316    /// let expected = [0x0, (char::MAX as u32) + 1];
317    /// assert_eq!(
318    ///     CodePointInversionList::all().get_inversion_list_vec(),
319    ///     expected
320    /// );
321    /// assert_eq!(
322    ///     CodePointInversionList::all().size(),
323    ///     (expected[1] - expected[0]) as usize
324    /// );
325    /// ```
326    pub const fn all() -> Self {
327        Self {
328            inv_list: ::zerovec::ZeroSlice::<PotentialCodePoint>::from_ule_slice(const {
                &[PotentialCodePoint::to_unaligned(PotentialCodePoint::from_u24(0)),
                            PotentialCodePoint::to_unaligned(PotentialCodePoint::from_u24(SENTINEL))]
            }).as_zerovec()zerovec!(PotentialCodePoint; PotentialCodePoint::to_unaligned; [PotentialCodePoint::from_u24(0), PotentialCodePoint::from_u24(SENTINEL)]),
329            size: SENTINEL,
330        }
331    }
332
333    /// Returns [`CodePointInversionList`] spanning BMP range
334    ///
335    /// The range spans from `0x0 -> 0xFFFF` inclusive.
336    ///
337    /// # Examples
338    ///
339    /// ```
340    /// use icu::collections::codepointinvlist::CodePointInversionList;
341    ///
342    /// const BMP_MAX: u32 = 0xFFFF;
343    ///
344    /// let expected = [0x0, BMP_MAX + 1];
345    /// assert_eq!(
346    ///     CodePointInversionList::bmp().get_inversion_list_vec(),
347    ///     expected
348    /// );
349    /// assert_eq!(
350    ///     CodePointInversionList::bmp().size(),
351    ///     (expected[1] - expected[0]) as usize
352    /// );
353    /// ```
354    pub fn bmp() -> Self {
355        /// The exclusive end value of the Basic Multilingual Plane, i.e. the first code point in plane 2.
356        const BMP_SENTINEL: u32 = 0x10000;
357
358        Self {
359            inv_list: ::zerovec::ZeroSlice::<PotentialCodePoint>::from_ule_slice(const {
                &[PotentialCodePoint::to_unaligned(PotentialCodePoint::from_u24(0)),
                            PotentialCodePoint::to_unaligned(PotentialCodePoint::from_u24(BMP_SENTINEL))]
            }).as_zerovec()zerovec!(PotentialCodePoint; PotentialCodePoint::to_unaligned; [PotentialCodePoint::from_u24(0), PotentialCodePoint::from_u24(BMP_SENTINEL)]),
360            size: BMP_SENTINEL,
361        }
362    }
363
364    /// Returns the inversion list as a slice
365    ///
366    /// Public only to the crate, not exposed to public
367    #[cfg(feature = "alloc")]
368    pub(crate) fn as_inversion_list(&self) -> &ZeroVec<'_, PotentialCodePoint> {
369        &self.inv_list
370    }
371
372    /// Yields an [`Iterator`] going through the character set in the [`CodePointInversionList`]
373    ///
374    /// # Examples
375    ///
376    /// ```
377    /// use icu::collections::codepointinvlist::CodePointInversionList;
378    /// let example_list = [0x41, 0x44, 0x45, 0x46];
379    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
380    ///     &example_list,
381    /// )
382    /// .unwrap();
383    /// let mut ex_iter_chars = example.iter_chars();
384    /// assert_eq!(Some('A'), ex_iter_chars.next());
385    /// assert_eq!(Some('B'), ex_iter_chars.next());
386    /// assert_eq!(Some('C'), ex_iter_chars.next());
387    /// assert_eq!(Some('E'), ex_iter_chars.next());
388    /// assert_eq!(None, ex_iter_chars.next());
389    /// ```
390    pub fn iter_chars(&self) -> impl Iterator<Item = char> + '_ {
391        #[expect(clippy::indexing_slicing)] // chunks
392        self.inv_list
393            .as_ule_slice()
394            .chunks(2)
395            .flat_map(|pair| {
396                u32::from(PotentialCodePoint::from_unaligned(pair[0]))
397                    ..u32::from(PotentialCodePoint::from_unaligned(pair[1]))
398            })
399            .filter_map(char::from_u32)
400    }
401
402    /// Yields an [`Iterator`] returning the ranges of the code points that are
403    /// included in the [`CodePointInversionList`]
404    ///
405    /// Ranges are returned as [`RangeInclusive`], which is inclusive of its
406    /// `end` bound value. An end-inclusive behavior matches the ICU4C/J
407    /// behavior of ranges, ex: `CodePointInversionList::contains(UChar32 start, UChar32 end)`.
408    ///
409    /// # Example
410    ///
411    /// ```
412    /// use icu::collections::codepointinvlist::CodePointInversionList;
413    /// let example_list = [0x41, 0x44, 0x45, 0x46];
414    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
415    ///     &example_list,
416    /// )
417    /// .unwrap();
418    /// let mut example_iter_ranges = example.iter_ranges();
419    /// assert_eq!(Some(0x41..=0x43), example_iter_ranges.next());
420    /// assert_eq!(Some(0x45..=0x45), example_iter_ranges.next());
421    /// assert_eq!(None, example_iter_ranges.next());
422    /// ```
423    pub fn iter_ranges(&self) -> impl ExactSizeIterator<Item = RangeInclusive<u32>> + '_ {
424        #[expect(clippy::indexing_slicing)] // chunks
425        self.inv_list.as_ule_slice().chunks(2).map(|pair| {
426            let range_start = u32::from(PotentialCodePoint::from_unaligned(pair[0]));
427            let range_limit = u32::from(PotentialCodePoint::from_unaligned(pair[1]));
428            range_start..=(range_limit - 1)
429        })
430    }
431
432    /// Yields an [`Iterator`] returning the ranges of the code points that are
433    /// *not* included in the [`CodePointInversionList`]
434    ///
435    /// Ranges are returned as [`RangeInclusive`], which is inclusive of its
436    /// `end` bound value. An end-inclusive behavior matches the ICU4C/J
437    /// behavior of ranges, ex: `CodePointInversionList::contains(UChar32 start, UChar32 end)`.
438    ///
439    /// # Example
440    ///
441    /// ```
442    /// use icu::collections::codepointinvlist::CodePointInversionList;
443    /// let example_list = [0x41, 0x44, 0x45, 0x46];
444    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
445    ///     &example_list,
446    /// )
447    /// .unwrap();
448    /// let mut example_iter_ranges = example.iter_ranges_complemented();
449    /// assert_eq!(Some(0..=0x40), example_iter_ranges.next());
450    /// assert_eq!(Some(0x44..=0x44), example_iter_ranges.next());
451    /// assert_eq!(Some(0x46..=char::MAX as u32), example_iter_ranges.next());
452    /// assert_eq!(None, example_iter_ranges.next());
453    /// ```
454    pub fn iter_ranges_complemented(&self) -> impl Iterator<Item = RangeInclusive<u32>> + '_ {
455        let inv_ule = self.inv_list.as_ule_slice();
456        let middle = inv_ule.get(1..inv_ule.len() - 1).unwrap_or(&[]);
457        let beginning = if let Some(first) = self.inv_list.first() {
458            let first = u32::from(first);
459            if first == 0 {
460                None
461            } else {
462                Some(0..=first - 1)
463            }
464        } else {
465            None
466        };
467        let end = if let Some(last) = self.inv_list.last() {
468            let last = u32::from(last);
469            if last == SENTINEL {
470                None
471            } else {
472                Some(last..=char::MAX as u32)
473            }
474        } else {
475            None
476        };
477        #[expect(clippy::indexing_slicing)] // chunks
478        let chunks = middle.chunks(2).map(|pair| {
479            let range_start = u32::from(PotentialCodePoint::from_unaligned(pair[0]));
480            let range_limit = u32::from(PotentialCodePoint::from_unaligned(pair[1]));
481            range_start..=(range_limit - 1)
482        });
483        beginning.into_iter().chain(chunks).chain(end)
484    }
485
486    /// Returns the number of ranges contained in this [`CodePointInversionList`]
487    pub fn get_range_count(&self) -> usize {
488        self.inv_list.len() / 2
489    }
490
491    /// Returns a specific range contained in this [`CodePointInversionList`] by index.
492    /// Intended for use in FFI.
493    pub fn get_nth_range(&self, idx: usize) -> Option<RangeInclusive<u32>> {
494        let start_idx = idx * 2;
495        let end_idx = start_idx + 1;
496        let start = u32::from(self.inv_list.get(start_idx)?);
497        let end = u32::from(self.inv_list.get(end_idx)?);
498        Some(start..=(end - 1))
499    }
500
501    /// Returns the number of elements of the [`CodePointInversionList`]
502    pub fn size(&self) -> usize {
503        if self.is_empty() {
504            return 0;
505        }
506        self.size as usize
507    }
508
509    /// Returns whether or not the [`CodePointInversionList`] is empty
510    pub fn is_empty(&self) -> bool {
511        self.inv_list.is_empty()
512    }
513
514    /// Wrapper for contains
515    ///
516    /// Returns an [`Option`] as to whether or not it is possible for the query to be contained.
517    /// The value in the [`Option`] is the start index of the range that contains the query.
518    fn contains_query(&self, query: u32) -> Option<usize> {
519        let query = PotentialCodePoint::try_from(query).ok()?;
520        match self.inv_list.binary_search(&query) {
521            Ok(pos) => {
522                if pos % 2 == 0 {
523                    Some(pos)
524                } else {
525                    None
526                }
527            }
528            Err(pos) => {
529                if pos % 2 != 0 && pos < self.inv_list.len() {
530                    Some(pos - 1)
531                } else {
532                    None
533                }
534            }
535        }
536    }
537
538    /// Checks to see the query is in the [`CodePointInversionList`]
539    ///
540    /// Runs a binary search in `O(log(n))` where `n` is the number of start and end points
541    /// in the set using [`core`] implementation
542    ///
543    /// # Examples
544    ///
545    /// ```
546    /// use icu::collections::codepointinvlist::CodePointInversionList;
547    /// let example_list = [0x41, 0x43, 0x44, 0x45];
548    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
549    ///     &example_list,
550    /// )
551    /// .unwrap();
552    /// assert!(example.contains('A'));
553    /// assert!(!example.contains('C'));
554    /// ```
555    pub fn contains(&self, query: char) -> bool {
556        self.contains_query(query as u32).is_some()
557    }
558
559    /// Checks to see the unsigned int is in the [`CodePointInversionList::all()`](CodePointInversionList::all())
560    ///
561    /// Note: Even though [`u32`] and [`prim@char`] in Rust are non-negative 4-byte
562    /// values, there is an important difference. A [`u32`] can take values up to
563    /// a very large integer value, while a [`prim@char`] in Rust is defined to be in
564    /// the range from 0 to the maximum valid Unicode Scalar Value.
565    ///
566    /// Runs a binary search in `O(log(n))` where `n` is the number of start and end points
567    /// in the set using [`core`] implementation
568    ///
569    /// # Examples
570    ///
571    /// ```
572    /// use icu::collections::codepointinvlist::CodePointInversionList;
573    /// let example_list = [0x41, 0x43, 0x44, 0x45];
574    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
575    ///     &example_list,
576    /// )
577    /// .unwrap();
578    /// assert!(example.contains32(0x41));
579    /// assert!(!example.contains32(0x43));
580    /// ```
581    pub fn contains32(&self, query: u32) -> bool {
582        self.contains_query(query).is_some()
583    }
584
585    /// Returns the index of `query` in the [`CodePointInversionList`], or `None` if it is not contained.
586    ///
587    /// # Examples
588    ///
589    /// ```
590    /// use icu::collections::codepointinvlist::CodePointInversionList;
591    /// let example_list = [0x41, 0x43, 0x44, 0x45];
592    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
593    ///     &example_list,
594    /// )
595    /// .unwrap();
596    /// assert_eq!(example.position('A'), Some(0));
597    /// assert_eq!(example.position('B'), Some(1));
598    /// assert_eq!(example.position('C'), None);
599    /// assert_eq!(example.position('D'), Some(2));
600    /// ```
601    #[inline]
602    pub fn position(&self, query: char) -> Option<usize> {
603        self.position32(query as u32)
604    }
605
606    /// Returns the index of `query` in the [`CodePointInversionList`], or `None` if it is not contained.
607    ///
608    /// See [`Self::position`].
609    #[inline]
610    pub fn position32(&self, query: u32) -> Option<usize> {
611        let mut offset = 0;
612        for r in self.iter_ranges() {
613            if query < *r.start() {
614                return None;
615            }
616            if query <= *r.end() {
617                return Some((offset + (query - r.start())) as usize);
618            }
619            offset += r.end() - r.start() + 1;
620        }
621        None
622    }
623
624    /// Checks to see if the range is in the [`CodePointInversionList`]
625    ///
626    /// Runs a binary search in `O(log(n))` where `n` is the number of start and end points
627    /// in the set using [`Vec`] implementation. Only runs the search once on the `start`
628    /// parameter, while the `end` parameter is checked in a single `O(1)` step.
629    ///
630    /// # Examples
631    ///
632    /// ```
633    /// use icu::collections::codepointinvlist::CodePointInversionList;
634    /// let example_list = [0x41, 0x43, 0x44, 0x45];
635    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
636    ///     &example_list,
637    /// )
638    /// .unwrap();
639    /// assert!(example.contains_range('A'..'C'));
640    /// assert!(example.contains_range('A'..='B'));
641    /// assert!(!example.contains_range('A'..='C'));
642    /// ```
643    ///
644    /// Surrogate points (`0xD800 -> 0xDFFF`) will return [`false`] if the Range contains them but the
645    /// [`CodePointInversionList`] does not.
646    ///
647    /// Note: when comparing to ICU4C/J, keep in mind that `Range`s in Rust are
648    /// constructed inclusive of start boundary and exclusive of end boundary.
649    /// The ICU4C/J `CodePointInversionList::contains(UChar32 start, UChar32 end)` method
650    /// differs by including the end boundary.
651    ///
652    /// # Examples
653    ///
654    /// ```
655    /// use icu::collections::codepointinvlist::CodePointInversionList;
656    /// let check =
657    ///     char::from_u32(0xD7FE).unwrap()..char::from_u32(0xE001).unwrap();
658    /// let example_list = [0xD7FE, 0xD7FF, 0xE000, 0xE001];
659    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
660    ///     &example_list,
661    /// )
662    /// .unwrap();
663    /// assert!(!example.contains_range(check));
664    /// ```
665    pub fn contains_range(&self, range: impl RangeBounds<char>) -> bool {
666        let (from, till) = deconstruct_range(range);
667        if from >= till {
668            return false;
669        }
670        match self.contains_query(from) {
671            Some(pos) => {
672                if let Some(x) = self.inv_list.get(pos + 1) {
673                    (till) <= x.into()
674                } else {
675                    if true {
    if !false {
        {
            ::core::panicking::panic_fmt(format_args!("Inversion list query should not return out of bounds index"));
        }
    };
};debug_assert!(
676                        false,
677                        "Inversion list query should not return out of bounds index"
678                    );
679                    false
680                }
681            }
682            None => false,
683        }
684    }
685
686    /// Check if the calling [`CodePointInversionList`] contains all the characters of the given [`CodePointInversionList`]
687    ///
688    /// # Examples
689    ///
690    /// ```
691    /// use icu::collections::codepointinvlist::CodePointInversionList;
692    /// let example_list = [0x41, 0x46, 0x55, 0x5B]; // A - E, U - Z
693    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
694    ///     &example_list,
695    /// )
696    /// .unwrap();
697    /// let a_b_d = CodePointInversionList::try_from_u32_inversion_list_slice(&[
698    ///     0x41, 0x42, 0x44, 0x45,
699    /// ])
700    /// .unwrap();
701    /// let f_to_t = CodePointInversionList::try_from_u32_inversion_list_slice(&[
702    ///     0x46, 0x55,
703    /// ])
704    /// .unwrap();
705    /// let r_to_x = CodePointInversionList::try_from_u32_inversion_list_slice(&[
706    ///     0x52, 0x58,
707    /// ])
708    /// .unwrap();
709    /// assert!(example.contains_set(&a_b_d)); // contains all
710    /// assert!(!example.contains_set(&f_to_t)); // contains none
711    /// assert!(!example.contains_set(&r_to_x)); // contains some
712    /// ```
713    pub fn contains_set(&self, set: &Self) -> bool {
714        if set.size() > self.size() {
715            return false;
716        }
717
718        let mut set_ranges = set.iter_ranges();
719        let mut check_elem = set_ranges.next();
720
721        let ranges = self.iter_ranges();
722        for range in ranges {
723            loop {
724                let Some(ref check_range) = check_elem else {
725                    return true;
726                };
727                if check_range.start() >= range.start() && check_range.end() <= &(range.end() + 1) {
728                    check_elem = set_ranges.next();
729                } else {
730                    break;
731                }
732            }
733        }
734        check_elem.is_none()
735    }
736
737    /// Returns the end of the initial substring where the characters are either contained/not contained
738    /// in the set.
739    ///
740    /// # Examples
741    ///
742    /// ```
743    /// use icu::collections::codepointinvlist::CodePointInversionList;
744    /// let example_list = [0x41, 0x44]; // {A, B, C}
745    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
746    ///     &example_list,
747    /// )
748    /// .unwrap();
749    /// assert_eq!(example.span("CABXYZ", true), 3);
750    /// assert_eq!(example.span("XYZC", false), 3);
751    /// assert_eq!(example.span("XYZ", true), 0);
752    /// assert_eq!(example.span("ABC", false), 0);
753    /// ```
754    pub fn span(&self, span_str: &str, contained: bool) -> usize {
755        span_str
756            .chars()
757            .take_while(|&x| self.contains(x) == contained)
758            .count()
759    }
760
761    /// Returns the start of the trailing substring (starting from end of string) where the characters are
762    /// either contained/not contained in the set. Returns the length of the string if no valid return.
763    ///
764    /// # Examples
765    ///
766    /// ```
767    /// use icu::collections::codepointinvlist::CodePointInversionList;
768    /// let example_list = [0x41, 0x44]; // {A, B, C}
769    /// let example = CodePointInversionList::try_from_u32_inversion_list_slice(
770    ///     &example_list,
771    /// )
772    /// .unwrap();
773    /// assert_eq!(example.span_back("XYZCAB", true), 3);
774    /// assert_eq!(example.span_back("ABCXYZ", true), 6);
775    /// assert_eq!(example.span_back("CABXYZ", false), 3);
776    /// ```
777    pub fn span_back(&self, span_str: &str, contained: bool) -> usize {
778        span_str.len()
779            - span_str
780                .chars()
781                .rev()
782                .take_while(|&x| self.contains(x) == contained)
783                .count()
784    }
785}
786
787#[cfg(test)]
788mod tests {
789    use super::{CodePointInversionList, InvalidSetError};
790    use std::{char, vec::Vec};
791    use zerovec::ZeroVec;
792
793    #[test]
794    fn test_codepointinversionlist_try_from_vec() {
795        let ex = vec![0x2, 0x3, 0x4, 0x5];
796        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
797        assert_eq!(ex, check.get_inversion_list_vec());
798        assert_eq!(2, check.size());
799    }
800
801    #[test]
802    fn test_codepointinversionlist_try_from_vec_error() {
803        let check = vec![0x1, 0x1, 0x2, 0x3, 0x4];
804        let set = CodePointInversionList::try_from_u32_inversion_list_slice(&check);
805        assert!(matches!(set, Err(InvalidSetError(_))));
806        if let Err(InvalidSetError(actual)) = set {
807            assert_eq!(
808                &check,
809                &actual.into_iter().map(u32::from).collect::<Vec<_>>()
810            );
811        }
812    }
813
814    // CodePointInversionList membership functions
815    #[test]
816    fn test_codepointinversionlist_contains_query() {
817        let ex = vec![0x41, 0x46, 0x4B, 0x55];
818        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
819        assert!(check.contains_query(0x40).is_none());
820        assert_eq!(check.contains_query(0x41).unwrap(), 0);
821        assert_eq!(check.contains_query(0x44).unwrap(), 0);
822        assert!(check.contains_query(0x46).is_none());
823        assert_eq!(check.contains_query(0x4C).unwrap(), 2);
824        assert!(check.contains_query(0x56).is_none());
825    }
826
827    #[test]
828    fn test_codepointinversionlist_contains() {
829        let ex = vec![0x2, 0x5, 0xA, 0xF];
830        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
831        assert!(check.contains(0x2 as char));
832        assert!(check.contains(0x4 as char));
833        assert!(check.contains(0xA as char));
834        assert!(check.contains(0xE as char));
835    }
836
837    #[test]
838    fn test_codepointinversionlist_contains_false() {
839        let ex = vec![0x2, 0x5, 0xA, 0xF];
840        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
841        assert!(!check.contains(0x1 as char));
842        assert!(!check.contains(0x5 as char));
843        assert!(!check.contains(0x9 as char));
844        assert!(!check.contains(0xF as char));
845        assert!(!check.contains(0x10 as char));
846    }
847
848    #[test]
849    fn test_codepointinversionlist_contains_range() {
850        let ex = vec![0x41, 0x46, 0x4B, 0x55];
851        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
852        assert!(check.contains_range('A'..='E')); // 65 - 69
853        assert!(check.contains_range('C'..'D')); // 67 - 67
854        assert!(check.contains_range('L'..'P')); // 76 - 80
855        assert!(!check.contains_range('L'..='U')); // 76 - 85
856    }
857
858    #[test]
859    fn test_codepointinversionlist_contains_range_false() {
860        let ex = vec![0x41, 0x46, 0x4B, 0x55];
861        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
862        assert!(!check.contains_range('!'..'A')); // 33 - 65
863        assert!(!check.contains_range('F'..'K')); // 70 - 74
864        assert!(!check.contains_range('U'..)); // 85 - ..
865    }
866
867    #[test]
868    fn test_codepointinversionlist_contains_range_invalid() {
869        let check = CodePointInversionList::all();
870        assert!(!check.contains_range('A'..'!')); // 65 - 33
871        assert!(!check.contains_range('A'..'A')); // 65 - 65
872    }
873
874    #[test]
875    fn test_codepointinversionlist_contains_set_u() {
876        let ex = vec![0xA, 0x14, 0x28, 0x32, 0x46, 0x50, 0x64, 0x6E];
877        let u = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
878        let inside = vec![0xF, 0x14, 0x2C, 0x31, 0x46, 0x50, 0x64, 0x6D];
879        let s = CodePointInversionList::try_from_u32_inversion_list_slice(&inside).unwrap();
880        assert!(u.contains_set(&s));
881    }
882
883    #[test]
884    fn test_codepointinversionlist_contains_set_u_false() {
885        let ex = vec![0xA, 0x14, 0x28, 0x32, 0x46, 0x50, 0x64, 0x78];
886        let u = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
887        let outside = vec![0x0, 0xA, 0x16, 0x2C, 0x32, 0x46, 0x4F, 0x51, 0x6D, 0x6F];
888        let s = CodePointInversionList::try_from_u32_inversion_list_slice(&outside).unwrap();
889        assert!(!u.contains_set(&s));
890    }
891
892    #[test]
893    fn test_codepointinversionlist_size() {
894        let ex = vec![0x2, 0x5, 0xA, 0xF];
895        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
896        assert_eq!(8, check.size());
897        let check = CodePointInversionList::all();
898        let expected = (char::MAX as u32) + 1;
899        assert_eq!(expected as usize, check.size());
900        let inv_list_vec = vec![];
901        let check = CodePointInversionList {
902            inv_list: ZeroVec::from_slice_or_alloc(&inv_list_vec),
903            size: 0,
904        };
905        assert_eq!(check.size(), 0);
906    }
907
908    #[test]
909    fn test_codepointinversionlist_is_empty() {
910        let inv_list_vec = vec![];
911        let check = CodePointInversionList {
912            inv_list: ZeroVec::from_slice_or_alloc(&inv_list_vec),
913            size: 0,
914        };
915        assert!(check.is_empty());
916    }
917
918    #[test]
919    fn test_codepointinversionlist_is_not_empty() {
920        let check = CodePointInversionList::all();
921        assert!(!check.is_empty());
922    }
923
924    #[test]
925    fn test_codepointinversionlist_iter_chars() {
926        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
927        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
928        let mut iter = check.iter_chars();
929        assert_eq!(Some('A'), iter.next());
930        assert_eq!(Some('B'), iter.next());
931        assert_eq!(Some('C'), iter.next());
932        assert_eq!(Some('E'), iter.next());
933        assert_eq!(None, iter.next());
934    }
935
936    #[test]
937    fn test_codepointinversionlist_iter_ranges() {
938        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
939        let set = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
940        let mut ranges = set.iter_ranges();
941        assert_eq!(Some(0x41..=0x43), ranges.next());
942        assert_eq!(Some(0x45..=0x45), ranges.next());
943        assert_eq!(Some(0xD800..=0xD800), ranges.next());
944        assert_eq!(None, ranges.next());
945    }
946
947    #[test]
948    fn test_codepointinversionlist_iter_ranges_exactsizeiter_trait() {
949        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
950        let set = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
951        let ranges = set.iter_ranges();
952        assert_eq!(3, ranges.len());
953    }
954
955    #[test]
956    fn test_codepointinversionlist_range_count() {
957        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
958        let set = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
959        assert_eq!(3, set.get_range_count());
960    }
961
962    #[test]
963    fn test_codepointinversionlist_get_nth_range() {
964        let ex = vec![0x41, 0x44, 0x45, 0x46, 0xD800, 0xD801];
965        let set = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
966        assert_eq!(Some(0x41..=0x43), set.get_nth_range(0));
967        assert_eq!(Some(0x45..=0x45), set.get_nth_range(1));
968        assert_eq!(Some(0xD800..=0xD800), set.get_nth_range(2));
969        assert_eq!(None, set.get_nth_range(3));
970    }
971
972    // Range<char> cannot represent the upper bound (non-inclusive) for
973    // char::MAX, whereas Range<u32> can.
974    #[test]
975    fn test_codepointinversionlist_iter_ranges_with_max_code_point() {
976        let ex = vec![0x80, (char::MAX as u32) + 1];
977        let set = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
978        let mut ranges = set.iter_ranges();
979        assert_eq!(Some(0x80..=(char::MAX as u32)), ranges.next());
980        assert_eq!(None, ranges.next());
981    }
982
983    #[test]
984    fn test_codepointinversionlist_span_contains() {
985        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
986        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
987        assert_eq!(check.span("ABCDE", true), 3);
988        assert_eq!(check.span("E", true), 0);
989    }
990
991    #[test]
992    fn test_codepointinversionlist_span_does_not_contain() {
993        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
994        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
995        assert_eq!(check.span("DEF", false), 2);
996        assert_eq!(check.span("KLMA", false), 3);
997    }
998
999    #[test]
1000    fn test_codepointinversionlist_span_back_contains() {
1001        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
1002        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
1003        assert_eq!(check.span_back("XYZABFH", true), 3);
1004        assert_eq!(check.span_back("ABCXYZ", true), 6);
1005    }
1006
1007    #[test]
1008    fn test_codepointinversionlist_span_back_does_not_contain() {
1009        let ex = vec![0x41, 0x44, 0x46, 0x4B]; // A - D, F - K
1010        let check = CodePointInversionList::try_from_u32_inversion_list_slice(&ex).unwrap();
1011        assert_eq!(check.span_back("ABCXYZ", false), 3);
1012        assert_eq!(check.span_back("XYZABC", false), 6);
1013    }
1014
1015    #[test]
1016    fn test_uniset_to_inv_list() {
1017        let inv_list = [
1018            0x9, 0xE, 0x20, 0x21, 0x85, 0x86, 0xA0, 0xA1, 0x1626, 0x1627, 0x2000, 0x2003, 0x2028,
1019            0x202A, 0x202F, 0x2030, 0x205F, 0x2060, 0x3000, 0x3001,
1020        ];
1021        let s: CodePointInversionList =
1022            CodePointInversionList::try_from_u32_inversion_list_slice(&inv_list).unwrap();
1023        let round_trip_inv_list = s.get_inversion_list_vec();
1024        assert_eq!(
1025            round_trip_inv_list.into_iter().collect::<ZeroVec<u32>>(),
1026            inv_list
1027        );
1028    }
1029
1030    #[test]
1031    fn test_serde_serialize() {
1032        let inv_list = [0x41, 0x46, 0x4B, 0x55];
1033        let uniset = CodePointInversionList::try_from_u32_inversion_list_slice(&inv_list).unwrap();
1034        let json_str = serde_json::to_string(&uniset).unwrap();
1035        assert_eq!(json_str, r#"["A-E","K-T"]"#);
1036    }
1037
1038    #[test]
1039    fn test_serde_serialize_surrogates() {
1040        let inv_list = [0xDFAB, 0xDFFF];
1041        let uniset = CodePointInversionList::try_from_u32_inversion_list_slice(&inv_list).unwrap();
1042        let json_str = serde_json::to_string(&uniset).unwrap();
1043        assert_eq!(json_str, r#"["U+DFAB-U+DFFE"]"#);
1044    }
1045
1046    #[test]
1047    fn test_serde_deserialize() {
1048        let inv_list_str = r#"["A-E","K-T"]"#;
1049        let exp_inv_list = [0x41, 0x46, 0x4B, 0x55];
1050        let exp_uniset =
1051            CodePointInversionList::try_from_u32_inversion_list_slice(&exp_inv_list).unwrap();
1052        let act_uniset: CodePointInversionList = serde_json::from_str(inv_list_str).unwrap();
1053        assert_eq!(act_uniset, exp_uniset);
1054    }
1055
1056    #[test]
1057    fn test_serde_deserialize_surrogates() {
1058        let inv_list_str = r#"["U+DFAB-U+DFFE"]"#;
1059        let exp_inv_list = [0xDFAB, 0xDFFF];
1060        let exp_uniset =
1061            CodePointInversionList::try_from_u32_inversion_list_slice(&exp_inv_list).unwrap();
1062        let act_uniset: CodePointInversionList = serde_json::from_str(inv_list_str).unwrap();
1063        assert_eq!(act_uniset, exp_uniset);
1064    }
1065
1066    #[test]
1067    fn test_serde_deserialize_invalid() {
1068        assert!(serde_json::from_str::<CodePointInversionList>("[65,70,98775,85]").is_err());
1069        assert!(serde_json::from_str::<CodePointInversionList>("[65,70,U+FFFFFFFFFF,85]").is_err());
1070    }
1071
1072    #[test]
1073    fn test_serde_with_postcard_roundtrip() -> Result<(), postcard::Error> {
1074        let set = CodePointInversionList::bmp();
1075        let set_serialized: Vec<u8> = postcard::to_allocvec(&set).unwrap();
1076        let set_deserialized: CodePointInversionList =
1077            postcard::from_bytes::<CodePointInversionList>(&set_serialized)?;
1078
1079        assert_eq!(&set, &set_deserialized);
1080        assert!(!set_deserialized.inv_list.is_owned());
1081
1082        Ok(())
1083    }
1084
1085    #[test]
1086    fn databake() {
1087        databake::test_bake!(
1088            CodePointInversionList<'static>,
1089            const,
1090            unsafe {
1091                #[allow(unused_unsafe)]
1092                crate::codepointinvlist::CodePointInversionList::from_parts_unchecked(
1093                    unsafe {
1094                        zerovec::ZeroVec::from_bytes_unchecked(
1095                            b"0\0\0\0:\0\0\0A\0\0\0G\0\0\0a\0\0\0g\0\0\0",
1096                        )
1097                    },
1098                    22u32,
1099                )
1100            },
1101            icu_collections,
1102            [zerovec],
1103        );
1104    }
1105}