Skip to main content

lindera_dictionary/dictionary/
prefix_dictionary.rs

1use daachorse::DoubleArrayAhoCorasick;
2use rkyv::rancor::{Fallible, Source};
3use rkyv::with::{ArchiveWith, DeserializeWith, SerializeWith};
4use rkyv::{Archive, Deserialize as RkyvDeserialize, Place, Serialize as RkyvSerialize};
5use serde::{Deserialize, Serialize};
6
7use crate::{LinderaResult, error::LinderaErrorKind, util::Data, viterbi::WordEntry};
8
9/// Match structure for common prefix iterator compatibility
10#[derive(Debug, Clone)]
11pub struct Match {
12    pub word_idx: WordIdx,
13    pub end_char: usize,
14}
15
16#[derive(Debug, Clone, Copy)]
17pub struct WordIdx {
18    pub word_id: u32,
19}
20
21impl WordIdx {
22    pub fn new(word_id: u32) -> Self {
23        Self { word_id }
24    }
25}
26
27/// Whether the `da_data` passed to [`PrefixDictionary::load`] is trusted to
28/// be the exact, undamaged output of `DoubleArrayAhoCorasick::serialize()`.
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum DaTrust {
31    /// Skip daachorse's validation pass (`deserialize_unchecked`). Only for
32    /// data this crate's own build pipeline produced and embedded verbatim.
33    Trusted,
34    /// Run daachorse's full validation pass (`deserialize`). Use for any
35    /// filesystem-, network-, or caller-supplied bytes.
36    Untrusted,
37}
38
39pub struct DoubleArrayArchiver;
40
41impl ArchiveWith<DoubleArrayAhoCorasick<u32>> for DoubleArrayArchiver {
42    type Archived = rkyv::vec::ArchivedVec<u8>;
43    type Resolver = rkyv::vec::VecResolver;
44
45    fn resolve_with(
46        field: &DoubleArrayAhoCorasick<u32>,
47        resolver: Self::Resolver,
48        out: Place<Self::Archived>,
49    ) {
50        let bytes = field.serialize();
51        rkyv::vec::ArchivedVec::resolve_from_slice(&bytes, resolver, out);
52    }
53}
54
55impl<S: Fallible + rkyv::ser::Writer + rkyv::ser::Allocator + ?Sized>
56    SerializeWith<DoubleArrayAhoCorasick<u32>, S> for DoubleArrayArchiver
57{
58    fn serialize_with(
59        field: &DoubleArrayAhoCorasick<u32>,
60        serializer: &mut S,
61    ) -> Result<Self::Resolver, S::Error> {
62        let bytes = field.serialize();
63        rkyv::vec::ArchivedVec::serialize_from_slice(&bytes, serializer)
64    }
65}
66
67impl<D: Fallible<Error: Source> + ?Sized>
68    DeserializeWith<rkyv::vec::ArchivedVec<u8>, DoubleArrayAhoCorasick<u32>, D>
69    for DoubleArrayArchiver
70{
71    /// Deserialize the archived byte vector into a `DoubleArrayAhoCorasick`.
72    ///
73    /// # Returns
74    ///
75    /// The deserialized `DoubleArrayAhoCorasick`, or an error if deserialization fails.
76    fn deserialize_with(
77        archived: &rkyv::vec::ArchivedVec<u8>,
78        _deserializer: &mut D,
79    ) -> Result<DoubleArrayAhoCorasick<u32>, D::Error> {
80        let (da, _) = DoubleArrayAhoCorasick::deserialize(archived.as_slice()).map_err(|err| {
81            D::Error::new(std::io::Error::new(
82                std::io::ErrorKind::InvalidData,
83                err.to_string(),
84            ))
85        })?;
86        Ok(da)
87    }
88}
89
90mod double_array_serde {
91    use daachorse::DoubleArrayAhoCorasick;
92    use serde::{Deserialize, Deserializer, Serializer};
93
94    pub fn serialize<S>(da: &DoubleArrayAhoCorasick<u32>, serializer: S) -> Result<S::Ok, S::Error>
95    where
96        S: Serializer,
97    {
98        let bytes = da.serialize();
99        serializer.serialize_bytes(&bytes)
100    }
101
102    pub fn deserialize<'de, D>(deserializer: D) -> Result<DoubleArrayAhoCorasick<u32>, D::Error>
103    where
104        D: Deserializer<'de>,
105    {
106        let bytes: Vec<u8> = Deserialize::deserialize(deserializer)?;
107        let (da, _) = DoubleArrayAhoCorasick::deserialize(&bytes)
108            .map_err(|err| serde::de::Error::custom(err.to_string()))?;
109        Ok(da)
110    }
111}
112
113#[derive(Clone, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)]
114pub struct PrefixDictionary {
115    #[serde(with = "self::double_array_serde")]
116    #[rkyv(with = DoubleArrayArchiver)]
117    pub da: DoubleArrayAhoCorasick<u32>,
118    pub vals_data: Data,
119    pub words_idx_data: Data,
120    pub words_data: Data,
121    pub is_system: bool,
122}
123
124impl PrefixDictionary {
125    /// Decode the `(offset, count)` pair stored in the double-array value.
126    ///
127    /// Both system and user dictionaries pack the word-id offset in the high
128    /// 24 bits and the per-surface variant count in the low 8 bits (up to 255
129    /// variants). The legacy 5-bit user-dictionary encoding was retired in
130    /// v4.0.0; user `.bin` files built with v3 must be rebuilt from CSV.
131    #[inline]
132    pub(crate) fn decode_val(&self, val: u32) -> (u32, u32) {
133        (val >> 8u32, val & ((1u32 << 8) - 1u32))
134    }
135
136    /// Load a `PrefixDictionary` from raw binary data.
137    ///
138    /// # Arguments
139    ///
140    /// * `da_data` - Double-array data bytes.
141    /// * `vals_data` - Values data bytes.
142    /// * `words_idx_data` - Word index data bytes.
143    /// * `words_data` - Words data bytes.
144    /// * `is_system` - Whether this is a system dictionary.
145    /// * `trust` - Whether `da_data` is trusted to be the exact output of
146    ///   `DoubleArrayAhoCorasick::serialize()`, allowing the validation pass
147    ///   to be skipped. See [`DaTrust`].
148    ///
149    /// # Returns
150    ///
151    /// A `PrefixDictionary`, or an error if deserialization fails.
152    pub fn load(
153        da_data: impl Into<Data>,
154        vals_data: impl Into<Data>,
155        words_idx_data: impl Into<Data>,
156        words_data: impl Into<Data>,
157        is_system: bool,
158        trust: DaTrust,
159    ) -> LinderaResult<PrefixDictionary> {
160        let da_bytes = da_data.into();
161        let da = match trust {
162            DaTrust::Trusted => {
163                debug_assert!(
164                    matches!(da_bytes, Data::Static(_)),
165                    "DaTrust::Trusted should only be used for embedded (Data::Static) da_data"
166                );
167                // SAFETY: `da_bytes` must be the byte-exact output of this
168                // exact daachorse version's `DoubleArrayAhoCorasick::serialize()`.
169                // Only `embedded_dictionary!` (lindera-dictionary/src/macros.rs)
170                // passes `DaTrust::Trusted`, using `include_bytes!` data
171                // produced by this crate's own build pipeline
172                // (builder/prefix_dictionary.rs's write_double_array_file,
173                // which calls `.serialize()` directly and writes the bytes
174                // verbatim, with no re-encoding/compression step and no
175                // alternate producer). Note: the opt-in build cache
176                // (assets.rs, LINDERA_BUILD_DICTIONARY_CACHE_DIR) keys only
177                // on the dictionary crate's own version, not daachorse's, so
178                // bumping the pinned daachorse version while reusing a stale
179                // cache dir is a narrow, currently-unexploited path that
180                // could violate this invariant in the future.
181                unsafe { DoubleArrayAhoCorasick::deserialize_unchecked(&da_bytes[..]).0 }
182            }
183            DaTrust::Untrusted => {
184                DoubleArrayAhoCorasick::deserialize(&da_bytes[..])
185                    .map_err(|err| {
186                        LinderaErrorKind::Deserialize.with_error(anyhow::anyhow!(err.to_string()))
187                    })?
188                    .0
189            }
190        };
191
192        Ok(PrefixDictionary {
193            da,
194            vals_data: vals_data.into(),
195            words_idx_data: words_idx_data.into(),
196            words_data: words_data.into(),
197            is_system,
198        })
199    }
200
201    pub fn prefix<'a>(&'a self, s: &'a str) -> impl Iterator<Item = (usize, WordEntry)> + 'a {
202        self.da
203            .find_overlapping_iter(s)
204            .filter(|m| m.start() == 0)
205            .flat_map(move |m| {
206                let (offset, len) = self.decode_val(m.value());
207                let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
208                let data: &[u8] = &self.vals_data[offset_bytes..];
209                (0..len as usize).map(move |i| {
210                    (
211                        m.end(),
212                        WordEntry::deserialize(
213                            &data[WordEntry::SERIALIZED_LEN * i..],
214                            self.is_system,
215                        ),
216                    )
217                })
218            })
219    }
220
221    /// Find `WordEntry`s with surface
222    pub fn find_surface(&self, surface: &str) -> Vec<WordEntry> {
223        self.find_surface_iter(surface).collect()
224    }
225
226    /// Find `WordEntry`s with surface using lazy evaluation
227    /// This iterator-based approach reduces memory allocations
228    pub fn find_surface_iter<'a>(
229        &'a self,
230        surface: &'a str,
231    ) -> impl Iterator<Item = WordEntry> + 'a {
232        self.da
233            .find_overlapping_iter(surface)
234            .filter(|m| m.start() == 0 && m.end() == surface.len())
235            .flat_map(move |m| {
236                let (offset, len) = self.decode_val(m.value());
237                let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
238                let data = &self.vals_data[offset_bytes..];
239                (0..len as usize).map(move |i| {
240                    WordEntry::deserialize(&data[WordEntry::SERIALIZED_LEN * i..], self.is_system)
241                })
242            })
243    }
244
245    /// Common prefix iterator using character array input
246    pub fn common_prefix_iterator(&self, suffix: &[char]) -> Vec<Match> {
247        // Warning: This method takes &[char], but daachorse works on bytes (str).
248        // Converting char slice to string is costly but necessary if we use daachorse standard API.
249
250        if self.vals_data.is_empty() {
251            return Vec::new();
252        }
253
254        let suffix_str: String = suffix.iter().collect();
255
256        self.da
257            .find_overlapping_iter(&suffix_str)
258            .filter(|m| m.start() == 0)
259            .flat_map(|m| {
260                let (offset, len) = self.decode_val(m.value());
261                let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
262
263                // 範囲チェックを追加
264                if offset_bytes >= self.vals_data.len() {
265                    return vec![].into_iter();
266                }
267
268                let data: &[u8] = &self.vals_data[offset_bytes..];
269                (0..len as usize)
270                    .filter_map(move |i| {
271                        let required_bytes = WordEntry::SERIALIZED_LEN * (i + 1);
272                        if required_bytes <= data.len() {
273                            let word_entry = WordEntry::deserialize(
274                                &data[WordEntry::SERIALIZED_LEN * i..],
275                                self.is_system,
276                            );
277                            Some(Match {
278                                word_idx: WordIdx::new(word_entry.word_id().id()),
279                                end_char: m.end(), // prefix_len in bytes? No, m.end() is byte index.
280                                                   // Match expects char length?
281                                                   // Original code: end_char: prefix_len
282                                                   // prefix_len was number of bytes or chars?
283                                                   // yada::common_prefix_search returns (val, len) where len is length in bytes?
284                                                   // yada common_prefix_search(str) returns length in bytes.
285                                                   // But common_prefix_iterator takes &[char].
286                                                   // Match.end_char usually implies character index if used for Viterbi on chars.
287                                                   // But Viterbi usually works on bytes in Lindera?
288                                                   // Let's check typical usage.
289                                                   // NOTE: daachorse returns byte indices.
290                                                   // If input was chars converted to String, byte index != char index.
291                                                   // We need to map back to char index?
292                                                   // This function common_prefix_iterator might be inefficient or deprecated given we move to byte-based Viterbi.
293                                                   // For now, let's assume we return byte length.
294                                                   // But wait, suffix is &[char].
295                                                   // The caller likely expects char length?
296                                                   // Yes. if suffix is &[char], end_char 3 means 3 chars.
297                                                   // We have byte length from daachorse.
298                                                   // We need to count chars in suffix_str[..m.end()].
299                                                   // This is inefficient.
300                            })
301                        } else {
302                            None
303                        }
304                    })
305                    .collect::<Vec<_>>()
306                    .into_iter()
307            })
308            .collect()
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use daachorse::DoubleArrayAhoCorasickBuilder;
315
316    use super::*;
317
318    fn build_valid_da_bytes() -> Vec<u8> {
319        let keyset: Vec<(&[u8], u32)> = vec![(b"a", 0), (b"ab", 1), (b"b", 2)];
320        let da = DoubleArrayAhoCorasickBuilder::new()
321            .build_with_values(keyset)
322            .unwrap();
323        da.serialize()
324    }
325
326    #[test]
327    fn test_prefix_dictionary_load_trusted_matches_untrusted() {
328        let da_bytes = build_valid_da_bytes();
329        // `DaTrust::Trusted` asserts (debug builds) that the input is
330        // `Data::Static`, mirroring the only real caller (the
331        // `embedded_dictionary!` macro's `include_bytes!` data). Leak the
332        // buffer to get a genuine `&'static [u8]` for this test.
333        let da_bytes_static: &'static [u8] = Box::leak(da_bytes.clone().into_boxed_slice());
334
335        let trusted = PrefixDictionary::load(
336            da_bytes_static,
337            Vec::<u8>::new(),
338            Vec::<u8>::new(),
339            Vec::<u8>::new(),
340            true,
341            DaTrust::Trusted,
342        )
343        .unwrap();
344        let untrusted = PrefixDictionary::load(
345            da_bytes,
346            Vec::<u8>::new(),
347            Vec::<u8>::new(),
348            Vec::<u8>::new(),
349            true,
350            DaTrust::Untrusted,
351        )
352        .unwrap();
353
354        let trusted_matches: Vec<_> = trusted.da.find_overlapping_iter("ab").collect();
355        let untrusted_matches: Vec<_> = untrusted.da.find_overlapping_iter("ab").collect();
356        assert_eq!(trusted_matches.len(), untrusted_matches.len());
357        assert!(!trusted_matches.is_empty());
358        for (t, u) in trusted_matches.iter().zip(untrusted_matches.iter()) {
359            assert_eq!(t.value(), u.value());
360            assert_eq!(t.start(), u.start());
361            assert_eq!(t.end(), u.end());
362        }
363    }
364
365    #[test]
366    fn test_prefix_dictionary_load_untrusted_rejects_corrupted_da_data() {
367        let mut da_bytes = build_valid_da_bytes();
368        // Truncate to well short of a valid length-prefixed record; the
369        // checked `deserialize` path must reject this rather than panic or
370        // read out of bounds.
371        da_bytes.truncate(4);
372
373        let result = PrefixDictionary::load(
374            da_bytes,
375            Vec::<u8>::new(),
376            Vec::<u8>::new(),
377            Vec::<u8>::new(),
378            true,
379            DaTrust::Untrusted,
380        );
381
382        assert!(result.is_err());
383    }
384}