static_lang_word_lists/
word_lists.rs1use std::{
2 borrow::Cow,
3 fs, io,
4 ops::{Deref, Index},
5 path::{Path, PathBuf},
6 slice,
7 sync::LazyLock,
8};
9
10use thiserror::Error;
11
12use crate::{metadata::WordListMetadata, newline_delimited_words};
13
14pub(crate) type Word = String;
16pub(crate) type WordSource = Box<[Word]>;
17
18#[derive(Debug)]
20pub struct WordList {
21 words: EagerOrLazy<WordSource>,
22 pub metadata: WordListMetadata,
34}
35
36impl WordList {
37 #[doc = include_str!("../data/aosp/en_Latn.toml")]
44 #[allow(clippy::result_large_err)]
48 pub fn load(
49 path: impl AsRef<Path>,
50 metadata_path: impl AsRef<Path>,
51 ) -> Result<Self, WordListError> {
52 let mut word_list = WordList::load_without_metadata(path)?;
53 word_list.metadata = WordListMetadata::load(metadata_path)?;
54 Ok(word_list)
55 }
56
57 #[allow(clippy::result_large_err)]
62 pub fn load_without_metadata(
63 path: impl AsRef<Path>,
64 ) -> Result<Self, WordListError> {
65 let path = path.as_ref();
66 let file_content = fs::read_to_string(path).map_err(|io_err| {
67 WordListError::FailedToRead(path.to_owned(), io_err)
68 })?;
69 let name = path
70 .file_stem()
71 .ok_or_else(|| {
72 WordListError::FailedToRead(
73 path.to_owned(),
74 io::Error::new(
75 io::ErrorKind::InvalidData,
76 "file name is empty",
77 ),
78 )
79 })?
80 .to_string_lossy()
81 .replace("/", "_");
82
83 Ok(WordList {
84 metadata: WordListMetadata::new_from_name(name),
85 words: newline_delimited_words(file_content).into(),
86 })
87 }
88
89 #[must_use]
96 pub fn define(
97 name_or_metadata: impl Into<WordListMetadata>,
98 words: impl IntoIterator<Item = impl Into<String>>,
99 ) -> Self {
100 WordList {
101 metadata: name_or_metadata.into(),
102 words: words.into_iter().map(Into::into).collect::<Vec<_>>().into(),
103 }
104 }
105
106 #[must_use]
108 pub(crate) const fn new_lazy(
109 metadata: WordListMetadata,
110 words: LazyLock<WordSource>,
111 ) -> Self {
112 WordList {
113 words: EagerOrLazy::Lazy(words),
114 metadata,
115 }
116 }
117
118 #[allow(dead_code)]
120 pub(crate) const fn stub() -> Self {
121 WordList {
122 metadata: WordListMetadata {
123 name: Cow::Borrowed("stub"),
124 script: None,
125 language: None,
126 },
127 words: EagerOrLazy::Lazy(LazyLock::new(|| unreachable!())),
128 }
129 }
130
131 #[inline]
133 #[must_use]
134 pub fn name(&self) -> &str {
135 self.metadata.name()
136 }
137
138 #[inline]
144 #[must_use]
145 pub fn script(&self) -> Option<&str> {
146 self.metadata.script()
147 }
148
149 #[inline]
154 #[must_use]
155 pub fn language(&self) -> Option<&str> {
156 self.metadata.language()
157 }
158
159 #[must_use]
161 pub fn iter(&self) -> WordListIter<'_> {
162 WordListIter(self.words.iter())
163 }
164
165 #[inline]
167 #[must_use]
168 pub fn len(&self) -> usize {
169 self.words.len()
170 }
171
172 #[inline]
174 #[must_use]
175 pub fn is_empty(&self) -> bool {
176 self.words.is_empty()
177 }
178
179 pub fn filter<F>(&self, mut predicate: F) -> Self
186 where
187 F: FnMut(&str) -> bool,
188 {
189 let reduced_words = self
190 .words
191 .iter()
192 .filter(|word| predicate(word))
193 .cloned()
194 .collect::<Vec<_>>();
195 let reduced_words =
196 EagerOrLazy::Eager(reduced_words.into_boxed_slice());
197 Self {
198 metadata: self.metadata.clone(),
199 words: reduced_words,
200 }
201 }
202}
203
204impl Clone for WordList {
205 fn clone(&self) -> Self {
210 Self {
211 metadata: self.metadata.clone(),
212 words: EagerOrLazy::Eager(self.words.deref().clone()),
213 }
214 }
215}
216
217impl Index<usize> for WordList {
218 type Output = str;
219
220 fn index(&self, index: usize) -> &Self::Output {
221 self.words.index(index).deref()
222 }
223}
224
225#[derive(Debug)]
226enum EagerOrLazy<T> {
227 Eager(T),
228 Lazy(LazyLock<T>),
229}
230
231impl<T> From<T> for EagerOrLazy<T> {
232 fn from(value: T) -> Self {
233 EagerOrLazy::Eager(value)
234 }
235}
236
237impl<T> Deref for EagerOrLazy<T> {
238 type Target = T;
239
240 fn deref(&self) -> &Self::Target {
241 match self {
242 EagerOrLazy::Eager(e) => e,
243 EagerOrLazy::Lazy(l) => l,
244 }
245 }
246}
247
248impl From<Vec<String>> for EagerOrLazy<WordSource> {
249 fn from(value: Vec<String>) -> Self {
250 Self::Eager(value.into_boxed_slice())
251 }
252}
253
254#[derive(Debug)]
258pub struct WordListIter<'a>(slice::Iter<'a, String>);
259
260impl<'a> Iterator for WordListIter<'a> {
261 type Item = &'a str;
262
263 fn next(&mut self) -> Option<Self::Item> {
264 self.0.next().map(String::as_ref)
265 }
266}
267
268impl ExactSizeIterator for WordListIter<'_> {
269 fn len(&self) -> usize {
270 self.0.len()
271 }
272}
273
274impl DoubleEndedIterator for WordListIter<'_> {
275 fn next_back(&mut self) -> Option<Self::Item> {
276 self.0.next_back().map(String::as_ref)
277 }
278}
279
280#[derive(Debug, Error)]
282pub enum WordListError {
283 #[error("failed to read from {}: {}", .0.display(), .1)]
285 FailedToRead(PathBuf, io::Error),
286 #[error("failed to parse metadata from {}: {}", .0.display(), .1)]
288 MetadataError(PathBuf, toml::de::Error),
289}
290
291#[cfg(feature = "rayon")]
292pub(crate) mod rayon {
293 use rayon::iter::{
294 IndexedParallelIterator, ParallelIterator,
295 plumbing::{
296 Consumer, Producer, ProducerCallback, UnindexedConsumer, bridge,
297 },
298 };
299
300 use super::{WordList, WordListIter};
301
302 #[derive(Debug)]
306 pub struct ParWordListIter<'a>(&'a [String]);
307
308 impl<'a> ParallelIterator for ParWordListIter<'a> {
309 type Item = &'a str;
310
311 fn drive_unindexed<C>(self, consumer: C) -> C::Result
312 where
313 C: UnindexedConsumer<Self::Item>,
314 {
315 bridge(self, consumer)
316 }
317
318 fn opt_len(&self) -> Option<usize> {
319 Some(self.0.len())
320 }
321 }
322
323 impl<'a> Producer for ParWordListIter<'a> {
324 type IntoIter = WordListIter<'a>;
325 type Item = &'a str;
326
327 fn into_iter(self) -> Self::IntoIter {
328 WordListIter(self.0.iter())
329 }
330
331 fn split_at(self, index: usize) -> (Self, Self) {
332 let (left, right) = self.0.split_at(index);
333 (ParWordListIter(left), ParWordListIter(right))
334 }
335 }
336
337 impl IndexedParallelIterator for ParWordListIter<'_> {
338 fn len(&self) -> usize {
339 self.0.len()
340 }
341
342 fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
343 bridge(self, consumer)
344 }
345
346 fn with_producer<CB>(self, callback: CB) -> CB::Output
347 where
348 CB: ProducerCallback<Self::Item>,
349 {
350 callback.callback(self)
351 }
352 }
353
354 impl<'a> rayon::iter::IntoParallelIterator for &'a WordList {
355 type Item = &'a str;
356 type Iter = ParWordListIter<'a>;
357
358 fn into_par_iter(self) -> Self::Iter {
359 ParWordListIter(&self.words)
360 }
361 }
362
363 impl WordList {
364 #[must_use]
366 pub fn par_iter(&self) -> ParWordListIter<'_> {
367 ParWordListIter(&self.words)
368 }
369 }
370}