uqa_analysis/analyzer/
compiled.rs1use std::sync::Arc;
10use uqa_core::memory::{Budgeted, MemoryBudget};
11
12use super::Analyzer;
13use crate::{
14 char_filter::PreparedCharFilter, token_filter::PreparedTokenFilter,
15 tokenizer::PreparedTokenizer, AnalysisResult, AnalyzedText, AnalyzerDescriptor,
16 AnalyzerResources, FilteredText,
17};
18
19#[derive(Debug)]
21pub struct CompiledAnalyzer {
22 descriptor: Arc<AnalyzerDescriptor>,
23 configuration: Analyzer,
24 char_filters: Vec<PreparedCharFilter<'static>>,
25 tokenizer: PreparedTokenizer,
26 token_filters: Vec<PreparedTokenFilter<'static>>,
27 normalization: crate::normalization::PreparedNormalization,
28}
29
30impl Analyzer {
31 pub fn compile(&self) -> AnalysisResult<Arc<CompiledAnalyzer>> {
47 AnalyzerResources::default().compile(self)
48 }
49
50 pub fn compile_with_resources(
52 &self,
53 resources: &AnalyzerResources,
54 ) -> AnalysisResult<Arc<CompiledAnalyzer>> {
55 resources.compile(self)
56 }
57}
58
59impl CompiledAnalyzer {
60 pub(crate) fn prepare(
61 descriptor: Arc<AnalyzerDescriptor>,
62 #[cfg(feature = "nori")] nori: crate::nori::pipeline::ResolvedNoriPipeline,
63 #[cfg(feature = "kuromoji")] kuromoji: crate::kuromoji::pipeline::ResolvedKuromojiPipeline,
64 ) -> AnalysisResult<Self> {
65 let config = descriptor.configuration()?;
66 let char_filters = config
67 .char_filters
68 .iter()
69 .map(|filter| filter.prepare().map(PreparedCharFilter::into_owned))
70 .collect::<AnalysisResult<_>>()?;
71 let tokenizer = match &config.tokenizer {
72 #[cfg(feature = "nori")]
73 crate::Tokenizer::Nori(_) => PreparedTokenizer::Nori(nori.tokenizer.clone().ok_or(
74 crate::AnalysisError::Descriptor("missing resolved Korean tokenizer"),
75 )?),
76 #[cfg(feature = "kuromoji")]
77 crate::Tokenizer::Kuromoji(_) => {
78 PreparedTokenizer::Kuromoji(kuromoji.tokenizer.clone().ok_or(
79 crate::AnalysisError::Descriptor("missing resolved Japanese tokenizer"),
80 )?)
81 }
82 _ => config.tokenizer.prepare()?,
83 };
84 let token_filters = config
85 .token_filters
86 .iter()
87 .enumerate()
88 .map(|(index, filter)| {
89 #[cfg(not(feature = "kuromoji"))]
90 let _ = index;
91 #[cfg(feature = "nori")]
92 if let Some(filter) = nori.filter(filter)? {
93 return Ok(PreparedTokenFilter::Nori(filter));
94 }
95 #[cfg(feature = "kuromoji")]
96 if let Some(filter) = kuromoji.filter(index, filter)? {
97 return Ok(PreparedTokenFilter::Kuromoji(filter));
98 }
99 filter.prepare().map(PreparedTokenFilter::into_owned)
100 })
101 .collect::<AnalysisResult<_>>()?;
102 let normalization = crate::normalization::PreparedNormalization::new(
103 config.normalization.as_ref(),
104 #[cfg(feature = "nori")]
105 nori.normalizer,
106 #[cfg(feature = "kuromoji")]
107 kuromoji.normalizer,
108 )?;
109 Ok(Self {
110 descriptor,
111 configuration: config,
112 char_filters,
113 tokenizer,
114 token_filters,
115 normalization,
116 })
117 }
118
119 pub fn descriptor(&self) -> &Arc<AnalyzerDescriptor> {
120 &self.descriptor
121 }
122
123 pub fn configuration(&self) -> &Analyzer {
127 &self.configuration
128 }
129
130 pub fn analyze_tokens(&self, text: &str) -> AnalysisResult<AnalyzedText> {
132 Ok(self
133 .analyze_tokens_budgeted(text, &MemoryBudget::new(usize::MAX), || Ok(()))?
134 .into_parts()
135 .0)
136 }
137
138 pub fn analyze_tokens_budgeted(
155 &self,
156 text: &str,
157 budget: &MemoryBudget,
158 mut poll: impl FnMut() -> AnalysisResult<()>,
159 ) -> AnalysisResult<Budgeted<AnalyzedText>> {
160 poll()?;
161 let mut filtered = FilteredText::new(text);
162 for filter in &self.char_filters {
163 filtered = filter.filter_mapped_budgeted(filtered, budget, &mut poll)?;
164 }
165 let mut tokens = self
166 .tokenizer
167 .tokenize_mapped_for_filters_budgeted(&filtered, budget, &mut poll)?;
168 drop(filtered);
169 for filter in &self.token_filters {
170 tokens = filter.filter_analyzed_budgeted(tokens, &mut poll)?;
171 }
172 #[cfg(feature = "kuromoji")]
173 if matches!(self.tokenizer, PreparedTokenizer::Kuromoji(_)) {
174 tokens.validate_japanese_attributes(&mut poll)?;
175 }
176 poll()?;
177 Ok(tokens)
178 }
179
180 pub fn analyze(&self, text: &str) -> AnalysisResult<Vec<String>> {
181 self.analyze_tokens(text)?.into_terms()
182 }
183
184 pub fn normalize(&self, text: &str) -> AnalysisResult<String> {
186 Ok(self
187 .normalize_budgeted(text, &MemoryBudget::new(usize::MAX), || Ok(()))?
188 .into_parts()
189 .0)
190 }
191
192 pub fn normalize_budgeted(
194 &self,
195 text: &str,
196 budget: &MemoryBudget,
197 mut poll: impl FnMut() -> AnalysisResult<()>,
198 ) -> AnalysisResult<Budgeted<String>> {
199 self.normalization
200 .normalize_budgeted(text, budget, &mut poll)
201 }
202}