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, UserPrefixDictionary};
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 chars_buf: Vec<char>,
340 sys_matches: Vec<(u32, WordEntry)>,
345}
346
347const PATH_COST_CLAMP: i32 = i32::MAX - 131_072;
351
352#[derive(Clone, Copy, Debug, Default)]
353struct CharData {
354 byte_offset: u32,
355 is_kanji: bool,
356 categories_start: u32,
357 categories_len: u16,
358 kanji_run_byte_len: u32,
359}
360
361#[inline]
362pub fn is_kanji(c: char) -> bool {
363 let c = c as u32;
364 (0x4E00..=0x9FAF).contains(&c) || (0x3400..=0x4DBF).contains(&c)
366}
367
368impl Lattice {
369 #[inline]
371 fn create_edge(word_entry: WordEntry, start: usize, stop: usize, kanji_only: bool) -> Edge {
372 Edge {
373 word_entry,
374 left_index: u16::MAX,
375 start_index: start as u32,
376 stop_index: stop as u32,
377 path_cost: i32::MAX,
378 kanji_only,
379 }
380 }
381
382 pub fn clear(&mut self) {
383 let bound = self.last_text_len + 1;
392 for edge_vec in self.ends_at.iter_mut().take(bound) {
393 edge_vec.clear();
394 }
395 debug_assert!(
396 self.ends_at.iter().skip(bound).all(|v| v.is_empty()),
397 "ends_at slot beyond last_text_len must be empty"
398 );
399 for path_vec in self.all_paths.iter_mut().take(bound) {
400 path_vec.clear();
401 }
402 debug_assert!(
403 self.all_paths.iter().skip(bound).all(|v| v.is_empty()),
404 "all_paths slot beyond last_text_len must be empty"
405 );
406 self.char_info_buffer.clear();
407 self.categories_buffer.clear();
408 }
409
410 #[inline]
411 fn is_kanji_all(&self, char_idx: usize, byte_len: usize) -> bool {
412 self.char_info_buffer[char_idx].kanji_run_byte_len >= byte_len as u32
413 }
414
415 #[inline]
416 fn get_cached_category(&self, char_idx: usize, category_ord: usize) -> CategoryId {
417 let char_data = &self.char_info_buffer[char_idx];
418 self.categories_buffer[char_data.categories_start as usize + category_ord]
419 }
420
421 fn set_capacity(&mut self, text_len: usize) {
422 self.clear();
423 self.last_text_len = text_len;
424 if self.capacity <= text_len {
425 self.capacity = text_len;
426 self.ends_at
433 .resize_with(text_len + 1, || Vec::with_capacity(16));
434 }
435 }
436
437 fn set_capacity_nbest(&mut self, text_len: usize) {
438 self.set_capacity(text_len);
439 if self.nbest_capacity <= text_len {
440 self.nbest_capacity = text_len;
441 self.all_paths.resize(text_len + 1, Vec::new());
442 }
443 }
444
445 pub fn capacity(&self) -> usize {
452 self.capacity
453 }
454
455 pub fn shrink_to(&mut self, text_len: usize) {
477 self.clear();
478 let slots = text_len + 1;
479 if self.capacity > text_len {
480 self.ends_at.truncate(slots);
481 self.ends_at.shrink_to(slots);
482 for slot in &mut self.ends_at {
483 slot.shrink_to(16);
486 }
487 self.capacity = text_len;
488 }
489 if self.nbest_capacity > text_len {
490 self.all_paths.truncate(slots);
491 self.all_paths.shrink_to(slots);
492 for paths in &mut self.all_paths {
493 paths.shrink_to(0);
494 }
495 self.nbest_capacity = text_len;
496 }
497 self.last_text_len = self.last_text_len.min(text_len);
500 self.char_info_buffer.shrink_to(text_len);
505 self.categories_buffer.shrink_to(4 * text_len);
506 self.matches_head.shrink_to(slots);
507 self.matches_store.shrink_to(8 * slots);
508 self.chars_buf.shrink_to(text_len);
509 self.sys_matches.shrink_to(64);
510 }
511
512 #[inline(never)]
513 #[allow(clippy::too_many_arguments)]
517 pub fn set_text(
518 &mut self,
519 dict: &PrefixDictionary,
520 user_dict: &Option<&UserPrefixDictionary>,
521 char_definitions: &CharacterDefinition,
522 unknown_dictionary: &UnknownDictionary,
523 cost_matrix: &ConnectionCostMatrix,
524 text: &str,
525 search_mode: &Mode,
526 ) {
527 let len = text.len();
528 self.set_capacity(len);
529
530 self.char_info_buffer.clear();
532 self.categories_buffer.clear();
533 self.chars_buf.clear();
534
535 for (byte_offset, c) in text.char_indices() {
536 let categories_start = self.categories_buffer.len() as u32;
537
538 let categories = char_definitions.lookup_categories(c);
542 for &category in categories {
543 self.categories_buffer.push(category);
544 }
545
546 let categories_len = (self.categories_buffer.len() as u32 - categories_start) as u16;
547
548 self.char_info_buffer.push(CharData {
549 byte_offset: byte_offset as u32,
550 is_kanji: is_kanji(c),
551 categories_start,
552 categories_len,
553 kanji_run_byte_len: 0,
554 });
555 self.chars_buf.push(c);
556 }
557 self.char_info_buffer.push(CharData {
559 byte_offset: len as u32,
560 is_kanji: false,
561 categories_start: 0,
562 categories_len: 0,
563 kanji_run_byte_len: 0,
564 });
565
566 for i in (0..self.char_info_buffer.len() - 1).rev() {
568 if self.char_info_buffer[i].is_kanji {
569 let next_byte_offset = self.char_info_buffer[i + 1].byte_offset;
570 let char_byte_len = next_byte_offset - self.char_info_buffer[i].byte_offset;
571 self.char_info_buffer[i].kanji_run_byte_len =
572 char_byte_len + self.char_info_buffer[i + 1].kanji_run_byte_len;
573 } else {
574 self.char_info_buffer[i].kanji_run_byte_len = 0;
575 }
576 }
577
578 let start_edge = Edge {
579 path_cost: 0,
580 left_index: u16::MAX,
581 ..Default::default()
582 };
583 self.ends_at[0].push(start_edge);
584
585 let mut unknown_word_end: Option<usize> = None;
587
588 self.matches_head.clear();
601 self.matches_store.clear();
602
603 if let Some(ud) = user_dict {
605 self.matches_head.resize(len + 1, u32::MAX);
606 let ud_vals: &[u8] = &ud.vals_data;
607 for m in ud.da.find_overlapping_iter(text) {
608 let start = m.start();
609 let (offset, count) = ud.decode_val(m.value());
610 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
611
612 if start < self.matches_head.len() {
613 let avail = ud_vals.len().saturating_sub(offset_bytes);
614 let n = (count as usize).min(avail / WordEntry::SERIALIZED_LEN);
615 let block =
616 &ud_vals[offset_bytes..offset_bytes + n * WordEntry::SERIALIZED_LEN];
617 let end = m.end() as u32;
618 for chunk in block.chunks_exact(WordEntry::SERIALIZED_LEN) {
619 let entry = WordEntry::deserialize(chunk, false);
620 let next = self.matches_head[start];
621 self.matches_head[start] = self.matches_store.len() as u32;
622 self.matches_store.push((end, entry, next));
623 }
624 }
625 }
626 }
627
628 for char_idx in 0..self.char_info_buffer.len() - 1 {
629 let start = self.char_info_buffer[char_idx].byte_offset as usize;
630
631 if self.ends_at[start].is_empty() {
634 continue;
635 }
636
637 let mut found: bool = false;
638
639 if start < self.matches_head.len() {
642 let mut match_idx = self.matches_head[start];
643 while match_idx != u32::MAX {
644 let (end, word_entry, next) = self.matches_store[match_idx as usize];
645
646 let prefix_len = end as usize - start;
647 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
648 let edge = Self::create_edge(
649 word_entry, start,
651 end as usize,
652 kanji_only,
653 );
654 self.add_edge_in_lattice(edge, cost_matrix, search_mode);
655 found = true;
656
657 match_idx = next;
658 }
659 }
660
661 self.sys_matches.clear();
668 {
669 let suffix = &self.chars_buf[char_idx..];
670 for (entries, end_char_offset) in dict.common_prefix_search(suffix) {
671 let end_char_idx = char_idx + end_char_offset;
672 let end = self.char_info_buffer[end_char_idx].byte_offset;
673 for chunk in entries.chunks_exact(WordEntry::SERIALIZED_LEN) {
674 self.sys_matches
675 .push((end, WordEntry::deserialize(chunk, true)));
676 }
677 }
678 }
679 for i in (0..self.sys_matches.len()).rev() {
680 let (end, word_entry) = self.sys_matches[i];
681 let end = end as usize;
682 let prefix_len = end - start;
683 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
684 let edge = Self::create_edge(word_entry, start, end, kanji_only);
685 self.add_edge_in_lattice(edge, cost_matrix, search_mode);
686 found = true;
687 }
688
689 if (search_mode.is_search()
691 || unknown_word_end.map(|index| index <= start).unwrap_or(true))
692 && char_idx < self.char_info_buffer.len() - 1
693 {
694 let num_categories = self.char_info_buffer[char_idx].categories_len as usize;
695 for category_ord in 0..num_categories {
696 let category = self.get_cached_category(char_idx, category_ord);
697 unknown_word_end = self.process_unknown_word(
698 char_definitions,
699 unknown_dictionary,
700 cost_matrix,
701 search_mode,
702 category,
703 category_ord,
704 unknown_word_end,
705 start,
706 char_idx,
707 found,
708 );
709 }
710 }
711 }
712
713 if !self.ends_at[len].is_empty() {
715 let mut eos_edge = Edge {
716 start_index: len as u32,
717 stop_index: len as u32,
718 ..Default::default()
719 };
720 let left_edges = &self.ends_at[len];
722 let mut best_cost = i32::MAX;
723 let mut best_left = None;
724 let cost_row = cost_matrix.row(0); for (i, left_edge) in left_edges.iter().enumerate() {
727 let path_cost =
728 left_edge.path_cost + cost_row[left_edge.word_entry.right_id() as usize] as i32;
729 if path_cost < best_cost {
730 best_cost = path_cost;
731 best_left = Some(i as u16);
732 }
733 }
734 if let Some(left_idx) = best_left {
735 eos_edge.left_index = left_idx;
736 eos_edge.path_cost = best_cost;
737 self.ends_at[len].push(eos_edge);
738 }
739 }
740 }
741
742 #[allow(clippy::too_many_arguments)]
743 fn process_unknown_word(
744 &mut self,
745 char_definitions: &CharacterDefinition,
746 unknown_dictionary: &UnknownDictionary,
747 cost_matrix: &ConnectionCostMatrix,
748 search_mode: &Mode,
749 category: CategoryId,
750 category_ord: usize,
751 unknown_word_index: Option<usize>,
752 start: usize,
753 char_idx: usize,
754 found: bool,
755 ) -> Option<usize> {
756 let mut unknown_word_num_chars: usize = 0;
757 let category_data = char_definitions.lookup_definition(category);
758 if category_data.invoke || !found {
759 unknown_word_num_chars = 1;
760 if category_data.group {
761 for i in 1.. {
762 let next_idx = char_idx + i;
763 if next_idx >= self.char_info_buffer.len() - 1 {
764 break;
765 }
766 let num_categories = self.char_info_buffer[next_idx].categories_len as usize;
767 let mut found_cat = false;
768 if category_ord < num_categories {
769 let cat = self.get_cached_category(next_idx, category_ord);
770 if cat == category {
771 unknown_word_num_chars += 1;
772 found_cat = true;
773 }
774 }
775 if !found_cat {
776 break;
777 }
778 }
779 }
780 }
781 if unknown_word_num_chars > 0 {
782 let byte_end_offset =
783 self.char_info_buffer[char_idx + unknown_word_num_chars].byte_offset;
784 let byte_len = byte_end_offset as usize - start;
785
786 let kanji_only = self.is_kanji_all(char_idx, byte_len);
788
789 for &word_id in unknown_dictionary.lookup_word_ids(category) {
790 let word_entry = unknown_dictionary.word_entry(word_id);
791 let edge = Self::create_edge(word_entry, start, start + byte_len, kanji_only);
792 self.add_edge_in_lattice(edge, cost_matrix, search_mode);
793 }
794 return Some(start + byte_len);
795 }
796 unknown_word_index
797 }
798
799 fn add_edge_in_lattice(
801 &mut self,
802 mut edge: Edge,
803 cost_matrix: &ConnectionCostMatrix,
804 mode: &Mode,
805 ) {
806 let start_index = edge.start_index as usize;
807 let stop_index = edge.stop_index as usize;
808 let right_left_id = edge.word_entry.left_id();
809
810 if self.ends_at[start_index].is_empty() {
811 return;
812 }
813
814 let mut best_cost = i32::MAX;
815 let mut best_left = None;
816
817 match mode {
818 Mode::Normal => {
819 let left_edges = &self.ends_at[start_index];
822 let cost_row = cost_matrix.row(right_left_id);
823 for (i, left_edge) in left_edges.iter().enumerate() {
824 let conn_cost = cost_row[left_edge.word_entry.right_id() as usize] as i32;
825 let total_cost = left_edge.path_cost + conn_cost;
826
827 if total_cost < best_cost {
828 best_cost = total_cost;
829 best_left = Some(i as u16);
830 }
831 }
832 }
833 Mode::Decompose(penalty) => {
834 let left_edges = &self.ends_at[start_index];
835 for (i, left_edge) in left_edges.iter().enumerate() {
836 let left_right_id = left_edge.word_entry.right_id();
837 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
838 let penalty_cost = penalty.penalty(left_edge);
839 let total_cost = left_edge
840 .path_cost
841 .saturating_add(conn_cost)
842 .saturating_add(penalty_cost);
843
844 if total_cost < best_cost {
845 best_cost = total_cost;
846 best_left = Some(i as u16);
847 }
848 }
849 }
850 }
851
852 if let Some(best_left_idx) = best_left {
853 edge.path_cost = best_cost
854 .saturating_add(edge.word_entry.word_cost as i32)
855 .min(PATH_COST_CLAMP);
856 edge.left_index = best_left_idx;
857 self.ends_at[stop_index].push(edge);
858 }
859 }
860
861 pub fn tokens_offset(&self) -> Vec<(usize, WordId)> {
870 let mut offsets = Vec::new();
871 self.tokens_offset_into(&mut offsets);
872 offsets
873 }
874
875 pub fn tokens_offset_into(&self, offsets: &mut Vec<(usize, WordId)>) {
884 offsets.clear();
885
886 if self.ends_at.is_empty() {
887 return;
888 }
889
890 let mut last_idx = self.last_text_len.min(self.ends_at.len() - 1);
895 while last_idx > 0 && self.ends_at[last_idx].is_empty() {
896 last_idx -= 1;
897 }
898
899 if self.ends_at[last_idx].is_empty() {
900 return;
901 }
902
903 let idx = self.ends_at[last_idx].len() - 1;
904 let mut edge = &self.ends_at[last_idx][idx];
905
906 if edge.left_index == u16::MAX {
907 return;
908 }
909
910 loop {
911 if edge.left_index == u16::MAX {
912 break;
913 }
914
915 offsets.push((edge.start_index as usize, edge.word_entry.word_id));
916
917 let left_idx = edge.left_index as usize;
918 let start_idx = edge.start_index as usize;
919
920 edge = &self.ends_at[start_idx][left_idx];
921 }
922
923 offsets.reverse();
924 offsets.pop(); }
926
927 pub fn text_len(&self) -> usize {
931 self.last_text_len
932 }
933
934 pub fn edges_at(&self, byte_pos: usize) -> &[Edge] {
936 &self.ends_at[byte_pos]
937 }
938
939 pub fn paths_at(&self, byte_pos: usize) -> &[PathEntry] {
941 if byte_pos < self.all_paths.len() {
942 &self.all_paths[byte_pos]
943 } else {
944 &[]
945 }
946 }
947
948 fn add_edge_in_lattice_nbest(
950 &mut self,
951 mut edge: Edge,
952 cost_matrix: &ConnectionCostMatrix,
953 mode: &Mode,
954 ) {
955 let start_index = edge.start_index as usize;
956 let stop_index = edge.stop_index as usize;
957 let right_left_id = edge.word_entry.left_id();
958
959 if self.ends_at[start_index].is_empty() {
960 return;
961 }
962
963 let mut best_cost = i32::MAX;
964 let mut best_left = None;
965
966 let new_edge_index = self.ends_at[stop_index].len() as u16;
968
969 match mode {
970 Mode::Normal => {
971 let cost_row = cost_matrix.row(right_left_id);
973 for i in 0..self.ends_at[start_index].len() {
974 let left_edge = &self.ends_at[start_index][i];
975 let total_cost = left_edge.path_cost
976 + cost_row[left_edge.word_entry.right_id() as usize] as i32;
977
978 self.all_paths[stop_index].push(PathEntry {
980 edge_index: new_edge_index,
981 left_pos: start_index as u32,
982 left_index: i as u16,
983 cost: total_cost,
984 });
985
986 if total_cost < best_cost {
987 best_cost = total_cost;
988 best_left = Some(i as u16);
989 }
990 }
991 }
992 Mode::Decompose(penalty) => {
993 for i in 0..self.ends_at[start_index].len() {
994 let left_edge = &self.ends_at[start_index][i];
995 let left_right_id = left_edge.word_entry.right_id();
996 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
997 let penalty_cost = penalty.penalty(left_edge);
998 let total_cost = left_edge
999 .path_cost
1000 .saturating_add(conn_cost)
1001 .saturating_add(penalty_cost);
1002
1003 self.all_paths[stop_index].push(PathEntry {
1005 edge_index: new_edge_index,
1006 left_pos: start_index as u32,
1007 left_index: i as u16,
1008 cost: total_cost,
1009 });
1010
1011 if total_cost < best_cost {
1012 best_cost = total_cost;
1013 best_left = Some(i as u16);
1014 }
1015 }
1016 }
1017 }
1018
1019 if let Some(best_left_idx) = best_left {
1020 edge.path_cost = best_cost
1021 .saturating_add(edge.word_entry.word_cost as i32)
1022 .min(PATH_COST_CLAMP);
1023 edge.left_index = best_left_idx;
1024 self.ends_at[stop_index].push(edge);
1025 }
1026 }
1027
1028 #[allow(clippy::too_many_arguments)]
1029 fn process_unknown_word_nbest(
1030 &mut self,
1031 char_definitions: &CharacterDefinition,
1032 unknown_dictionary: &UnknownDictionary,
1033 cost_matrix: &ConnectionCostMatrix,
1034 search_mode: &Mode,
1035 category: CategoryId,
1036 category_ord: usize,
1037 unknown_word_index: Option<usize>,
1038 start: usize,
1039 char_idx: usize,
1040 found: bool,
1041 ) -> Option<usize> {
1042 let mut unknown_word_num_chars: usize = 0;
1043 let category_data = char_definitions.lookup_definition(category);
1044 if category_data.invoke || !found {
1045 unknown_word_num_chars = 1;
1046 if category_data.group {
1047 for i in 1.. {
1048 let next_idx = char_idx + i;
1049 if next_idx >= self.char_info_buffer.len() - 1 {
1050 break;
1051 }
1052 let num_categories = self.char_info_buffer[next_idx].categories_len as usize;
1053 let mut found_cat = false;
1054 if category_ord < num_categories {
1055 let cat = self.get_cached_category(next_idx, category_ord);
1056 if cat == category {
1057 unknown_word_num_chars += 1;
1058 found_cat = true;
1059 }
1060 }
1061 if !found_cat {
1062 break;
1063 }
1064 }
1065 }
1066 }
1067 if unknown_word_num_chars > 0 {
1068 let byte_end_offset =
1069 self.char_info_buffer[char_idx + unknown_word_num_chars].byte_offset;
1070 let byte_len = byte_end_offset as usize - start;
1071
1072 let kanji_only = self.is_kanji_all(char_idx, byte_len);
1073
1074 for &word_id in unknown_dictionary.lookup_word_ids(category) {
1075 let word_entry = unknown_dictionary.word_entry(word_id);
1076 let edge = Self::create_edge(word_entry, start, start + byte_len, kanji_only);
1077 self.add_edge_in_lattice_nbest(edge, cost_matrix, search_mode);
1078 }
1079 return Some(start + byte_len);
1080 }
1081 unknown_word_index
1082 }
1083
1084 #[inline(never)]
1087 #[allow(clippy::too_many_arguments)]
1088 pub fn set_text_nbest(
1089 &mut self,
1090 dict: &PrefixDictionary,
1091 user_dict: &Option<&UserPrefixDictionary>,
1092 char_definitions: &CharacterDefinition,
1093 unknown_dictionary: &UnknownDictionary,
1094 cost_matrix: &ConnectionCostMatrix,
1095 text: &str,
1096 search_mode: &Mode,
1097 ) {
1098 let len = text.len();
1099 self.set_capacity_nbest(len);
1100
1101 self.char_info_buffer.clear();
1103 self.categories_buffer.clear();
1104 self.chars_buf.clear();
1105
1106 for (byte_offset, c) in text.char_indices() {
1107 let categories_start = self.categories_buffer.len() as u32;
1108
1109 let categories = char_definitions.lookup_categories(c);
1113 for &category in categories {
1114 self.categories_buffer.push(category);
1115 }
1116
1117 let categories_len = (self.categories_buffer.len() as u32 - categories_start) as u16;
1118
1119 self.char_info_buffer.push(CharData {
1120 byte_offset: byte_offset as u32,
1121 is_kanji: is_kanji(c),
1122 categories_start,
1123 categories_len,
1124 kanji_run_byte_len: 0,
1125 });
1126 self.chars_buf.push(c);
1127 }
1128 self.char_info_buffer.push(CharData {
1130 byte_offset: len as u32,
1131 is_kanji: false,
1132 categories_start: 0,
1133 categories_len: 0,
1134 kanji_run_byte_len: 0,
1135 });
1136
1137 for i in (0..self.char_info_buffer.len() - 1).rev() {
1139 if self.char_info_buffer[i].is_kanji {
1140 let next_byte_offset = self.char_info_buffer[i + 1].byte_offset;
1141 let char_byte_len = next_byte_offset - self.char_info_buffer[i].byte_offset;
1142 self.char_info_buffer[i].kanji_run_byte_len =
1143 char_byte_len + self.char_info_buffer[i + 1].kanji_run_byte_len;
1144 } else {
1145 self.char_info_buffer[i].kanji_run_byte_len = 0;
1146 }
1147 }
1148
1149 let start_edge = Edge {
1150 path_cost: 0,
1151 left_index: u16::MAX,
1152 ..Default::default()
1153 };
1154 self.ends_at[0].push(start_edge);
1155
1156 let mut unknown_word_end: Option<usize> = None;
1157
1158 self.matches_head.clear();
1164 self.matches_store.clear();
1165
1166 if let Some(ud) = user_dict {
1168 self.matches_head.resize(len + 1, u32::MAX);
1169 let ud_vals: &[u8] = &ud.vals_data;
1170 for m in ud.da.find_overlapping_iter(text) {
1171 let start = m.start();
1172 let (offset, count) = ud.decode_val(m.value());
1173 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
1174
1175 if start < self.matches_head.len() {
1176 let avail = ud_vals.len().saturating_sub(offset_bytes);
1177 let n = (count as usize).min(avail / WordEntry::SERIALIZED_LEN);
1178 let block =
1179 &ud_vals[offset_bytes..offset_bytes + n * WordEntry::SERIALIZED_LEN];
1180 let end = m.end() as u32;
1181 for chunk in block.chunks_exact(WordEntry::SERIALIZED_LEN) {
1182 let entry = WordEntry::deserialize(chunk, false);
1183 let next = self.matches_head[start];
1184 self.matches_head[start] = self.matches_store.len() as u32;
1185 self.matches_store.push((end, entry, next));
1186 }
1187 }
1188 }
1189 }
1190
1191 for char_idx in 0..self.char_info_buffer.len() - 1 {
1192 let start = self.char_info_buffer[char_idx].byte_offset as usize;
1193
1194 if self.ends_at[start].is_empty() {
1195 continue;
1196 }
1197
1198 let mut found: bool = false;
1199
1200 if start < self.matches_head.len() {
1203 let mut match_idx = self.matches_head[start];
1204 while match_idx != u32::MAX {
1205 let (end, word_entry, next) = self.matches_store[match_idx as usize];
1206
1207 let prefix_len = end as usize - start;
1208 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
1209 let edge = Self::create_edge(word_entry, start, end as usize, kanji_only);
1210 self.add_edge_in_lattice_nbest(edge, cost_matrix, search_mode);
1211 found = true;
1212
1213 match_idx = next;
1214 }
1215 }
1216
1217 self.sys_matches.clear();
1224 {
1225 let suffix = &self.chars_buf[char_idx..];
1226 for (entries, end_char_offset) in dict.common_prefix_search(suffix) {
1227 let end_char_idx = char_idx + end_char_offset;
1228 let end = self.char_info_buffer[end_char_idx].byte_offset;
1229 for chunk in entries.chunks_exact(WordEntry::SERIALIZED_LEN) {
1230 self.sys_matches
1231 .push((end, WordEntry::deserialize(chunk, true)));
1232 }
1233 }
1234 }
1235 for i in (0..self.sys_matches.len()).rev() {
1236 let (end, word_entry) = self.sys_matches[i];
1237 let end = end as usize;
1238 let prefix_len = end - start;
1239 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
1240 let edge = Self::create_edge(word_entry, start, end, kanji_only);
1241 self.add_edge_in_lattice_nbest(edge, cost_matrix, search_mode);
1242 found = true;
1243 }
1244
1245 if (search_mode.is_search()
1246 || unknown_word_end.map(|index| index <= start).unwrap_or(true))
1247 && char_idx < self.char_info_buffer.len() - 1
1248 {
1249 let num_categories = self.char_info_buffer[char_idx].categories_len as usize;
1250 for category_ord in 0..num_categories {
1251 let category = self.get_cached_category(char_idx, category_ord);
1252 unknown_word_end = self.process_unknown_word_nbest(
1253 char_definitions,
1254 unknown_dictionary,
1255 cost_matrix,
1256 search_mode,
1257 category,
1258 category_ord,
1259 unknown_word_end,
1260 start,
1261 char_idx,
1262 found,
1263 );
1264 }
1265 }
1266 }
1267
1268 if !self.ends_at[len].is_empty() {
1270 let eos_edge_index = self.ends_at[len].len() as u16;
1271 let mut eos_edge = Edge {
1272 start_index: len as u32,
1273 stop_index: len as u32,
1274 ..Default::default()
1275 };
1276 let mut best_cost = i32::MAX;
1277 let mut best_left = None;
1278 let cost_row = cost_matrix.row(0); for i in 0..self.ends_at[len].len() {
1281 let left_edge = &self.ends_at[len][i];
1282 let path_cost =
1283 left_edge.path_cost + cost_row[left_edge.word_entry.right_id() as usize] as i32;
1284
1285 self.all_paths[len].push(PathEntry {
1287 edge_index: eos_edge_index,
1288 left_pos: len as u32,
1289 left_index: i as u16,
1290 cost: path_cost,
1291 });
1292
1293 if path_cost < best_cost {
1294 best_cost = path_cost;
1295 best_left = Some(i as u16);
1296 }
1297 }
1298 if let Some(left_idx) = best_left {
1299 eos_edge.left_index = left_idx;
1300 eos_edge.path_cost = best_cost;
1301 self.ends_at[len].push(eos_edge);
1302 }
1303 }
1304 }
1305
1306 pub fn nbest_tokens_offset(
1314 &self,
1315 n: usize,
1316 unique: bool,
1317 cost_threshold: Option<i64>,
1318 ) -> Vec<(Vec<(usize, WordId)>, i64)> {
1319 use std::collections::HashSet;
1320
1321 use crate::nbest::NBestGenerator;
1322 let mut generator = NBestGenerator::new(self);
1323 let mut results = Vec::with_capacity(n);
1324 let mut best_cost: Option<i64> = None;
1325
1326 if unique {
1327 let mut seen: HashSet<Vec<usize>> = HashSet::new();
1328 while results.len() < n {
1329 match generator.next() {
1330 Some((path, cost)) => {
1331 let bc = *best_cost.get_or_insert(cost);
1333 if let Some(threshold) = cost_threshold
1335 && cost > bc + threshold
1336 {
1337 break;
1338 }
1339 let key: Vec<usize> = path.iter().map(|(start, _)| *start).collect();
1340 if seen.insert(key) {
1341 results.push((path, cost));
1342 }
1343 }
1344 None => break,
1345 }
1346 }
1347 } else {
1348 while results.len() < n {
1349 match generator.next() {
1350 Some((path, cost)) => {
1351 let bc = *best_cost.get_or_insert(cost);
1352 if let Some(threshold) = cost_threshold
1353 && cost > bc + threshold
1354 {
1355 break;
1356 }
1357 results.push((path, cost));
1358 }
1359 None => break,
1360 }
1361 }
1362 }
1363 results
1364 }
1365}
1366
1367#[cfg(test)]
1368mod tests {
1369 use crate::viterbi::{Edge, Lattice, LexType, WordEntry, WordId};
1370
1371 fn test_edge(word_id: u32, start: usize, stop: usize, left_index: u16) -> Edge {
1374 let mut edge = Lattice::create_edge(
1375 WordEntry::new(WordId::new(LexType::System, word_id), 0, 0, 0),
1376 start,
1377 stop,
1378 false,
1379 );
1380 edge.left_index = left_index;
1381 edge.path_cost = 0;
1382 edge
1383 }
1384
1385 #[test]
1386 fn test_word_entry() {
1387 let mut buffer = Vec::new();
1388 let word_entry =
1389 WordEntry::new(WordId::new(LexType::System, 1u32), -17i16, 1411u16, 1412u16);
1390 word_entry.serialize(&mut buffer).unwrap();
1391 assert_eq!(WordEntry::SERIALIZED_LEN, buffer.len());
1392 let word_entry2 = WordEntry::deserialize(&buffer[..], true);
1393 assert_eq!(word_entry, word_entry2);
1394 }
1395
1396 #[test]
1402 fn test_set_capacity_presizes_all_new_slots() {
1403 let mut lattice = Lattice::default();
1404
1405 lattice.set_capacity(5);
1406 assert_eq!(lattice.ends_at.len(), 6);
1407 for (i, slot) in lattice.ends_at.iter().enumerate() {
1408 assert!(
1409 slot.capacity() >= 16,
1410 "slot {} has capacity {} < 16 after initial growth",
1411 i,
1412 slot.capacity()
1413 );
1414 }
1415
1416 lattice.set_capacity(10);
1418 assert_eq!(lattice.ends_at.len(), 11);
1419 for (i, slot) in lattice.ends_at.iter().enumerate() {
1420 assert!(
1421 slot.capacity() >= 16,
1422 "slot {} has capacity {} < 16 after second growth",
1423 i,
1424 slot.capacity()
1425 );
1426 }
1427 }
1428
1429 #[test]
1434 fn test_clear_after_shrink_leaves_no_stale_edges() {
1435 let mut lattice = Lattice::default();
1436
1437 lattice.set_capacity(100);
1439 lattice.ends_at[0].push(test_edge(1, 0, 0, u16::MAX));
1440 lattice.ends_at[57].push(test_edge(2, 0, 57, 0));
1441 lattice.ends_at[100].push(test_edge(3, 57, 100, 0)); lattice.set_capacity(10);
1446 assert!(
1447 lattice.ends_at.iter().all(|v| v.is_empty()),
1448 "stale edges survived a bounded clear"
1449 );
1450
1451 lattice.ends_at[10].push(test_edge(4, 0, 10, 0)); lattice.set_capacity(3);
1455 assert!(
1456 lattice.ends_at.iter().all(|v| v.is_empty()),
1457 "stale edge at the previous boundary slot survived"
1458 );
1459 }
1460
1461 #[test]
1465 fn test_tokens_offset_finds_eos_at_last_text_len_after_shrink() {
1466 let mut lattice = Lattice::default();
1467
1468 lattice.set_capacity(100);
1470
1471 lattice.set_capacity(3);
1474 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();
1479 assert_eq!(offsets.len(), 1);
1480 assert_eq!(offsets[0].0, 0);
1481 assert_eq!(offsets[0].1, WordId::new(LexType::System, 42));
1482 }
1483
1484 #[test]
1488 fn test_shrink_to_truncates_and_keeps_presize() {
1489 let mut lattice = Lattice::default();
1490
1491 lattice.set_capacity(100);
1492 lattice.ends_at[0].push(test_edge(1, 0, 0, u16::MAX));
1493 lattice.ends_at[100].push(test_edge(2, 0, 100, 0));
1494
1495 lattice.shrink_to(10);
1496 assert_eq!(lattice.capacity(), 10);
1497 assert_eq!(lattice.ends_at.len(), 11);
1498 assert!(
1499 lattice.ends_at.iter().all(|v| v.is_empty()),
1500 "shrink_to must clear all slots"
1501 );
1502 for (i, slot) in lattice.ends_at.iter().enumerate() {
1503 assert!(
1504 slot.capacity() >= 16,
1505 "slot {} lost its pre-size after shrink_to (capacity {})",
1506 i,
1507 slot.capacity()
1508 );
1509 }
1510
1511 lattice.set_capacity(50);
1513 assert_eq!(lattice.ends_at.len(), 51);
1514 for (i, slot) in lattice.ends_at.iter().enumerate() {
1515 assert!(
1516 slot.capacity() >= 16,
1517 "slot {} not pre-sized after regrowth (capacity {})",
1518 i,
1519 slot.capacity()
1520 );
1521 }
1522 }
1523
1524 #[test]
1527 fn test_shrink_to_noop_when_target_not_smaller() {
1528 let mut lattice = Lattice::default();
1529 lattice.set_capacity(5);
1530
1531 lattice.shrink_to(100);
1532 assert_eq!(lattice.capacity(), 5);
1533 assert_eq!(lattice.ends_at.len(), 6);
1534
1535 lattice.shrink_to(5);
1536 assert_eq!(lattice.capacity(), 5);
1537 assert_eq!(lattice.ends_at.len(), 6);
1538
1539 let mut fresh = Lattice::default();
1541 fresh.shrink_to(0);
1542 assert_eq!(fresh.capacity(), 0);
1543 assert!(fresh.ends_at.is_empty());
1544 }
1545
1546 #[test]
1551 fn test_backtrace_works_after_shrink_to() {
1552 let mut lattice = Lattice::default();
1553 lattice.set_capacity(100);
1554 lattice.ends_at[0].push(test_edge(1, 0, 0, u16::MAX));
1555 lattice.ends_at[100].push(test_edge(2, 0, 100, 0));
1556
1557 lattice.shrink_to(10);
1558
1559 lattice.set_capacity(3);
1561 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();
1566 assert_eq!(offsets.len(), 1);
1567 assert_eq!(offsets[0].0, 0);
1568 assert_eq!(offsets[0].1, WordId::new(LexType::System, 42));
1569
1570 lattice.clear();
1572 assert!(lattice.ends_at.iter().all(|v| v.is_empty()));
1573 }
1574
1575 #[test]
1577 fn test_shrink_to_releases_nbest_paths() {
1578 let mut lattice = Lattice::default();
1579 lattice.set_capacity_nbest(100);
1580 assert_eq!(lattice.all_paths.len(), 101);
1581
1582 lattice.shrink_to(10);
1583 assert_eq!(lattice.all_paths.len(), 11);
1584 assert_eq!(lattice.nbest_capacity, 10);
1585 assert!(lattice.all_paths.iter().all(|v| v.is_empty()));
1586
1587 lattice.set_capacity_nbest(20);
1589 assert_eq!(lattice.all_paths.len(), 21);
1590 }
1591
1592 #[test]
1595 fn test_tokens_offset_into_matches_tokens_offset() {
1596 let mut lattice = Lattice::default();
1597 lattice.set_capacity(3);
1598 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);
1604 assert_eq!(reused, lattice.tokens_offset());
1605 assert_eq!(reused.len(), 1);
1606
1607 lattice.clear();
1609 lattice.tokens_offset_into(&mut reused);
1610 assert!(reused.is_empty());
1611 assert!(lattice.tokens_offset().is_empty());
1612 }
1613}