icu_collections/codepointinvliststringlist/
mod.rs1#[cfg(feature = "alloc")]
13use crate::codepointinvlist::CodePointInversionListBuilder;
14use crate::codepointinvlist::{CodePointInversionList, CodePointInversionListULE};
15#[cfg(feature = "alloc")]
16use alloc::string::{String, ToString};
17#[cfg(feature = "alloc")]
18use alloc::vec::Vec;
19use displaydoc::Display;
20use yoke::Yokeable;
21use zerofrom::ZeroFrom;
22use zerovec::{VarZeroSlice, VarZeroVec};
23
24#[zerovec::make_varule(CodePointInversionListAndStringListULE)]
29#[zerovec::skip_derive(Ord)]
30#[zerovec::derive(Debug)]
31#[derive(Debug, Eq, PartialEq, Clone, Yokeable, ZeroFrom)]
32#[cfg_attr(not(feature = "alloc"), zerovec::skip_derive(ZeroMapKV, ToOwned))]
33#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
35#[cfg_attr(feature = "serde", zerovec::derive(Serialize, Deserialize, Debug))]
36pub struct CodePointInversionListAndStringList<'data> {
37 #[cfg_attr(feature = "serde", serde(borrow))]
38 #[zerovec::varule(CodePointInversionListULE)]
39 cp_inv_list: CodePointInversionList<'data>,
40 #[cfg_attr(feature = "serde", serde(borrow))]
45 str_list: VarZeroVec<'data, str>,
46}
47
48#[cfg(feature = "databake")]
49impl databake::Bake for CodePointInversionListAndStringList<'_> {
50 fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
51 env.insert("icu_collections");
52 let cp_inv_list = self.cp_inv_list.bake(env);
53 let str_list = self.str_list.bake(env);
54 databake::quote! {
56 icu_collections::codepointinvliststringlist::CodePointInversionListAndStringList::from_parts_unchecked(#cp_inv_list, #str_list)
57 }
58 }
59}
60
61#[cfg(feature = "databake")]
62impl databake::BakeSize for CodePointInversionListAndStringList<'_> {
63 fn borrows_size(&self) -> usize {
64 self.cp_inv_list.borrows_size() + self.str_list.borrows_size()
65 }
66}
67
68impl<'data> CodePointInversionListAndStringList<'data> {
69 pub fn try_from(
72 cp_inv_list: CodePointInversionList<'data>,
73 str_list: VarZeroVec<'data, str>,
74 ) -> Result<Self, InvalidStringList> {
75 {
82 let mut it = str_list.iter();
83 if let Some(mut x) = it.next() {
84 if x.len() == 1 {
85 return Err(InvalidStringList::InvalidStringLength(
86 #[cfg(feature = "alloc")]
87 x.to_string(),
88 ));
89 }
90 for y in it {
91 if x.len() == 1 {
92 return Err(InvalidStringList::InvalidStringLength(
93 #[cfg(feature = "alloc")]
94 x.to_string(),
95 ));
96 } else if x == y {
97 return Err(InvalidStringList::StringListNotUnique(
98 #[cfg(feature = "alloc")]
99 x.to_string(),
100 ));
101 } else if x > y {
102 return Err(InvalidStringList::StringListNotSorted(
103 #[cfg(feature = "alloc")]
104 x.to_string(),
105 #[cfg(feature = "alloc")]
106 y.to_string(),
107 ));
108 }
109
110 x = y;
112 }
113 }
114 }
115
116 Ok(CodePointInversionListAndStringList {
117 cp_inv_list,
118 str_list,
119 })
120 }
121
122 #[doc(hidden)] pub const fn from_parts_unchecked(
124 cp_inv_list: CodePointInversionList<'data>,
125 str_list: VarZeroVec<'data, str>,
126 ) -> Self {
127 CodePointInversionListAndStringList {
128 cp_inv_list,
129 str_list,
130 }
131 }
132
133 pub fn size(&self) -> usize {
137 self.cp_inv_list.size() + self.str_list.len()
138 }
139
140 pub fn has_strings(&self) -> bool {
142 !self.str_list.is_empty()
143 }
144
145 pub fn contains_str(&self, s: &str) -> bool {
167 let mut chars = s.chars();
168 if let Some(first_char) = chars.next() {
169 if chars.next().is_none() {
170 return self.contains(first_char);
171 }
172 }
173 self.str_list.binary_search(s).is_ok()
174 }
175
176 pub fn contains_utf8(&self, s: &[u8]) -> bool {
178 if let Ok(well_formed) = core::str::from_utf8(s) {
179 self.contains_str(well_formed)
180 } else {
181 false
182 }
183 }
184
185 pub fn contains32(&self, cp: u32) -> bool {
205 self.cp_inv_list.contains32(cp)
206 }
207
208 pub fn contains(&self, ch: char) -> bool {
228 self.contains32(ch as u32)
229 }
230
231 pub fn code_points(&self) -> &CodePointInversionList<'data> {
233 &self.cp_inv_list
234 }
235
236 pub fn strings(&self) -> &VarZeroSlice<str> {
238 &self.str_list
239 }
240}
241
242#[cfg(feature = "alloc")]
243impl<'a> FromIterator<&'a str> for CodePointInversionListAndStringList<'_> {
245 fn from_iter<I>(it: I) -> Self
246 where
247 I: IntoIterator<Item = &'a str>,
248 {
249 let mut builder = CodePointInversionListBuilder::new();
250 let mut strings = Vec::<&str>::new();
251 for s in it {
252 let mut chars = s.chars();
253 if let Some(first_char) = chars.next() {
254 if chars.next().is_none() {
255 builder.add_char(first_char);
256 continue;
257 }
258 }
259 strings.push(s);
260 }
261
262 strings.sort_unstable();
265 strings.dedup();
266
267 let cp_inv_list = builder.build();
268 let str_list = VarZeroVec::<str>::from(&strings);
269
270 CodePointInversionListAndStringList {
271 cp_inv_list,
272 str_list,
273 }
274 }
275}
276
277#[derive(Display, Debug)]
279#[allow(clippy::exhaustive_enums)] pub enum InvalidStringList {
281 #[cfg_attr(feature = "alloc", displaydoc("Invalid string length for string: {0}"))]
283 InvalidStringLength(#[cfg(feature = "alloc")] String),
284 #[cfg_attr(feature = "alloc", displaydoc("String list has duplicate: {0}"))]
286 StringListNotUnique(#[cfg(feature = "alloc")] String),
287 #[cfg_attr(
289 feature = "alloc",
290 displaydoc("Strings in string list not in sorted order: ({0}, {1})")
291 )]
292 StringListNotSorted(
293 #[cfg(feature = "alloc")] String,
294 #[cfg(feature = "alloc")] String,
295 ),
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_size_has_strings() {
304 let cp_slice = &[0, 1, 0x7F, 0x80, 0xFFFF, 0x1_0000, 0x10_FFFF, 0x11_0000];
305 let cp_list = CodePointInversionList::try_from_u32_inversion_list_slice(cp_slice).unwrap();
306 let str_slice = &["ascii_max", "bmp_max", "unicode_max", "zero"];
307 let str_list = VarZeroVec::<str>::from(str_slice);
308
309 let cpilsl = CodePointInversionListAndStringList::try_from(cp_list, str_list).unwrap();
310
311 assert!(cpilsl.has_strings());
312 assert_eq!(8, cpilsl.size());
313 }
314
315 #[test]
316 fn test_empty_string_allowed() {
317 let cp_slice = &[0, 1, 0x7F, 0x80, 0xFFFF, 0x1_0000, 0x10_FFFF, 0x11_0000];
318 let cp_list = CodePointInversionList::try_from_u32_inversion_list_slice(cp_slice).unwrap();
319 let str_slice = &["", "ascii_max", "bmp_max", "unicode_max", "zero"];
320 let str_list = VarZeroVec::<str>::from(str_slice);
321
322 let cpilsl = CodePointInversionListAndStringList::try_from(cp_list, str_list).unwrap();
323
324 assert!(cpilsl.has_strings());
325 assert_eq!(9, cpilsl.size());
326 }
327
328 #[test]
329 fn test_invalid_string() {
330 let cp_slice = &[0, 1];
331 let cp_list = CodePointInversionList::try_from_u32_inversion_list_slice(cp_slice).unwrap();
332 let str_slice = &["a"];
333 let str_list = VarZeroVec::<str>::from(str_slice);
334
335 let cpilsl = CodePointInversionListAndStringList::try_from(cp_list, str_list);
336
337 assert!(matches!(
338 cpilsl,
339 Err(InvalidStringList::InvalidStringLength(_))
340 ));
341 }
342
343 #[test]
344 fn test_invalid_string_list_has_duplicate() {
345 let cp_slice = &[0, 1];
346 let cp_list = CodePointInversionList::try_from_u32_inversion_list_slice(cp_slice).unwrap();
347 let str_slice = &["abc", "abc"];
348 let str_list = VarZeroVec::<str>::from(str_slice);
349
350 let cpilsl = CodePointInversionListAndStringList::try_from(cp_list, str_list);
351
352 assert!(matches!(
353 cpilsl,
354 Err(InvalidStringList::StringListNotUnique(_))
355 ));
356 }
357
358 #[test]
359 fn test_invalid_string_list_not_sorted() {
360 let cp_slice = &[0, 1];
361 let cp_list = CodePointInversionList::try_from_u32_inversion_list_slice(cp_slice).unwrap();
362 let str_slice = &["xyz", "abc"];
363 let str_list = VarZeroVec::<str>::from(str_slice);
364
365 let cpilsl = CodePointInversionListAndStringList::try_from(cp_list, str_list);
366
367 assert!(matches!(
368 cpilsl,
369 Err(InvalidStringList::StringListNotSorted(_, _))
370 ));
371 }
372
373 #[test]
374 fn test_from_iter_invariants() {
375 let in_strs_1 = ["a", "abc", "xyz", "abc"];
376 let in_strs_2 = ["xyz", "abc", "a", "abc"];
377
378 let cpilsl_1 = CodePointInversionListAndStringList::from_iter(in_strs_1);
379 let cpilsl_2 = CodePointInversionListAndStringList::from_iter(in_strs_2);
380
381 assert_eq!(cpilsl_1, cpilsl_2);
382
383 assert!(cpilsl_1.has_strings());
384 assert!(cpilsl_1.contains_str("abc"));
385 assert!(cpilsl_1.contains_str("xyz"));
386 assert!(!cpilsl_1.contains_str("def"));
387
388 assert_eq!(1, cpilsl_1.cp_inv_list.size());
389 assert!(cpilsl_1.contains('a'));
390 assert!(!cpilsl_1.contains('0'));
391 assert!(!cpilsl_1.contains('q'));
392
393 assert_eq!(3, cpilsl_1.size());
394 }
395}