Skip to main content

lindera_dictionary/
util.rs

1use std::fs::File;
2use std::io::{Read, Write};
3use std::ops::Deref;
4use std::path::Path;
5
6#[cfg(feature = "mmap")]
7use memmap2::Mmap;
8
9use anyhow::anyhow;
10use encoding_rs::Encoding;
11use serde::{Deserialize, Serialize};
12
13use crate::LinderaResult;
14use crate::error::LinderaErrorKind;
15
16use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
17
18/// Counts the fields a NUL-joined detail blob splits into.
19///
20/// The dictionary builder joins a row's detail fields with `"\0"` and writes
21/// no trailing separator, so the field count is exactly one more than the
22/// number of NUL bytes. Callers use this to size their detail vectors up
23/// front instead of letting them grow from capacity zero, which cost two
24/// reallocations per token for a 9-field dictionary (#966).
25///
26/// # 引数
27///
28/// * `joined_details` - The NUL-joined detail blob.
29///
30/// # 戻り値
31///
32/// The number of fields, always at least 1 (an empty blob splits into a
33/// single empty field, matching `slice::split`).
34#[inline]
35pub(crate) fn detail_field_count(joined_details: &[u8]) -> usize {
36    memchr::memchr_iter(0, joined_details).count() + 1
37}
38
39/// Reads the `words_data` byte offset a word id maps to.
40///
41/// Both packed dictionaries store `words_idx_data` as one little-endian `u32`
42/// per word id. Every step is bounds-checked, so a corrupt archive or an
43/// absurd id ends in `None` instead of a panic -- the accessors used to slice
44/// this unchecked (#966).
45///
46/// # 引数
47///
48/// * `words_idx_data` - The word-id index table.
49/// * `word_id` - The word id to look up.
50///
51/// # 戻り値
52///
53/// The entry's byte offset into `words_data`, or `None` when the id has no
54/// slot in the table. Callers map `None` onto their own missing-entry
55/// fallback, which differs per dictionary.
56#[inline]
57pub(crate) fn words_idx_offset(words_idx_data: &[u8], word_id: usize) -> Option<usize> {
58    let start = word_id.checked_mul(4)?;
59    let bytes = words_idx_data.get(start..start.checked_add(4)?)?;
60    let offset: [u8; 4] = bytes.try_into().ok()?;
61    Some(u32::from_le_bytes(offset) as usize)
62}
63
64/// Locates and validates one entry's NUL-joined detail blob.
65///
66/// Each entry is a 4-byte little-endian length followed by that many bytes of
67/// NUL-joined fields. The whole blob is validated once rather than field by
68/// field: NUL is ASCII and therefore never occurs inside a multi-byte UTF-8
69/// sequence, so "the blob is valid UTF-8" and "every field is valid UTF-8"
70/// are the same statement. One `from_utf8` call replaces one per field, and
71/// the resulting `&str` can be split without re-validating.
72///
73/// # 引数
74///
75/// * `words_data` - The packed detail records.
76/// * `offset` - The entry's byte offset inside `words_data`.
77///
78/// # 戻り値
79///
80/// The entry's joined fields, or `None` when the offset, the declared length
81/// or the payload's encoding is invalid.
82#[inline]
83pub(crate) fn joined_details_at(words_data: &[u8], offset: usize) -> Option<&str> {
84    let header: [u8; 4] = words_data
85        .get(offset..offset.checked_add(4)?)?
86        .try_into()
87        .ok()?;
88    let start = offset + 4;
89    let len = u32::from_le_bytes(header) as usize;
90    let bytes = words_data.get(start..start.checked_add(len)?)?;
91    str::from_utf8(bytes).ok()
92}
93
94/// Write data directly to the writer.
95pub fn write_data<W: Write>(buffer: &[u8], writer: &mut W) -> LinderaResult<()> {
96    writer.write_all(buffer).map_err(|err| {
97        LinderaErrorKind::Io
98            .with_error(err)
99            .add_context("Failed to write data to output")
100    })?;
101    Ok(())
102}
103
104pub fn read_file(filename: &Path) -> LinderaResult<Vec<u8>> {
105    let mut input_read = File::open(filename).map_err(|err| {
106        LinderaErrorKind::Io
107            .with_error(err)
108            .add_context(format!("Failed to open file: {}", filename.display()))
109    })?;
110    let mut buffer = Vec::new();
111    input_read.read_to_end(&mut buffer).map_err(|err| {
112        LinderaErrorKind::Io.with_error(err).add_context(format!(
113            "Failed to read file contents: {}",
114            filename.display()
115        ))
116    })?;
117    Ok(buffer)
118}
119
120/// Reads a file into a 16-byte aligned buffer, as required when loading rkyv
121/// archives (e.g. `char_def.bin`, `unk.bin`).
122pub fn read_aligned_file(filename: &Path) -> LinderaResult<rkyv::util::AlignedVec<16>> {
123    let raw_data = read_file(filename)?;
124
125    let mut aligned_data = rkyv::util::AlignedVec::<16>::new();
126    aligned_data.extend_from_slice(&raw_data);
127
128    Ok(aligned_data)
129}
130
131#[cfg(feature = "mmap")]
132pub fn mmap_file(filename: &Path) -> LinderaResult<Mmap> {
133    let file = File::open(filename).map_err(|err| {
134        LinderaErrorKind::Io.with_error(err).add_context(format!(
135            "Failed to open file for memory mapping: {}",
136            filename.display()
137        ))
138    })?;
139    let mmap = unsafe { Mmap::map(&file) }.map_err(|err| {
140        LinderaErrorKind::Io
141            .with_error(err)
142            .add_context(format!("Failed to memory map file: {}", filename.display()))
143    })?;
144    Ok(mmap)
145}
146
147pub fn read_file_with_encoding(filepath: &Path, encoding_name: &str) -> LinderaResult<String> {
148    let encoding = Encoding::for_label_no_replacement(encoding_name.as_bytes());
149    let encoding = encoding.ok_or_else(|| {
150        LinderaErrorKind::Decode.with_error(anyhow!("Invalid encoding: {encoding_name}"))
151    })?;
152
153    let buffer = read_file(filepath)?;
154    Ok(encoding.decode(&buffer).0.into_owned())
155}
156
157use std::sync::Arc;
158
159#[derive(Clone)]
160pub enum Data {
161    Static(&'static [u8]),
162    Vec(Vec<u8>),
163    #[cfg(feature = "mmap")]
164    Map(Arc<Mmap>),
165}
166
167impl Archive for Data {
168    type Archived = rkyv::vec::ArchivedVec<u8>;
169    type Resolver = rkyv::vec::VecResolver;
170
171    fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
172        rkyv::vec::ArchivedVec::resolve_from_slice(self.deref(), resolver, out);
173    }
174}
175
176impl<S> RkyvSerialize<S> for Data
177where
178    S: rkyv::rancor::Fallible + rkyv::ser::Writer + rkyv::ser::Allocator + ?Sized,
179{
180    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
181        rkyv::vec::ArchivedVec::serialize_from_slice(self.deref(), serializer)
182    }
183}
184
185impl<D: rkyv::rancor::Fallible + ?Sized> RkyvDeserialize<Data, D> for rkyv::vec::ArchivedVec<u8> {
186    fn deserialize(&self, _deserializer: &mut D) -> Result<Data, D::Error> {
187        let mut vec = Vec::with_capacity(self.len());
188        vec.extend_from_slice(self.as_slice());
189        Ok(Data::Vec(vec))
190    }
191}
192
193impl Deref for Data {
194    type Target = [u8];
195    fn deref(&self) -> &Self::Target {
196        match self {
197            Data::Static(s) => s,
198            Data::Vec(v) => v,
199            #[cfg(feature = "mmap")]
200            Data::Map(m) => m,
201        }
202    }
203}
204
205impl From<&'static [u8]> for Data {
206    fn from(s: &'static [u8]) -> Self {
207        Self::Static(s)
208    }
209}
210
211impl<T: Deref<Target = [u8]>> From<&'static T> for Data {
212    fn from(t: &'static T) -> Self {
213        Self::Static(t)
214    }
215}
216
217impl From<Vec<u8>> for Data {
218    fn from(v: Vec<u8>) -> Self {
219        Self::Vec(v)
220    }
221}
222
223#[cfg(feature = "mmap")]
224impl From<Mmap> for Data {
225    fn from(m: Mmap) -> Self {
226        Self::Map(Arc::new(m))
227    }
228}
229
230impl Serialize for Data {
231    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232    where
233        S: serde::Serializer,
234    {
235        serializer.serialize_bytes(self.deref())
236    }
237}
238
239impl<'de> Deserialize<'de> for Data {
240    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241    where
242        D: serde::Deserializer<'de>,
243    {
244        let v = <Vec<u8> as serde::Deserialize>::deserialize(deserializer)?;
245        Ok(Data::Vec(v))
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::{detail_field_count, joined_details_at, words_idx_offset};
252
253    /// The count must match what `slice::split` actually yields, which is the
254    /// invariant the presized detail vectors rely on.
255    #[test]
256    fn detail_field_count_matches_split() {
257        for blob in [
258            &b""[..],
259            &b"a"[..],
260            &b"a\0b"[..],
261            &b"\0"[..],
262            &b"\0\0"[..],
263            &b"a\0\0b"[..],
264            &b"NOUN\0general\0*\0*\0*\0*\0base\0reading\0pron"[..],
265        ] {
266            assert_eq!(
267                detail_field_count(blob),
268                blob.split(|&b| b == 0).count(),
269                "blob {blob:?}"
270            );
271        }
272    }
273
274    /// An empty blob still splits into one (empty) field, so the count is
275    /// never zero -- a zero capacity would reintroduce the growth this
276    /// helper exists to avoid.
277    #[test]
278    fn detail_field_count_is_never_zero() {
279        assert_eq!(detail_field_count(b""), 1);
280    }
281
282    /// The IPADIC shape: 9 fields joined by 8 separators.
283    #[test]
284    fn detail_field_count_ipadic_shape() {
285        let blob = b"\xe5\x90\x8d\xe8\xa9\x9e\0*\0*\0*\0*\0*\0a\0b\0c";
286        assert_eq!(detail_field_count(blob), 9);
287    }
288
289    /// Builds a `words_data` blob holding `entries`, each encoded as a 4-byte
290    /// LE length followed by its NUL-joined fields, and returns it with each
291    /// entry's offset.
292    fn packed(entries: &[&[&str]]) -> (Vec<u8>, Vec<usize>) {
293        let mut data = Vec::new();
294        let mut offsets = Vec::new();
295        for fields in entries {
296            offsets.push(data.len());
297            let joined = fields.join("\0");
298            data.extend_from_slice(&(joined.len() as u32).to_le_bytes());
299            data.extend_from_slice(joined.as_bytes());
300        }
301        (data, offsets)
302    }
303
304    /// Each entry decodes to its own blob, not to the tail of the buffer.
305    #[test]
306    fn joined_details_at_reads_the_declared_length() {
307        let (data, offsets) = packed(&[&["名詞", "一般"], &["動詞", "自立", "*"]]);
308
309        assert_eq!(joined_details_at(&data, offsets[0]), Some("名詞\0一般"));
310        assert_eq!(joined_details_at(&data, offsets[1]), Some("動詞\0自立\0*"));
311    }
312
313    /// A truncated header, an offset past the end, and a declared length that
314    /// runs past the buffer all yield `None` rather than panicking. The
315    /// accessors used to slice this unchecked.
316    #[test]
317    fn joined_details_at_rejects_out_of_range_offsets_and_lengths() {
318        let (mut data, offsets) = packed(&[&["名詞", "一般"]]);
319
320        // Offset past the end, and an offset whose 4-byte header straddles it.
321        assert_eq!(joined_details_at(&data, data.len()), None);
322        assert_eq!(joined_details_at(&data, data.len() - 2), None);
323        // An offset so large that `offset + 4` would overflow.
324        assert_eq!(joined_details_at(&data, usize::MAX), None);
325
326        // Declared length running past the buffer.
327        let past_end = (data.len() + 1) as u32;
328        data[offsets[0]..offsets[0] + 4].copy_from_slice(&past_end.to_le_bytes());
329        assert_eq!(joined_details_at(&data, offsets[0]), None);
330
331        // A length so large that `start + len` would overflow.
332        data[offsets[0]..offsets[0] + 4].copy_from_slice(&u32::MAX.to_le_bytes());
333        assert_eq!(joined_details_at(&data, offsets[0]), None);
334    }
335
336    /// Invalid UTF-8 anywhere in the blob rejects the whole entry, which is
337    /// what per-field validation did too.
338    #[test]
339    fn joined_details_at_rejects_invalid_utf8() {
340        let (mut data, offsets) = packed(&[&["ok", "fields"]]);
341        let last = data.len() - 1;
342        data[last] = 0xff;
343
344        assert_eq!(joined_details_at(&data, offsets[0]), None);
345    }
346
347    /// Word ids index a table of little-endian `u32`s; out-of-range and
348    /// overflowing ids yield `None`.
349    #[test]
350    fn words_idx_offset_reads_and_bounds_checks() {
351        let table: Vec<u8> = [7u32, 42u32].iter().flat_map(|v| v.to_le_bytes()).collect();
352
353        assert_eq!(words_idx_offset(&table, 0), Some(7));
354        assert_eq!(words_idx_offset(&table, 1), Some(42));
355        assert_eq!(words_idx_offset(&table, 2), None);
356        // `word_id * 4` overflows.
357        assert_eq!(words_idx_offset(&table, usize::MAX), None);
358        assert_eq!(words_idx_offset(&table, usize::MAX / 4), None);
359    }
360}