Skip to main content

sudachi/dic/
dictionary_access.rs

1/*
2 *  Copyright (c) 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 std::collections::HashMap;
18use std::ops::Deref;
19
20use crate::analysis::morpheme::SingleMorpheme;
21use crate::dic::description::Description;
22use crate::dic::grammar::Grammar;
23use crate::dic::lexicon_set::LexiconSet;
24use crate::dic::subset::InfoSubset;
25use crate::error::SudachiResult;
26use crate::input_text::InputBuffer;
27use crate::plugin::input_text::InputTextPlugin;
28use crate::plugin::oov::OovProviderPlugin;
29use crate::plugin::path_rewrite::PathRewritePlugin;
30
31pub trait LexiconAccess {
32    fn lexicon(&self) -> &LexiconSet<'_>;
33}
34
35impl<T> LexiconAccess for T
36where
37    T: Deref,
38    <T as Deref>::Target: LexiconAccess,
39{
40    fn lexicon(&self) -> &LexiconSet<'_> {
41        <T as Deref>::deref(self).lexicon()
42    }
43}
44
45pub trait DescriptionAccess {
46    fn description(&self) -> &Description;
47}
48
49impl<T> DescriptionAccess for T
50where
51    T: Deref,
52    <T as Deref>::Target: DescriptionAccess,
53{
54    fn description(&self) -> &Description {
55        <T as Deref>::deref(self).description()
56    }
57}
58
59/// Build-time helper access to dictionary entry reference IDs.
60pub trait ReferenceIdAccess {
61    fn reference_ids(&self) -> HashMap<u32, String>;
62}
63
64impl<T> ReferenceIdAccess for T
65where
66    T: Deref,
67    <T as Deref>::Target: ReferenceIdAccess,
68{
69    fn reference_ids(&self) -> HashMap<u32, String> {
70        <T as Deref>::deref(self).reference_ids()
71    }
72}
73
74/// Provides access to dictionary data
75pub trait DictionaryAccess: LexiconAccess {
76    fn grammar(&self) -> &Grammar<'_>;
77
78    fn input_text_plugins(&self) -> &[Box<dyn InputTextPlugin + Sync + Send>];
79    fn oov_provider_plugins(&self) -> &[Box<dyn OovProviderPlugin + Sync + Send>];
80    fn path_rewrite_plugins(&self) -> &[Box<dyn PathRewritePlugin + Sync + Send>];
81}
82
83impl<T> DictionaryAccess for T
84where
85    T: Deref,
86    <T as Deref>::Target: DictionaryAccess,
87{
88    fn grammar(&self) -> &Grammar<'_> {
89        <T as Deref>::deref(self).grammar()
90    }
91
92    fn input_text_plugins(&self) -> &[Box<dyn InputTextPlugin + Sync + Send>] {
93        <T as Deref>::deref(self).input_text_plugins()
94    }
95
96    fn oov_provider_plugins(&self) -> &[Box<dyn OovProviderPlugin + Sync + Send>] {
97        <T as Deref>::deref(self).oov_provider_plugins()
98    }
99
100    fn path_rewrite_plugins(&self) -> &[Box<dyn PathRewritePlugin + Sync + Send>] {
101        <T as Deref>::deref(self).path_rewrite_plugins()
102    }
103}
104
105/// Build normalized input text by applying dictionary input-text plugins.
106pub(crate) fn normalize_input_text<D: DictionaryAccess + ?Sized>(
107    dict: &D,
108    text: &str,
109    buffer: &mut InputBuffer,
110) -> SudachiResult<()> {
111    buffer.reset().push_str(text);
112    buffer.start_build()?;
113    for plugin in dict.input_text_plugins() {
114        plugin.rewrite(buffer)?;
115    }
116    buffer.build(dict.grammar())
117}
118
119/// Applies input-text plugins and leaves the rewritten text in `buffer.current()`.
120///
121/// This intentionally does not build grammar/index metadata and must only be
122/// used when the rewritten string is needed for comparison, not for morpheme
123/// offsets or analysis.
124pub(crate) fn rewrite_input_text_for_comparison<D: DictionaryAccess + ?Sized>(
125    dict: &D,
126    text: &str,
127    buffer: &mut InputBuffer,
128) -> SudachiResult<()> {
129    buffer.reset().push_str(text);
130    buffer.start_build()?;
131    for plugin in dict.input_text_plugins() {
132        plugin.rewrite(buffer)?;
133    }
134    Ok(())
135}
136
137/// Look up entries by scanning every public dictionary entry.
138pub(crate) fn lookup_all_entries<D>(
139    dict: D,
140    surface: &str,
141    subset: InfoSubset,
142) -> SudachiResult<Vec<SingleMorpheme<D>>>
143where
144    D: DictionaryAccess + Clone,
145{
146    let mut query_buffer = InputBuffer::new();
147    rewrite_input_text_for_comparison(&dict, surface, &mut query_buffer)?;
148    let query = query_buffer.current().to_owned();
149    let mut entry_buffer = InputBuffer::new();
150    let mut result = Vec::new();
151
152    for word_id in dict.lexicon().word_ids() {
153        let word_id = word_id?;
154        let word_info = dict
155            .lexicon()
156            .get_word_info_subset(word_id, InfoSubset::HEADWORD)?;
157        rewrite_input_text_for_comparison(
158            &dict,
159            word_info.headword(dict.lexicon()),
160            &mut entry_buffer,
161        )?;
162        if entry_buffer.current() == query {
163            result.push(SingleMorpheme::from_word_id(dict.clone(), word_id, subset)?);
164        }
165    }
166
167    Ok(result)
168}