Skip to main content

sudachi/dic/word_info/
data.rs

1/*
2 * Copyright (c) 2025-2026 Works Applications Co., Ltd.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::dic::lexicon::strings::StringPointer;
18use crate::dic::strings_cache::StringsCache;
19use crate::dic::subset::InfoSubset;
20use crate::dic::word_id::{DictId, WordId, WordRef};
21use crate::dic::word_info::WordInfoRawData;
22use crate::dic::LexiconAccess;
23
24/// wrapper type that indicates inner data are not resolved for the specific lexicon set.
25#[derive(Clone, Debug, Default)]
26#[repr(transparent)]
27pub struct WordInfoRefData {
28    raw: WordInfoRawData,
29}
30
31impl WordInfoRefData {
32    pub fn from_raw(raw: WordInfoRawData) -> Self {
33        WordInfoRefData { raw }
34    }
35
36    pub fn headword_strptr(&self) -> StringPointer {
37        self.raw.headword_strptr
38    }
39
40    /// Convert into WordInfoData resolving part-of-speech and word references.
41    pub fn resolve(
42        self,
43        dict_id: DictId,
44        num_system_pos: usize,
45        pos_offsets: &[usize],
46        subset: InfoSubset,
47    ) -> WordInfoData {
48        let mut raw = self.raw;
49
50        if subset.contains(InfoSubset::POS_ID) {
51            let pos_id = raw.pos_id as usize;
52            if dict_id.is_user() && pos_id >= num_system_pos {
53                // is user defined part-of-speech
54                let pos_id_diff = pos_id - num_system_pos;
55                let pos_offset = pos_offsets[dict_id.as_raw() as usize];
56
57                raw.pos_id = (pos_offset + pos_id_diff) as i16;
58            }
59        }
60
61        if subset.contains(InfoSubset::NORMALIZED_FORM) {
62            raw.normalized_form = WordRef::resolve_raw(raw.normalized_form, dict_id);
63        }
64        if subset.contains(InfoSubset::DICTIONARY_FORM) {
65            raw.dictionary_form = WordRef::resolve_raw(raw.dictionary_form, dict_id);
66        }
67
68        if subset.contains(InfoSubset::SPLIT_C) {
69            Self::resolve_ref_vec(&mut raw.c_unit_split, dict_id);
70        }
71        if subset.contains(InfoSubset::SPLIT_B) {
72            Self::resolve_ref_vec(&mut raw.b_unit_split, dict_id);
73        }
74        if subset.contains(InfoSubset::SPLIT_A) {
75            Self::resolve_ref_vec(&mut raw.a_unit_split, dict_id);
76        }
77        if subset.contains(InfoSubset::WORD_STRUCTURE) {
78            Self::resolve_ref_vec(&mut raw.word_structure, dict_id);
79        }
80
81        WordInfoData::from_resolved(raw)
82    }
83
84    fn resolve_ref_vec(refs: &mut [u32], dict_id: DictId) {
85        for raw in refs.iter_mut() {
86            *raw = WordRef::resolve_raw(*raw, dict_id);
87        }
88    }
89}
90
91/// wrapper type that indicates inner data are resolved for the specific lexicon set.
92#[derive(Clone, Debug, Default)]
93#[repr(transparent)]
94pub struct WordInfoData {
95    raw: WordInfoRawData,
96}
97
98impl WordInfoData {
99    /// WordRefs in the given WortInfoRawData must be resolved.
100    pub fn from_resolved(raw: WordInfoRawData) -> Self {
101        WordInfoData { raw }
102    }
103
104    pub fn new_oov(pos_id: i16, index_form_length: i16) -> Self {
105        Self {
106            raw: WordInfoRawData {
107                pos_id,
108                index_form_length,
109                ..Default::default()
110            },
111        }
112    }
113
114    pub fn pos_id(&self) -> u16 {
115        self.raw.pos_id as u16
116    }
117
118    pub fn index_form_length(&self) -> usize {
119        self.raw.index_form_length as usize
120    }
121
122    pub fn headword_strptr(&self) -> StringPointer {
123        self.raw.headword_strptr
124    }
125
126    pub fn reading_form_strptr(&self) -> StringPointer {
127        self.raw.reading_form_strptr
128    }
129
130    pub fn normalized_form_word_id(&self) -> WordId {
131        WordId::from_raw(self.raw.normalized_form)
132    }
133
134    pub fn dictionary_form_word_id(&self) -> WordId {
135        WordId::from_raw(self.raw.dictionary_form)
136    }
137
138    pub fn c_unit_split(&self) -> &[WordId] {
139        Self::as_word_id_slice(&self.raw.c_unit_split)
140    }
141
142    pub fn b_unit_split(&self) -> &[WordId] {
143        Self::as_word_id_slice(&self.raw.b_unit_split)
144    }
145
146    pub fn a_unit_split(&self) -> &[WordId] {
147        Self::as_word_id_slice(&self.raw.a_unit_split)
148    }
149
150    pub fn word_structure(&self) -> &[WordId] {
151        Self::as_word_id_slice(&self.raw.word_structure)
152    }
153
154    fn as_word_id_slice(raw_slice: &[u32]) -> &[WordId] {
155        if raw_slice.is_empty() {
156            &[]
157        } else {
158            // values in the slice are resolved and safely casted to WordId
159            unsafe {
160                std::slice::from_raw_parts(raw_slice.as_ptr() as *const WordId, raw_slice.len())
161            }
162        }
163    }
164
165    pub fn synonym_group_ids(&self) -> &[i32] {
166        &self.raw.synonym_group_ids
167    }
168
169    pub fn user_data(&self) -> &str {
170        &self.raw.user_data
171    }
172}
173
174/// Data structure needed to resolve references in the WordInfoData.
175/// Currently only lexicon set is needed, but it can be extended in the future if needed.
176pub trait WordInfoResolver: LexiconAccess {}
177
178impl<T: LexiconAccess> WordInfoResolver for T {}
179
180/// WordInfo API.
181///
182/// Internal data is not accessible by default, but can be extracted as
183/// `let data: WordInfoData = info.into()`.
184/// Note: this will consume WordInfo.
185#[derive(Clone)]
186pub struct WordInfo {
187    data: WordInfoData,
188
189    // keep self word_id for the purpose of simplisity
190    word_id: WordId,
191
192    // In dict v1, word info contains only string pointers, and the actual strings are resolved via lexicon set.
193    // keep them in the cache to avoid redundant lookups.
194    strings: StringsCache,
195}
196
197impl WordInfo {
198    pub fn new(data: WordInfoData, word_id: WordId) -> Self {
199        WordInfo {
200            data,
201            word_id,
202            strings: StringsCache::new(),
203        }
204    }
205
206    pub fn new_oov(pos_id: u16, index_form_length: i16, word_id: WordId, headword: String) -> Self {
207        Self {
208            data: WordInfoData::new_oov(pos_id as i16, index_form_length),
209            word_id,
210            strings: StringsCache::new_with_single_string(headword),
211        }
212    }
213
214    pub fn new_with_strings(
215        pos_id: i16,
216        index_form_length: i16,
217        word_id: WordId,
218        headword: String,
219        reading: String,
220        normalized_form: String,
221        dictionary_form: String,
222    ) -> Self {
223        WordInfo {
224            data: WordInfoData::new_oov(pos_id, index_form_length),
225            word_id,
226            strings: StringsCache::new_with_strings(
227                headword,
228                reading,
229                normalized_form,
230                dictionary_form,
231            ),
232        }
233    }
234
235    pub fn headword_strptr(&self) -> StringPointer {
236        // provide access to this for the normalized/dictionary form resolution via WorfRef.
237        self.data.headword_strptr()
238    }
239
240    pub fn index_form_length(&self) -> usize {
241        self.data.index_form_length()
242    }
243
244    pub fn pos_id(&self) -> u16 {
245        self.data.pos_id()
246    }
247
248    pub fn headword<T: WordInfoResolver>(&self, resolver: T) -> &str {
249        self.strings.headword(&resolver, &self.data, self.word_id)
250    }
251
252    pub fn reading_form<T: WordInfoResolver>(&self, resolver: T) -> &str {
253        self.strings.reading(&resolver, &self.data, self.word_id)
254    }
255
256    pub fn normalized_form<T: WordInfoResolver>(&self, resolver: T) -> &str {
257        self.strings
258            .normalized_form(&resolver, &self.data, self.word_id)
259    }
260
261    pub fn dictionary_form<T: WordInfoResolver>(&self, resolver: T) -> &str {
262        self.strings
263            .dictionary_form(&resolver, &self.data, self.word_id)
264    }
265
266    pub fn a_unit_split(&self) -> &[WordId] {
267        self.data.a_unit_split()
268    }
269
270    pub fn b_unit_split(&self) -> &[WordId] {
271        self.data.b_unit_split()
272    }
273
274    pub fn c_unit_split(&self) -> &[WordId] {
275        self.data.c_unit_split()
276    }
277
278    pub fn word_structure(&self) -> &[WordId] {
279        self.data.word_structure()
280    }
281
282    pub fn synonym_group_ids(&self) -> &[i32] {
283        self.data.synonym_group_ids()
284    }
285
286    pub fn user_data(&self) -> &str {
287        self.data.user_data()
288    }
289
290    pub fn borrow_data(&self) -> &WordInfoData {
291        &self.data
292    }
293}
294
295impl From<WordInfo> for WordInfoData {
296    fn from(info: WordInfo) -> Self {
297        info.data
298    }
299}