1use std::io;
2
3use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
4use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
5use serde::{Deserialize, Serialize};
6
7use crate::dictionary::character_definition::{CategoryId, CharacterDefinition};
8use crate::dictionary::connection_cost_matrix::ConnectionCostMatrix;
9use crate::dictionary::prefix_dictionary::PrefixDictionary;
10use crate::dictionary::unknown_dictionary::UnknownDictionary;
11use crate::mode::Mode;
12
13#[derive(
15 Clone,
16 Copy,
17 Debug,
18 Eq,
19 PartialEq,
20 Serialize,
21 Deserialize,
22 Default,
23 Archive,
24 RkyvSerialize,
25 RkyvDeserialize,
26)]
27
28pub enum LexType {
29 #[default]
31 System,
32 User,
34 Unknown,
36}
37
38#[derive(
39 Clone,
40 Copy,
41 Debug,
42 Eq,
43 PartialEq,
44 Serialize,
45 Deserialize,
46 Archive,
47 RkyvDeserialize,
48 RkyvSerialize,
49)]
50
51pub struct WordId {
52 id: u32,
54 is_system: bool,
56 lex_type: LexType,
58}
59
60impl WordId {
61 pub fn new(lex_type: LexType, id: u32) -> Self {
63 WordId {
64 id,
65 is_system: matches!(lex_type, LexType::System),
66 lex_type,
67 }
68 }
69
70 #[inline]
76 pub fn id(&self) -> u32 {
77 self.id
78 }
79
80 #[inline]
82 pub fn is_unknown(&self) -> bool {
83 matches!(self.lex_type, LexType::Unknown)
84 }
85
86 #[inline]
88 pub fn is_system(&self) -> bool {
89 self.is_system
90 }
91
92 #[inline]
94 pub fn lex_type(&self) -> LexType {
95 self.lex_type
96 }
97}
98
99impl Default for WordId {
100 fn default() -> Self {
101 WordId {
102 id: u32::MAX,
103 is_system: true,
104 lex_type: LexType::System,
105 }
106 }
107}
108
109#[derive(
110 Default,
111 Clone,
112 Copy,
113 Debug,
114 Eq,
115 PartialEq,
116 Serialize,
117 Deserialize,
118 Archive,
119 RkyvSerialize,
120 RkyvDeserialize,
121)]
122
123pub struct WordEntry {
124 word_id: WordId,
126 word_cost: i16,
128 left_id: u16,
130 right_id: u16,
132}
133
134impl WordEntry {
135 pub(crate) const SERIALIZED_LEN: usize = 10;
137
138 #[inline]
147 pub fn new(word_id: WordId, word_cost: i16, left_id: u16, right_id: u16) -> Self {
148 WordEntry {
149 word_id,
150 word_cost,
151 left_id,
152 right_id,
153 }
154 }
155
156 #[inline]
158 pub fn word_id(&self) -> WordId {
159 self.word_id
160 }
161
162 #[inline]
164 pub fn word_cost(&self) -> i16 {
165 self.word_cost
166 }
167
168 #[inline]
170 pub fn left_id(&self) -> u32 {
171 self.left_id as u32
172 }
173
174 #[inline]
176 pub fn right_id(&self) -> u32 {
177 self.right_id as u32
178 }
179
180 pub(crate) fn serialize<W: io::Write>(&self, wtr: &mut W) -> io::Result<()> {
182 wtr.write_u32::<LittleEndian>(self.word_id.id)?;
183 wtr.write_i16::<LittleEndian>(self.word_cost)?;
184 wtr.write_u16::<LittleEndian>(self.left_id)?;
185 wtr.write_u16::<LittleEndian>(self.right_id)?;
186 Ok(())
187 }
188
189 pub(crate) fn deserialize(data: &[u8], is_system_entry: bool) -> WordEntry {
191 let word_id = WordId::new(
192 if is_system_entry {
193 LexType::System
194 } else {
195 LexType::User
196 },
197 LittleEndian::read_u32(&data[0..4]),
198 );
199 let word_cost = LittleEndian::read_i16(&data[4..6]);
200 let left_id = LittleEndian::read_u16(&data[6..8]);
201 let right_id = LittleEndian::read_u16(&data[8..10]);
202 WordEntry {
203 word_id,
204 word_cost,
205 left_id,
206 right_id,
207 }
208 }
209}
210
211#[derive(Default, Clone, Debug)]
212pub struct Edge {
213 word_entry: WordEntry,
215
216 path_cost: i32,
218 left_index: u16,
220
221 start_index: u32,
223 stop_index: u32,
225
226 kanji_only: bool,
228}
229
230impl Edge {
231 pub fn num_chars(&self) -> usize {
233 (self.stop_index - self.start_index) as usize / 3
234 }
235
236 #[inline]
238 pub(crate) fn word_entry(&self) -> &WordEntry {
239 &self.word_entry
240 }
241
242 #[inline]
244 pub(crate) fn path_cost(&self) -> i32 {
245 self.path_cost
246 }
247
248 #[inline]
250 pub(crate) fn left_index(&self) -> u16 {
251 self.left_index
252 }
253
254 #[inline]
256 pub(crate) fn start_index(&self) -> u32 {
257 self.start_index
258 }
259
260 #[inline]
262 pub(crate) fn stop_index(&self) -> u32 {
263 self.stop_index
264 }
265
266 #[inline]
268 pub(crate) fn kanji_only(&self) -> bool {
269 self.kanji_only
270 }
271}
272
273#[derive(Clone, Debug)]
277pub struct PathEntry {
278 edge_index: u16,
280 left_pos: u32,
282 left_index: u16,
284 cost: i32,
286}
287
288impl PathEntry {
289 #[inline]
291 pub(crate) fn edge_index(&self) -> u16 {
292 self.edge_index
293 }
294
295 #[inline]
297 pub(crate) fn left_pos(&self) -> u32 {
298 self.left_pos
299 }
300
301 #[inline]
303 pub(crate) fn left_index(&self) -> u16 {
304 self.left_index
305 }
306
307 #[inline]
309 pub(crate) fn cost(&self) -> i32 {
310 self.cost
311 }
312}
313
314#[derive(Clone, Default)]
315pub struct Lattice {
316 capacity: usize,
317 ends_at: Vec<Vec<Edge>>, char_info_buffer: Vec<CharData>,
319 categories_buffer: Vec<CategoryId>,
320
321 all_paths: Vec<Vec<PathEntry>>,
323 nbest_capacity: usize,
324 last_text_len: usize,
326
327 matches_head: Vec<u32>,
334 matches_store: Vec<(u32, WordEntry, u32)>,
336}
337
338const PATH_COST_CLAMP: i32 = i32::MAX - 131_072;
342
343#[derive(Clone, Copy, Debug, Default)]
344struct CharData {
345 byte_offset: u32,
346 is_kanji: bool,
347 categories_start: u32,
348 categories_len: u16,
349 kanji_run_byte_len: u32,
350}
351
352#[inline]
353pub fn is_kanji(c: char) -> bool {
354 let c = c as u32;
355 (0x4E00..=0x9FAF).contains(&c) || (0x3400..=0x4DBF).contains(&c)
357}
358
359impl Lattice {
360 #[inline]
362 fn create_edge(word_entry: WordEntry, start: usize, stop: usize, kanji_only: bool) -> Edge {
363 Edge {
364 word_entry,
365 left_index: u16::MAX,
366 start_index: start as u32,
367 stop_index: stop as u32,
368 path_cost: i32::MAX,
369 kanji_only,
370 }
371 }
372
373 pub fn clear(&mut self) {
374 let bound = self.last_text_len + 1;
383 for edge_vec in self.ends_at.iter_mut().take(bound) {
384 edge_vec.clear();
385 }
386 debug_assert!(
387 self.ends_at.iter().skip(bound).all(|v| v.is_empty()),
388 "ends_at slot beyond last_text_len must be empty"
389 );
390 for path_vec in self.all_paths.iter_mut().take(bound) {
391 path_vec.clear();
392 }
393 debug_assert!(
394 self.all_paths.iter().skip(bound).all(|v| v.is_empty()),
395 "all_paths slot beyond last_text_len must be empty"
396 );
397 self.char_info_buffer.clear();
398 self.categories_buffer.clear();
399 }
400
401 #[inline]
402 fn is_kanji_all(&self, char_idx: usize, byte_len: usize) -> bool {
403 self.char_info_buffer[char_idx].kanji_run_byte_len >= byte_len as u32
404 }
405
406 #[inline]
407 fn get_cached_category(&self, char_idx: usize, category_ord: usize) -> CategoryId {
408 let char_data = &self.char_info_buffer[char_idx];
409 self.categories_buffer[char_data.categories_start as usize + category_ord]
410 }
411
412 fn set_capacity(&mut self, text_len: usize) {
413 self.clear();
414 self.last_text_len = text_len;
415 if self.capacity <= text_len {
416 self.capacity = text_len;
417 self.ends_at
424 .resize_with(text_len + 1, || Vec::with_capacity(16));
425 }
426 }
427
428 fn set_capacity_nbest(&mut self, text_len: usize) {
429 self.set_capacity(text_len);
430 if self.nbest_capacity <= text_len {
431 self.nbest_capacity = text_len;
432 self.all_paths.resize(text_len + 1, Vec::new());
433 }
434 }
435
436 pub fn capacity(&self) -> usize {
443 self.capacity
444 }
445
446 pub fn shrink_to(&mut self, text_len: usize) {
468 self.clear();
469 let slots = text_len + 1;
470 if self.capacity > text_len {
471 self.ends_at.truncate(slots);
472 self.ends_at.shrink_to(slots);
473 for slot in &mut self.ends_at {
474 slot.shrink_to(16);
477 }
478 self.capacity = text_len;
479 }
480 if self.nbest_capacity > text_len {
481 self.all_paths.truncate(slots);
482 self.all_paths.shrink_to(slots);
483 for paths in &mut self.all_paths {
484 paths.shrink_to(0);
485 }
486 self.nbest_capacity = text_len;
487 }
488 self.last_text_len = self.last_text_len.min(text_len);
491 self.char_info_buffer.shrink_to(text_len);
496 self.categories_buffer.shrink_to(4 * text_len);
497 self.matches_head.shrink_to(slots);
498 self.matches_store.shrink_to(8 * slots);
499 }
500
501 #[inline(never)]
502 #[allow(clippy::too_many_arguments)]
506 pub fn set_text(
507 &mut self,
508 dict: &PrefixDictionary,
509 user_dict: &Option<&PrefixDictionary>,
510 char_definitions: &CharacterDefinition,
511 unknown_dictionary: &UnknownDictionary,
512 cost_matrix: &ConnectionCostMatrix,
513 text: &str,
514 search_mode: &Mode,
515 ) {
516 let len = text.len();
517 self.set_capacity(len);
518
519 self.char_info_buffer.clear();
521 self.categories_buffer.clear();
522
523 for (byte_offset, c) in text.char_indices() {
524 let categories_start = self.categories_buffer.len() as u32;
525
526 let categories = char_definitions.lookup_categories(c);
530 for &category in categories {
531 self.categories_buffer.push(category);
532 }
533
534 let categories_len = (self.categories_buffer.len() as u32 - categories_start) as u16;
535
536 self.char_info_buffer.push(CharData {
537 byte_offset: byte_offset as u32,
538 is_kanji: is_kanji(c),
539 categories_start,
540 categories_len,
541 kanji_run_byte_len: 0,
542 });
543 }
544 self.char_info_buffer.push(CharData {
546 byte_offset: len as u32,
547 is_kanji: false,
548 categories_start: 0,
549 categories_len: 0,
550 kanji_run_byte_len: 0,
551 });
552
553 for i in (0..self.char_info_buffer.len() - 1).rev() {
555 if self.char_info_buffer[i].is_kanji {
556 let next_byte_offset = self.char_info_buffer[i + 1].byte_offset;
557 let char_byte_len = next_byte_offset - self.char_info_buffer[i].byte_offset;
558 self.char_info_buffer[i].kanji_run_byte_len =
559 char_byte_len + self.char_info_buffer[i + 1].kanji_run_byte_len;
560 } else {
561 self.char_info_buffer[i].kanji_run_byte_len = 0;
562 }
563 }
564
565 let start_edge = Edge {
566 path_cost: 0,
567 left_index: u16::MAX,
568 ..Default::default()
569 };
570 self.ends_at[0].push(start_edge);
571
572 let mut unknown_word_end: Option<usize> = None;
574
575 self.matches_head.clear();
582 self.matches_head.resize(len + 1, u32::MAX);
583 self.matches_store.clear();
584
585 let vals: &[u8] = &dict.vals_data;
587 for m in dict.da.find_overlapping_iter(text) {
588 let start = m.start();
589 let (offset, count) = dict.decode_val(m.value());
590 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
591
592 if start < self.matches_head.len() {
594 let avail = vals.len().saturating_sub(offset_bytes);
597 let n = (count as usize).min(avail / WordEntry::SERIALIZED_LEN);
598 let block = &vals[offset_bytes..offset_bytes + n * WordEntry::SERIALIZED_LEN];
599 let end = m.end() as u32;
600 for chunk in block.chunks_exact(WordEntry::SERIALIZED_LEN) {
601 let entry = WordEntry::deserialize(chunk, true);
602 let next = self.matches_head[start];
603 self.matches_head[start] = self.matches_store.len() as u32;
604 self.matches_store.push((end, entry, next));
605 }
606 }
607 }
608
609 if let Some(ud) = user_dict {
611 let ud_vals: &[u8] = &ud.vals_data;
612 for m in ud.da.find_overlapping_iter(text) {
613 let start = m.start();
614 let (offset, count) = ud.decode_val(m.value());
615 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
616
617 if start < self.matches_head.len() {
618 let avail = ud_vals.len().saturating_sub(offset_bytes);
619 let n = (count as usize).min(avail / WordEntry::SERIALIZED_LEN);
620 let block =
621 &ud_vals[offset_bytes..offset_bytes + n * WordEntry::SERIALIZED_LEN];
622 let end = m.end() as u32;
623 for chunk in block.chunks_exact(WordEntry::SERIALIZED_LEN) {
624 let entry = WordEntry::deserialize(chunk, false);
625 let next = self.matches_head[start];
626 self.matches_head[start] = self.matches_store.len() as u32;
627 self.matches_store.push((end, entry, next));
628 }
629 }
630 }
631 }
632
633 for char_idx in 0..self.char_info_buffer.len() - 1 {
634 let start = self.char_info_buffer[char_idx].byte_offset as usize;
635
636 if self.ends_at[start].is_empty() {
639 continue;
640 }
641
642 let mut found: bool = false;
643
644 if start < self.matches_head.len() {
646 let mut match_idx = self.matches_head[start];
647 while match_idx != u32::MAX {
648 let (end, word_entry, next) = self.matches_store[match_idx as usize];
649
650 let prefix_len = end as usize - start;
651 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
652 let edge = Self::create_edge(
653 word_entry, start,
655 end as usize,
656 kanji_only,
657 );
658 self.add_edge_in_lattice(edge, cost_matrix, search_mode);
659 found = true;
660
661 match_idx = next;
662 }
663 }
664
665 if (search_mode.is_search()
667 || unknown_word_end.map(|index| index <= start).unwrap_or(true))
668 && char_idx < self.char_info_buffer.len() - 1
669 {
670 let num_categories = self.char_info_buffer[char_idx].categories_len as usize;
671 for category_ord in 0..num_categories {
672 let category = self.get_cached_category(char_idx, category_ord);
673 unknown_word_end = self.process_unknown_word(
674 char_definitions,
675 unknown_dictionary,
676 cost_matrix,
677 search_mode,
678 category,
679 category_ord,
680 unknown_word_end,
681 start,
682 char_idx,
683 found,
684 );
685 }
686 }
687 }
688
689 if !self.ends_at[len].is_empty() {
691 let mut eos_edge = Edge {
692 start_index: len as u32,
693 stop_index: len as u32,
694 ..Default::default()
695 };
696 let left_edges = &self.ends_at[len];
698 let mut best_cost = i32::MAX;
699 let mut best_left = None;
700 let cost_row = cost_matrix.row(0); for (i, left_edge) in left_edges.iter().enumerate() {
703 let path_cost =
704 left_edge.path_cost + cost_row[left_edge.word_entry.right_id() as usize] as i32;
705 if path_cost < best_cost {
706 best_cost = path_cost;
707 best_left = Some(i as u16);
708 }
709 }
710 if let Some(left_idx) = best_left {
711 eos_edge.left_index = left_idx;
712 eos_edge.path_cost = best_cost;
713 self.ends_at[len].push(eos_edge);
714 }
715 }
716 }
717
718 #[allow(clippy::too_many_arguments)]
719 fn process_unknown_word(
720 &mut self,
721 char_definitions: &CharacterDefinition,
722 unknown_dictionary: &UnknownDictionary,
723 cost_matrix: &ConnectionCostMatrix,
724 search_mode: &Mode,
725 category: CategoryId,
726 category_ord: usize,
727 unknown_word_index: Option<usize>,
728 start: usize,
729 char_idx: usize,
730 found: bool,
731 ) -> Option<usize> {
732 let mut unknown_word_num_chars: usize = 0;
733 let category_data = char_definitions.lookup_definition(category);
734 if category_data.invoke || !found {
735 unknown_word_num_chars = 1;
736 if category_data.group {
737 for i in 1.. {
738 let next_idx = char_idx + i;
739 if next_idx >= self.char_info_buffer.len() - 1 {
740 break;
741 }
742 let num_categories = self.char_info_buffer[next_idx].categories_len as usize;
743 let mut found_cat = false;
744 if category_ord < num_categories {
745 let cat = self.get_cached_category(next_idx, category_ord);
746 if cat == category {
747 unknown_word_num_chars += 1;
748 found_cat = true;
749 }
750 }
751 if !found_cat {
752 break;
753 }
754 }
755 }
756 }
757 if unknown_word_num_chars > 0 {
758 let byte_end_offset =
759 self.char_info_buffer[char_idx + unknown_word_num_chars].byte_offset;
760 let byte_len = byte_end_offset as usize - start;
761
762 let kanji_only = self.is_kanji_all(char_idx, byte_len);
764
765 for &word_id in unknown_dictionary.lookup_word_ids(category) {
766 let word_entry = unknown_dictionary.word_entry(word_id);
767 let edge = Self::create_edge(word_entry, start, start + byte_len, kanji_only);
768 self.add_edge_in_lattice(edge, cost_matrix, search_mode);
769 }
770 return Some(start + byte_len);
771 }
772 unknown_word_index
773 }
774
775 fn add_edge_in_lattice(
777 &mut self,
778 mut edge: Edge,
779 cost_matrix: &ConnectionCostMatrix,
780 mode: &Mode,
781 ) {
782 let start_index = edge.start_index as usize;
783 let stop_index = edge.stop_index as usize;
784 let right_left_id = edge.word_entry.left_id();
785
786 if self.ends_at[start_index].is_empty() {
787 return;
788 }
789
790 let mut best_cost = i32::MAX;
791 let mut best_left = None;
792
793 match mode {
794 Mode::Normal => {
795 let left_edges = &self.ends_at[start_index];
798 let cost_row = cost_matrix.row(right_left_id);
799 for (i, left_edge) in left_edges.iter().enumerate() {
800 let conn_cost = cost_row[left_edge.word_entry.right_id() as usize] as i32;
801 let total_cost = left_edge.path_cost + conn_cost;
802
803 if total_cost < best_cost {
804 best_cost = total_cost;
805 best_left = Some(i as u16);
806 }
807 }
808 }
809 Mode::Decompose(penalty) => {
810 let left_edges = &self.ends_at[start_index];
811 for (i, left_edge) in left_edges.iter().enumerate() {
812 let left_right_id = left_edge.word_entry.right_id();
813 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
814 let penalty_cost = penalty.penalty(left_edge);
815 let total_cost = left_edge
816 .path_cost
817 .saturating_add(conn_cost)
818 .saturating_add(penalty_cost);
819
820 if total_cost < best_cost {
821 best_cost = total_cost;
822 best_left = Some(i as u16);
823 }
824 }
825 }
826 }
827
828 if let Some(best_left_idx) = best_left {
829 edge.path_cost = best_cost
830 .saturating_add(edge.word_entry.word_cost as i32)
831 .min(PATH_COST_CLAMP);
832 edge.left_index = best_left_idx;
833 self.ends_at[stop_index].push(edge);
834 }
835 }
836
837 pub fn tokens_offset(&self) -> Vec<(usize, WordId)> {
846 let mut offsets = Vec::new();
847 self.tokens_offset_into(&mut offsets);
848 offsets
849 }
850
851 pub fn tokens_offset_into(&self, offsets: &mut Vec<(usize, WordId)>) {
860 offsets.clear();
861
862 if self.ends_at.is_empty() {
863 return;
864 }
865
866 let mut last_idx = self.last_text_len.min(self.ends_at.len() - 1);
871 while last_idx > 0 && self.ends_at[last_idx].is_empty() {
872 last_idx -= 1;
873 }
874
875 if self.ends_at[last_idx].is_empty() {
876 return;
877 }
878
879 let idx = self.ends_at[last_idx].len() - 1;
880 let mut edge = &self.ends_at[last_idx][idx];
881
882 if edge.left_index == u16::MAX {
883 return;
884 }
885
886 loop {
887 if edge.left_index == u16::MAX {
888 break;
889 }
890
891 offsets.push((edge.start_index as usize, edge.word_entry.word_id));
892
893 let left_idx = edge.left_index as usize;
894 let start_idx = edge.start_index as usize;
895
896 edge = &self.ends_at[start_idx][left_idx];
897 }
898
899 offsets.reverse();
900 offsets.pop(); }
902
903 pub fn text_len(&self) -> usize {
907 self.last_text_len
908 }
909
910 pub fn edges_at(&self, byte_pos: usize) -> &[Edge] {
912 &self.ends_at[byte_pos]
913 }
914
915 pub fn paths_at(&self, byte_pos: usize) -> &[PathEntry] {
917 if byte_pos < self.all_paths.len() {
918 &self.all_paths[byte_pos]
919 } else {
920 &[]
921 }
922 }
923
924 fn add_edge_in_lattice_nbest(
926 &mut self,
927 mut edge: Edge,
928 cost_matrix: &ConnectionCostMatrix,
929 mode: &Mode,
930 ) {
931 let start_index = edge.start_index as usize;
932 let stop_index = edge.stop_index as usize;
933 let right_left_id = edge.word_entry.left_id();
934
935 if self.ends_at[start_index].is_empty() {
936 return;
937 }
938
939 let mut best_cost = i32::MAX;
940 let mut best_left = None;
941
942 let new_edge_index = self.ends_at[stop_index].len() as u16;
944
945 match mode {
946 Mode::Normal => {
947 let cost_row = cost_matrix.row(right_left_id);
949 for i in 0..self.ends_at[start_index].len() {
950 let left_edge = &self.ends_at[start_index][i];
951 let total_cost = left_edge.path_cost
952 + cost_row[left_edge.word_entry.right_id() as usize] as i32;
953
954 self.all_paths[stop_index].push(PathEntry {
956 edge_index: new_edge_index,
957 left_pos: start_index as u32,
958 left_index: i as u16,
959 cost: total_cost,
960 });
961
962 if total_cost < best_cost {
963 best_cost = total_cost;
964 best_left = Some(i as u16);
965 }
966 }
967 }
968 Mode::Decompose(penalty) => {
969 for i in 0..self.ends_at[start_index].len() {
970 let left_edge = &self.ends_at[start_index][i];
971 let left_right_id = left_edge.word_entry.right_id();
972 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
973 let penalty_cost = penalty.penalty(left_edge);
974 let total_cost = left_edge
975 .path_cost
976 .saturating_add(conn_cost)
977 .saturating_add(penalty_cost);
978
979 self.all_paths[stop_index].push(PathEntry {
981 edge_index: new_edge_index,
982 left_pos: start_index as u32,
983 left_index: i as u16,
984 cost: total_cost,
985 });
986
987 if total_cost < best_cost {
988 best_cost = total_cost;
989 best_left = Some(i as u16);
990 }
991 }
992 }
993 }
994
995 if let Some(best_left_idx) = best_left {
996 edge.path_cost = best_cost
997 .saturating_add(edge.word_entry.word_cost as i32)
998 .min(PATH_COST_CLAMP);
999 edge.left_index = best_left_idx;
1000 self.ends_at[stop_index].push(edge);
1001 }
1002 }
1003
1004 #[allow(clippy::too_many_arguments)]
1005 fn process_unknown_word_nbest(
1006 &mut self,
1007 char_definitions: &CharacterDefinition,
1008 unknown_dictionary: &UnknownDictionary,
1009 cost_matrix: &ConnectionCostMatrix,
1010 search_mode: &Mode,
1011 category: CategoryId,
1012 category_ord: usize,
1013 unknown_word_index: Option<usize>,
1014 start: usize,
1015 char_idx: usize,
1016 found: bool,
1017 ) -> Option<usize> {
1018 let mut unknown_word_num_chars: usize = 0;
1019 let category_data = char_definitions.lookup_definition(category);
1020 if category_data.invoke || !found {
1021 unknown_word_num_chars = 1;
1022 if category_data.group {
1023 for i in 1.. {
1024 let next_idx = char_idx + i;
1025 if next_idx >= self.char_info_buffer.len() - 1 {
1026 break;
1027 }
1028 let num_categories = self.char_info_buffer[next_idx].categories_len as usize;
1029 let mut found_cat = false;
1030 if category_ord < num_categories {
1031 let cat = self.get_cached_category(next_idx, category_ord);
1032 if cat == category {
1033 unknown_word_num_chars += 1;
1034 found_cat = true;
1035 }
1036 }
1037 if !found_cat {
1038 break;
1039 }
1040 }
1041 }
1042 }
1043 if unknown_word_num_chars > 0 {
1044 let byte_end_offset =
1045 self.char_info_buffer[char_idx + unknown_word_num_chars].byte_offset;
1046 let byte_len = byte_end_offset as usize - start;
1047
1048 let kanji_only = self.is_kanji_all(char_idx, byte_len);
1049
1050 for &word_id in unknown_dictionary.lookup_word_ids(category) {
1051 let word_entry = unknown_dictionary.word_entry(word_id);
1052 let edge = Self::create_edge(word_entry, start, start + byte_len, kanji_only);
1053 self.add_edge_in_lattice_nbest(edge, cost_matrix, search_mode);
1054 }
1055 return Some(start + byte_len);
1056 }
1057 unknown_word_index
1058 }
1059
1060 #[inline(never)]
1063 #[allow(clippy::too_many_arguments)]
1064 pub fn set_text_nbest(
1065 &mut self,
1066 dict: &PrefixDictionary,
1067 user_dict: &Option<&PrefixDictionary>,
1068 char_definitions: &CharacterDefinition,
1069 unknown_dictionary: &UnknownDictionary,
1070 cost_matrix: &ConnectionCostMatrix,
1071 text: &str,
1072 search_mode: &Mode,
1073 ) {
1074 let len = text.len();
1075 self.set_capacity_nbest(len);
1076
1077 self.char_info_buffer.clear();
1079 self.categories_buffer.clear();
1080
1081 for (byte_offset, c) in text.char_indices() {
1082 let categories_start = self.categories_buffer.len() as u32;
1083
1084 let categories = char_definitions.lookup_categories(c);
1088 for &category in categories {
1089 self.categories_buffer.push(category);
1090 }
1091
1092 let categories_len = (self.categories_buffer.len() as u32 - categories_start) as u16;
1093
1094 self.char_info_buffer.push(CharData {
1095 byte_offset: byte_offset as u32,
1096 is_kanji: is_kanji(c),
1097 categories_start,
1098 categories_len,
1099 kanji_run_byte_len: 0,
1100 });
1101 }
1102 self.char_info_buffer.push(CharData {
1104 byte_offset: len as u32,
1105 is_kanji: false,
1106 categories_start: 0,
1107 categories_len: 0,
1108 kanji_run_byte_len: 0,
1109 });
1110
1111 for i in (0..self.char_info_buffer.len() - 1).rev() {
1113 if self.char_info_buffer[i].is_kanji {
1114 let next_byte_offset = self.char_info_buffer[i + 1].byte_offset;
1115 let char_byte_len = next_byte_offset - self.char_info_buffer[i].byte_offset;
1116 self.char_info_buffer[i].kanji_run_byte_len =
1117 char_byte_len + self.char_info_buffer[i + 1].kanji_run_byte_len;
1118 } else {
1119 self.char_info_buffer[i].kanji_run_byte_len = 0;
1120 }
1121 }
1122
1123 let start_edge = Edge {
1124 path_cost: 0,
1125 left_index: u16::MAX,
1126 ..Default::default()
1127 };
1128 self.ends_at[0].push(start_edge);
1129
1130 let mut unknown_word_end: Option<usize> = None;
1131
1132 self.matches_head.clear();
1137 self.matches_head.resize(len + 1, u32::MAX);
1138 self.matches_store.clear();
1139
1140 let vals: &[u8] = &dict.vals_data;
1142 for m in dict.da.find_overlapping_iter(text) {
1143 let start = m.start();
1144 let (offset, count) = dict.decode_val(m.value());
1145 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
1146
1147 if start < self.matches_head.len() {
1148 let avail = vals.len().saturating_sub(offset_bytes);
1151 let n = (count as usize).min(avail / WordEntry::SERIALIZED_LEN);
1152 let block = &vals[offset_bytes..offset_bytes + n * WordEntry::SERIALIZED_LEN];
1153 let end = m.end() as u32;
1154 for chunk in block.chunks_exact(WordEntry::SERIALIZED_LEN) {
1155 let entry = WordEntry::deserialize(chunk, true);
1156 let next = self.matches_head[start];
1157 self.matches_head[start] = self.matches_store.len() as u32;
1158 self.matches_store.push((end, entry, next));
1159 }
1160 }
1161 }
1162
1163 if let Some(ud) = user_dict {
1165 let ud_vals: &[u8] = &ud.vals_data;
1166 for m in ud.da.find_overlapping_iter(text) {
1167 let start = m.start();
1168 let (offset, count) = ud.decode_val(m.value());
1169 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
1170
1171 if start < self.matches_head.len() {
1172 let avail = ud_vals.len().saturating_sub(offset_bytes);
1173 let n = (count as usize).min(avail / WordEntry::SERIALIZED_LEN);
1174 let block =
1175 &ud_vals[offset_bytes..offset_bytes + n * WordEntry::SERIALIZED_LEN];
1176 let end = m.end() as u32;
1177 for chunk in block.chunks_exact(WordEntry::SERIALIZED_LEN) {
1178 let entry = WordEntry::deserialize(chunk, false);
1179 let next = self.matches_head[start];
1180 self.matches_head[start] = self.matches_store.len() as u32;
1181 self.matches_store.push((end, entry, next));
1182 }
1183 }
1184 }
1185 }
1186
1187 for char_idx in 0..self.char_info_buffer.len() - 1 {
1188 let start = self.char_info_buffer[char_idx].byte_offset as usize;
1189
1190 if self.ends_at[start].is_empty() {
1191 continue;
1192 }
1193
1194 let mut found: bool = false;
1195
1196 if start < self.matches_head.len() {
1197 let mut match_idx = self.matches_head[start];
1198 while match_idx != u32::MAX {
1199 let (end, word_entry, next) = self.matches_store[match_idx as usize];
1200
1201 let prefix_len = end as usize - start;
1202 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
1203 let edge = Self::create_edge(word_entry, start, end as usize, kanji_only);
1204 self.add_edge_in_lattice_nbest(edge, cost_matrix, search_mode);
1205 found = true;
1206
1207 match_idx = next;
1208 }
1209 }
1210
1211 if (search_mode.is_search()
1212 || unknown_word_end.map(|index| index <= start).unwrap_or(true))
1213 && char_idx < self.char_info_buffer.len() - 1
1214 {
1215 let num_categories = self.char_info_buffer[char_idx].categories_len as usize;
1216 for category_ord in 0..num_categories {
1217 let category = self.get_cached_category(char_idx, category_ord);
1218 unknown_word_end = self.process_unknown_word_nbest(
1219 char_definitions,
1220 unknown_dictionary,
1221 cost_matrix,
1222 search_mode,
1223 category,
1224 category_ord,
1225 unknown_word_end,
1226 start,
1227 char_idx,
1228 found,
1229 );
1230 }
1231 }
1232 }
1233
1234 if !self.ends_at[len].is_empty() {
1236 let eos_edge_index = self.ends_at[len].len() as u16;
1237 let mut eos_edge = Edge {
1238 start_index: len as u32,
1239 stop_index: len as u32,
1240 ..Default::default()
1241 };
1242 let mut best_cost = i32::MAX;
1243 let mut best_left = None;
1244 let cost_row = cost_matrix.row(0); for i in 0..self.ends_at[len].len() {
1247 let left_edge = &self.ends_at[len][i];
1248 let path_cost =
1249 left_edge.path_cost + cost_row[left_edge.word_entry.right_id() as usize] as i32;
1250
1251 self.all_paths[len].push(PathEntry {
1253 edge_index: eos_edge_index,
1254 left_pos: len as u32,
1255 left_index: i as u16,
1256 cost: path_cost,
1257 });
1258
1259 if path_cost < best_cost {
1260 best_cost = path_cost;
1261 best_left = Some(i as u16);
1262 }
1263 }
1264 if let Some(left_idx) = best_left {
1265 eos_edge.left_index = left_idx;
1266 eos_edge.path_cost = best_cost;
1267 self.ends_at[len].push(eos_edge);
1268 }
1269 }
1270 }
1271
1272 pub fn nbest_tokens_offset(
1280 &self,
1281 n: usize,
1282 unique: bool,
1283 cost_threshold: Option<i64>,
1284 ) -> Vec<(Vec<(usize, WordId)>, i64)> {
1285 use std::collections::HashSet;
1286
1287 use crate::nbest::NBestGenerator;
1288 let mut generator = NBestGenerator::new(self);
1289 let mut results = Vec::with_capacity(n);
1290 let mut best_cost: Option<i64> = None;
1291
1292 if unique {
1293 let mut seen: HashSet<Vec<usize>> = HashSet::new();
1294 while results.len() < n {
1295 match generator.next() {
1296 Some((path, cost)) => {
1297 let bc = *best_cost.get_or_insert(cost);
1299 if let Some(threshold) = cost_threshold
1301 && cost > bc + threshold
1302 {
1303 break;
1304 }
1305 let key: Vec<usize> = path.iter().map(|(start, _)| *start).collect();
1306 if seen.insert(key) {
1307 results.push((path, cost));
1308 }
1309 }
1310 None => break,
1311 }
1312 }
1313 } else {
1314 while results.len() < n {
1315 match generator.next() {
1316 Some((path, cost)) => {
1317 let bc = *best_cost.get_or_insert(cost);
1318 if let Some(threshold) = cost_threshold
1319 && cost > bc + threshold
1320 {
1321 break;
1322 }
1323 results.push((path, cost));
1324 }
1325 None => break,
1326 }
1327 }
1328 }
1329 results
1330 }
1331}
1332
1333#[cfg(test)]
1334mod tests {
1335 use crate::viterbi::{Edge, Lattice, LexType, WordEntry, WordId};
1336
1337 fn test_edge(word_id: u32, start: usize, stop: usize, left_index: u16) -> Edge {
1340 let mut edge = Lattice::create_edge(
1341 WordEntry::new(WordId::new(LexType::System, word_id), 0, 0, 0),
1342 start,
1343 stop,
1344 false,
1345 );
1346 edge.left_index = left_index;
1347 edge.path_cost = 0;
1348 edge
1349 }
1350
1351 #[test]
1352 fn test_word_entry() {
1353 let mut buffer = Vec::new();
1354 let word_entry =
1355 WordEntry::new(WordId::new(LexType::System, 1u32), -17i16, 1411u16, 1412u16);
1356 word_entry.serialize(&mut buffer).unwrap();
1357 assert_eq!(WordEntry::SERIALIZED_LEN, buffer.len());
1358 let word_entry2 = WordEntry::deserialize(&buffer[..], true);
1359 assert_eq!(word_entry, word_entry2);
1360 }
1361
1362 #[test]
1368 fn test_set_capacity_presizes_all_new_slots() {
1369 let mut lattice = Lattice::default();
1370
1371 lattice.set_capacity(5);
1372 assert_eq!(lattice.ends_at.len(), 6);
1373 for (i, slot) in lattice.ends_at.iter().enumerate() {
1374 assert!(
1375 slot.capacity() >= 16,
1376 "slot {} has capacity {} < 16 after initial growth",
1377 i,
1378 slot.capacity()
1379 );
1380 }
1381
1382 lattice.set_capacity(10);
1384 assert_eq!(lattice.ends_at.len(), 11);
1385 for (i, slot) in lattice.ends_at.iter().enumerate() {
1386 assert!(
1387 slot.capacity() >= 16,
1388 "slot {} has capacity {} < 16 after second growth",
1389 i,
1390 slot.capacity()
1391 );
1392 }
1393 }
1394
1395 #[test]
1400 fn test_clear_after_shrink_leaves_no_stale_edges() {
1401 let mut lattice = Lattice::default();
1402
1403 lattice.set_capacity(100);
1405 lattice.ends_at[0].push(test_edge(1, 0, 0, u16::MAX));
1406 lattice.ends_at[57].push(test_edge(2, 0, 57, 0));
1407 lattice.ends_at[100].push(test_edge(3, 57, 100, 0)); lattice.set_capacity(10);
1412 assert!(
1413 lattice.ends_at.iter().all(|v| v.is_empty()),
1414 "stale edges survived a bounded clear"
1415 );
1416
1417 lattice.ends_at[10].push(test_edge(4, 0, 10, 0)); lattice.set_capacity(3);
1421 assert!(
1422 lattice.ends_at.iter().all(|v| v.is_empty()),
1423 "stale edge at the previous boundary slot survived"
1424 );
1425 }
1426
1427 #[test]
1431 fn test_tokens_offset_finds_eos_at_last_text_len_after_shrink() {
1432 let mut lattice = Lattice::default();
1433
1434 lattice.set_capacity(100);
1436
1437 lattice.set_capacity(3);
1440 lattice.ends_at[0].push(test_edge(0, 0, 0, u16::MAX)); lattice.ends_at[3].push(test_edge(42, 0, 3, 0)); lattice.ends_at[3].push(test_edge(0, 3, 3, 0)); let offsets = lattice.tokens_offset();
1445 assert_eq!(offsets.len(), 1);
1446 assert_eq!(offsets[0].0, 0);
1447 assert_eq!(offsets[0].1, WordId::new(LexType::System, 42));
1448 }
1449
1450 #[test]
1454 fn test_shrink_to_truncates_and_keeps_presize() {
1455 let mut lattice = Lattice::default();
1456
1457 lattice.set_capacity(100);
1458 lattice.ends_at[0].push(test_edge(1, 0, 0, u16::MAX));
1459 lattice.ends_at[100].push(test_edge(2, 0, 100, 0));
1460
1461 lattice.shrink_to(10);
1462 assert_eq!(lattice.capacity(), 10);
1463 assert_eq!(lattice.ends_at.len(), 11);
1464 assert!(
1465 lattice.ends_at.iter().all(|v| v.is_empty()),
1466 "shrink_to must clear all slots"
1467 );
1468 for (i, slot) in lattice.ends_at.iter().enumerate() {
1469 assert!(
1470 slot.capacity() >= 16,
1471 "slot {} lost its pre-size after shrink_to (capacity {})",
1472 i,
1473 slot.capacity()
1474 );
1475 }
1476
1477 lattice.set_capacity(50);
1479 assert_eq!(lattice.ends_at.len(), 51);
1480 for (i, slot) in lattice.ends_at.iter().enumerate() {
1481 assert!(
1482 slot.capacity() >= 16,
1483 "slot {} not pre-sized after regrowth (capacity {})",
1484 i,
1485 slot.capacity()
1486 );
1487 }
1488 }
1489
1490 #[test]
1493 fn test_shrink_to_noop_when_target_not_smaller() {
1494 let mut lattice = Lattice::default();
1495 lattice.set_capacity(5);
1496
1497 lattice.shrink_to(100);
1498 assert_eq!(lattice.capacity(), 5);
1499 assert_eq!(lattice.ends_at.len(), 6);
1500
1501 lattice.shrink_to(5);
1502 assert_eq!(lattice.capacity(), 5);
1503 assert_eq!(lattice.ends_at.len(), 6);
1504
1505 let mut fresh = Lattice::default();
1507 fresh.shrink_to(0);
1508 assert_eq!(fresh.capacity(), 0);
1509 assert!(fresh.ends_at.is_empty());
1510 }
1511
1512 #[test]
1517 fn test_backtrace_works_after_shrink_to() {
1518 let mut lattice = Lattice::default();
1519 lattice.set_capacity(100);
1520 lattice.ends_at[0].push(test_edge(1, 0, 0, u16::MAX));
1521 lattice.ends_at[100].push(test_edge(2, 0, 100, 0));
1522
1523 lattice.shrink_to(10);
1524
1525 lattice.set_capacity(3);
1527 lattice.ends_at[0].push(test_edge(0, 0, 0, u16::MAX)); lattice.ends_at[3].push(test_edge(42, 0, 3, 0)); lattice.ends_at[3].push(test_edge(0, 3, 3, 0)); let offsets = lattice.tokens_offset();
1532 assert_eq!(offsets.len(), 1);
1533 assert_eq!(offsets[0].0, 0);
1534 assert_eq!(offsets[0].1, WordId::new(LexType::System, 42));
1535
1536 lattice.clear();
1538 assert!(lattice.ends_at.iter().all(|v| v.is_empty()));
1539 }
1540
1541 #[test]
1543 fn test_shrink_to_releases_nbest_paths() {
1544 let mut lattice = Lattice::default();
1545 lattice.set_capacity_nbest(100);
1546 assert_eq!(lattice.all_paths.len(), 101);
1547
1548 lattice.shrink_to(10);
1549 assert_eq!(lattice.all_paths.len(), 11);
1550 assert_eq!(lattice.nbest_capacity, 10);
1551 assert!(lattice.all_paths.iter().all(|v| v.is_empty()));
1552
1553 lattice.set_capacity_nbest(20);
1555 assert_eq!(lattice.all_paths.len(), 21);
1556 }
1557
1558 #[test]
1561 fn test_tokens_offset_into_matches_tokens_offset() {
1562 let mut lattice = Lattice::default();
1563 lattice.set_capacity(3);
1564 lattice.ends_at[0].push(test_edge(0, 0, 0, u16::MAX)); lattice.ends_at[3].push(test_edge(7, 0, 3, 0)); lattice.ends_at[3].push(test_edge(0, 3, 3, 0)); let mut reused = vec![(999usize, WordId::default())]; lattice.tokens_offset_into(&mut reused);
1570 assert_eq!(reused, lattice.tokens_offset());
1571 assert_eq!(reused.len(), 1);
1572
1573 lattice.clear();
1575 lattice.tokens_offset_into(&mut reused);
1576 assert!(reused.is_empty());
1577 assert!(lattice.tokens_offset().is_empty());
1578 }
1579}