Skip to main content

icu_provider/baked/
zerotrie.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//! Data stored as as [`ZeroTrieSimpleAscii`]
6
7/// This is a valid separator as `DataLocale` will never produce it.
8///
9/// Mostly for internal use
10pub const ID_SEPARATOR: u8 = 0x1E;
11
12pub use crate::DynamicDataMarker;
13use crate::{
14    prelude::{zerofrom::ZeroFrom, *},
15    ule::MaybeAsVarULE,
16};
17#[cfg(feature = "alloc")]
18use alloc::string::String;
19use core::fmt;
20pub use zerotrie::ZeroTrieSimpleAscii;
21use zerotrie::cursor::ZeroTrieSimpleAsciiCursor;
22use zerovec::{VarZeroSlice, vecs::Index32};
23
24/// Optimization to stop writing to a `ZeroTrie` cursor when the `ZeroTrie`
25/// is empty. See #8375
26struct EarlyExitCursor<'a, 'b>(&'b mut ZeroTrieSimpleAsciiCursor<'a>);
27
28impl fmt::Write for EarlyExitCursor<'_, '_> {
29    #[inline]
30    fn write_str(&mut self, s: &str) -> fmt::Result {
31        self.0.write_str(s)?;
32        if self.0.is_empty() {
33            Err(fmt::Error)
34        } else {
35            Ok(())
36        }
37    }
38}
39
40fn get_index(
41    trie: ZeroTrieSimpleAscii<&'static [u8]>,
42    id: DataIdentifierBorrowed,
43    attributes_prefix_match: bool,
44) -> Option<usize> {
45    use writeable::Writeable;
46    let mut cursor = trie.cursor();
47    let _is_ascii = id.locale.write_to(&mut EarlyExitCursor(&mut cursor));
48    if !id.marker_attributes.is_empty() {
49        cursor.step(ID_SEPARATOR);
50        id.marker_attributes
51            .write_to(&mut EarlyExitCursor(&mut cursor))
52            .ok()?;
53        loop {
54            if let Some(v) = cursor.take_value() {
55                break Some(v);
56            }
57            if !attributes_prefix_match || cursor.probe(0).is_none() {
58                break None;
59            }
60        }
61    } else {
62        cursor.take_value()
63    }
64}
65
66#[cfg(feature = "alloc")]
67#[expect(clippy::type_complexity)]
68fn iter(
69    trie: &'static ZeroTrieSimpleAscii<&'static [u8]>,
70) -> core::iter::FilterMap<
71    zerotrie::ZeroTrieStringIterator<'static>,
72    fn((String, usize)) -> Option<DataIdentifierCow<'static>>,
73> {
74    use alloc::borrow::ToOwned;
75    trie.iter().filter_map(move |(s, _)| {
76        if let Some((locale, attrs)) = s.split_once(ID_SEPARATOR as char) {
77            Some(DataIdentifierCow::from_owned(
78                DataMarkerAttributes::try_from_str(attrs).ok()?.to_owned(),
79                locale.parse().ok()?,
80            ))
81        } else {
82            s.parse().ok().map(DataIdentifierCow::from_locale)
83        }
84    })
85}
86
87/// Regular baked data: a trie for lookups and a slice of values
88#[derive(Debug)]
89pub struct Data<M: DataMarker> {
90    // Unsafe invariant: actual values contained MUST be valid indices into `values`
91    trie: ZeroTrieSimpleAscii<&'static [u8]>,
92    values: &'static [M::DataStruct],
93}
94
95impl<M: DataMarker> Data<M> {
96    /// Construct from a trie and values
97    ///
98    /// # Safety
99    /// The actual values contained in the trie must be valid indices into `values`
100    pub const unsafe fn from_trie_and_values_unchecked(
101        trie: ZeroTrieSimpleAscii<&'static [u8]>,
102        values: &'static [M::DataStruct],
103    ) -> Self {
104        Self { trie, values }
105    }
106}
107
108impl<M: DataMarker> super::private::Sealed for Data<M> {}
109impl<M: DataMarker> super::DataStore<M> for Data<M> {
110    fn get(
111        &self,
112        id: DataIdentifierBorrowed,
113        attributes_prefix_match: bool,
114    ) -> Option<DataPayload<M>> {
115        get_index(self.trie, id, attributes_prefix_match)
116            // Safety: Allowed since `i` came from the trie and the field safety invariant
117            .map(|i| unsafe { self.values.get_unchecked(i) })
118            .map(DataPayload::from_static_ref)
119    }
120
121    #[cfg(feature = "alloc")]
122    type IterReturn = core::iter::FilterMap<
123        zerotrie::ZeroTrieStringIterator<'static>,
124        fn((String, usize)) -> Option<DataIdentifierCow<'static>>,
125    >;
126    #[cfg(feature = "alloc")]
127    fn iter(&'static self) -> Self::IterReturn {
128        iter(&self.trie)
129    }
130}
131
132/// Regular baked data: a trie for lookups and a slice of values
133#[derive(Debug)]
134pub struct DataRef<M: DataMarker> {
135    // Unsafe invariant: actual values contained MUST be valid indices into `values`
136    trie: ZeroTrieSimpleAscii<&'static [u8]>,
137    values: &'static [&'static M::DataStruct],
138}
139
140impl<M: DataMarker> DataRef<M> {
141    /// Construct from a trie and references to values
142    ///
143    /// # Safety
144    /// The actual values contained in the trie must be valid indices into `values`
145    pub const unsafe fn from_trie_and_refs_unchecked(
146        trie: ZeroTrieSimpleAscii<&'static [u8]>,
147        values: &'static [&'static M::DataStruct],
148    ) -> Self {
149        Self { trie, values }
150    }
151}
152
153impl<M: DataMarker> super::private::Sealed for DataRef<M> {}
154impl<M: DataMarker> super::DataStore<M> for DataRef<M> {
155    fn get(
156        &self,
157        id: DataIdentifierBorrowed,
158        attributes_prefix_match: bool,
159    ) -> Option<DataPayload<M>> {
160        get_index(self.trie, id, attributes_prefix_match)
161            // Safety: Allowed since `i` came from the trie and the field safety invariant
162            .map(|i| unsafe { self.values.get_unchecked(i) })
163            .copied()
164            .map(DataPayload::from_static_ref)
165    }
166
167    #[cfg(feature = "alloc")]
168    type IterReturn = core::iter::FilterMap<
169        zerotrie::ZeroTrieStringIterator<'static>,
170        fn((String, usize)) -> Option<DataIdentifierCow<'static>>,
171    >;
172    #[cfg(feature = "alloc")]
173    fn iter(&'static self) -> Self::IterReturn {
174        iter(&self.trie)
175    }
176}
177
178/// Optimized data stored as a single [`VarZeroSlice`] to reduce token count
179#[allow(missing_debug_implementations)] // Debug on this will not be too useful
180pub struct DataForVarULEs<M: DataMarker>
181where
182    M::DataStruct: MaybeAsVarULE,
183    M::DataStruct: ZeroFrom<'static, <M::DataStruct as MaybeAsVarULE>::EncodedStruct>,
184{
185    // Unsafe invariant: actual values contained MUST be valid indices into `values`
186    trie: ZeroTrieSimpleAscii<&'static [u8]>,
187    values: &'static VarZeroSlice<<M::DataStruct as MaybeAsVarULE>::EncodedStruct, Index32>,
188}
189
190impl<M: DataMarker> super::private::Sealed for DataForVarULEs<M>
191where
192    M::DataStruct: MaybeAsVarULE,
193    M::DataStruct: ZeroFrom<'static, <M::DataStruct as MaybeAsVarULE>::EncodedStruct>,
194{
195}
196
197impl<M: DataMarker> DataForVarULEs<M>
198where
199    M::DataStruct: MaybeAsVarULE,
200    M::DataStruct: ZeroFrom<'static, <M::DataStruct as MaybeAsVarULE>::EncodedStruct>,
201{
202    /// Construct from a trie and values
203    ///
204    /// # Safety
205    /// The actual values contained in the trie must be valid indices into `values`
206    pub const unsafe fn from_trie_and_values_unchecked(
207        trie: ZeroTrieSimpleAscii<&'static [u8]>,
208        values: &'static VarZeroSlice<<M::DataStruct as MaybeAsVarULE>::EncodedStruct, Index32>,
209    ) -> Self {
210        Self { trie, values }
211    }
212}
213
214impl<M: DataMarker> super::DataStore<M> for DataForVarULEs<M>
215where
216    M::DataStruct: MaybeAsVarULE,
217    M::DataStruct: ZeroFrom<'static, <M::DataStruct as MaybeAsVarULE>::EncodedStruct>,
218{
219    fn get(
220        &self,
221        id: DataIdentifierBorrowed,
222        attributes_prefix_match: bool,
223    ) -> Option<DataPayload<M>> {
224        get_index(self.trie, id, attributes_prefix_match)
225            // Safety: Allowed since `i` came from the trie and the field safety invariant
226            .map(|i| unsafe { self.values.get_unchecked(i) })
227            .map(M::DataStruct::zero_from)
228            .map(DataPayload::from_owned)
229    }
230
231    #[cfg(feature = "alloc")]
232    type IterReturn = core::iter::FilterMap<
233        zerotrie::ZeroTrieStringIterator<'static>,
234        fn((String, usize)) -> Option<DataIdentifierCow<'static>>,
235    >;
236    #[cfg(feature = "alloc")]
237    fn iter(&'static self) -> Self::IterReturn {
238        iter(&self.trie)
239    }
240}