1use std::cmp;
18
19use self::trie::Trie;
20use self::word_id_table::WordIdTable;
21use self::word_params::WordParams;
22use crate::analysis::stateful_tokenizer::StatefulTokenizer;
23use crate::dic::binary_loader::BinaryLexicon;
24use crate::dic::lexicon::strings::CompactedStrings;
25use crate::dic::subset::InfoSubset;
26use crate::dic::word_id::{EntryId, WordId};
27use crate::dic::word_info::{WordInfoEntryIdCursor, WordInfoRefData, WordInfos};
28use crate::dic::DictionaryAccess;
29use crate::prelude::*;
30
31pub mod strings;
32pub mod trie;
33pub mod word_id_table;
34pub mod word_infos;
35pub mod word_params;
36
37pub const MAX_DICTIONARIES: usize = 15;
41
42pub struct Lexicon<'a> {
46 lex_id: u8,
47
48 trie: Trie<'a>,
49 word_id_table: WordIdTable<'a>,
50 word_params: WordParams<'a>,
51 word_infos: WordInfos<'a>,
52 strings: CompactedStrings<'a>,
53
54 num_total_entries: u32,
55}
56
57impl<'a> Lexicon<'a> {
58 const USER_DICT_COST_PER_MORPH: i32 = -20;
59
60 pub fn from_binary(binary_lexicon: BinaryLexicon<'a>) -> Self {
61 Self {
62 trie: binary_lexicon.trie,
63 word_id_table: binary_lexicon.word_id_table,
64 word_params: binary_lexicon.word_params,
65 word_infos: binary_lexicon.word_infos,
66 strings: binary_lexicon.strings,
67 lex_id: u8::MAX,
68 num_total_entries: binary_lexicon.num_total_entries,
69 }
70 }
71
72 pub fn size(&self) -> u32 {
74 self.num_total_entries
75 }
76
77 pub fn entry_ids_in_order(&self) -> Vec<EntryId> {
78 match self.word_infos.entry_ids_in_order(self.num_total_entries) {
79 Some(result) => result,
80 None => {
81 let mut result: Vec<EntryId> = self.word_id_table.all_entries().collect();
83 result.sort_unstable();
84 result
85 }
86 }
87 }
88
89 pub(crate) fn entry_ids(&self) -> impl Iterator<Item = SudachiResult<EntryId>> + '_ {
90 self.word_infos.entry_ids(self.num_total_entries)
91 }
92
93 pub(crate) fn entry_id_cursor(&self) -> WordInfoEntryIdCursor {
94 WordInfos::entry_id_cursor(self.num_total_entries)
95 }
96
97 pub(crate) fn next_entry_id(
98 &self,
99 cursor: &mut WordInfoEntryIdCursor,
100 ) -> SudachiResult<Option<EntryId>> {
101 self.word_infos.next_entry_id(cursor)
102 }
103
104 pub fn set_dic_id(&mut self, id: u8) {
106 assert!(id < MAX_DICTIONARIES as u8);
107 self.lex_id = id
108 }
109
110 #[inline]
111 fn word_id(&self, entry_id: u32) -> WordId {
112 WordId::new(self.lex_id, entry_id)
113 }
114
115 #[inline]
117 pub fn lookup(
118 &'a self,
119 input: &'a [u8],
120 offset: usize,
121 ) -> impl Iterator<Item = LexiconEntry> + 'a {
122 debug_assert!(self.lex_id < MAX_DICTIONARIES as u8);
123 self.trie
124 .common_prefix_iterator(input, offset)
125 .flat_map(move |e| {
126 self.word_id_table
127 .entries(e.value as usize)
128 .map(move |eid| LexiconEntry::new(self.word_id(eid.as_raw()), e.end))
129 })
130 }
131
132 #[inline]
138 pub fn lookup_batch<F: FnMut(usize, LexiconEntry)>(
139 &self,
140 input: &[u8],
141 starts: &[usize],
142 mut emit: F,
143 ) {
144 debug_assert!(self.lex_id < MAX_DICTIONARIES as u8);
145 self.trie
146 .common_prefix_batch(input, starts, |bucket, value, end| {
147 for eid in self.word_id_table.entries(value as usize) {
148 emit(bucket, LexiconEntry::new(self.word_id(eid.as_raw()), end));
149 }
150 });
151 }
152
153 #[inline]
155 pub(crate) fn lookup_prefix_ends(
156 &'a self,
157 input: &'a [u8],
158 offset: usize,
159 ) -> impl Iterator<Item = usize> + 'a {
160 self.trie
161 .common_prefix_iterator(input, offset)
162 .map(|entry| entry.end)
163 }
164
165 pub fn get_word_info(
169 &self,
170 entry_id: EntryId,
171 subset: InfoSubset,
172 ) -> SudachiResult<WordInfoRefData> {
173 self.word_infos.get_word_info(entry_id, subset)
174 }
175
176 #[inline]
179 pub fn get_word_param(&self, entry_id: EntryId) -> (i16, i16, i16) {
180 let params = self.word_params.get_params(entry_id);
181 (params.left_id(), params.right_id(), params.cost())
182 }
183
184 pub fn get_word_param_checked(&self, entry_id: EntryId) -> Option<(i16, i16, i16)> {
185 let params = self.word_params.get_params_checked(entry_id)?;
186 Some((params.left_id(), params.right_id(), params.cost()))
187 }
188
189 #[inline]
190 pub fn get_string(&self, strptr: strings::StringPointer) -> SudachiResult<String> {
191 self.strings.get_string(strptr)
192 }
193
194 pub fn update_cost<D: DictionaryAccess>(&mut self, dict: &D) -> SudachiResult<()> {
196 let mut tok = StatefulTokenizer::create(dict, false, Mode::C);
197 let mut ms = MorphemeList::empty(dict);
198
199 for entry_id in self.word_id_table.all_entries() {
200 if self.word_params.get_cost(entry_id) != i16::MIN {
201 continue;
202 }
203 let wi = self.get_word_info(entry_id, InfoSubset::HEADWORD)?;
205 tok.reset()
206 .push_str(self.strings.get_string(wi.headword_strptr())?.as_str());
207 tok.do_tokenize()?;
208 ms.collect_results(&mut tok)?;
209 let internal_cost = ms.get_internal_cost();
210 let cost = internal_cost + Lexicon::USER_DICT_COST_PER_MORPH * ms.len() as i32;
211 let cost = cmp::min(cost, i16::MAX as i32);
212 let cost = cmp::max(cost, i16::MIN as i32);
213 self.word_params.set_cost(entry_id, cost as i16);
214 }
215
216 Ok(())
217 }
218}
219
220#[derive(Eq, PartialEq, Debug)]
222pub struct LexiconEntry {
223 pub word_id: WordId,
225 pub end: usize,
227}
228
229impl LexiconEntry {
230 pub fn new(word_id: WordId, end: usize) -> LexiconEntry {
231 LexiconEntry { word_id, end }
232 }
233}