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 char_filters: Vec<PreparedCharFilter<'static>>,
24 tokenizer: PreparedTokenizer,
25 token_filters: Vec<PreparedTokenFilter<'static>>,
26 normalization: crate::normalization::PreparedNormalization,
27}
28
29impl Analyzer {
30 pub fn compile(&self) -> AnalysisResult<Arc<CompiledAnalyzer>> {
46 AnalyzerResources::default().compile(self)
47 }
48
49 pub fn compile_with_resources(
51 &self,
52 resources: &AnalyzerResources,
53 ) -> AnalysisResult<Arc<CompiledAnalyzer>> {
54 resources.compile(self)
55 }
56}
57
58impl CompiledAnalyzer {
59 pub(crate) fn prepare(
60 descriptor: Arc<AnalyzerDescriptor>,
61 #[cfg(feature = "nori")] nori: crate::nori::pipeline::ResolvedNoriPipeline,
62 #[cfg(feature = "kuromoji")] kuromoji: crate::kuromoji::pipeline::ResolvedKuromojiPipeline,
63 ) -> AnalysisResult<Self> {
64 let config = descriptor.configuration()?;
65 let char_filters = config
66 .char_filters
67 .iter()
68 .map(|filter| filter.prepare().map(PreparedCharFilter::into_owned))
69 .collect::<AnalysisResult<_>>()?;
70 let tokenizer = match &config.tokenizer {
71 #[cfg(feature = "nori")]
72 crate::Tokenizer::Nori(_) => PreparedTokenizer::Nori(nori.tokenizer.clone().ok_or(
73 crate::AnalysisError::Descriptor("missing resolved Korean tokenizer"),
74 )?),
75 #[cfg(feature = "kuromoji")]
76 crate::Tokenizer::Kuromoji(_) => {
77 PreparedTokenizer::Kuromoji(kuromoji.tokenizer.clone().ok_or(
78 crate::AnalysisError::Descriptor("missing resolved Japanese tokenizer"),
79 )?)
80 }
81 _ => config.tokenizer.prepare()?,
82 };
83 let token_filters = config
84 .token_filters
85 .iter()
86 .enumerate()
87 .map(|(index, filter)| {
88 #[cfg(not(feature = "kuromoji"))]
89 let _ = index;
90 #[cfg(feature = "nori")]
91 if let Some(filter) = nori.filter(filter)? {
92 return Ok(PreparedTokenFilter::Nori(filter));
93 }
94 #[cfg(feature = "kuromoji")]
95 if let Some(filter) = kuromoji.filter(index, filter)? {
96 return Ok(PreparedTokenFilter::Kuromoji(filter));
97 }
98 filter.prepare().map(PreparedTokenFilter::into_owned)
99 })
100 .collect::<AnalysisResult<_>>()?;
101 Ok(Self {
102 descriptor,
103 char_filters,
104 tokenizer,
105 token_filters,
106 normalization: crate::normalization::PreparedNormalization::new(
107 config.normalization.as_ref(),
108 #[cfg(feature = "nori")]
109 nori.normalizer,
110 #[cfg(feature = "kuromoji")]
111 kuromoji.normalizer,
112 )?,
113 })
114 }
115
116 pub fn descriptor(&self) -> &Arc<AnalyzerDescriptor> {
117 &self.descriptor
118 }
119
120 pub fn analyze_tokens(&self, text: &str) -> AnalysisResult<AnalyzedText> {
122 Ok(self
123 .analyze_tokens_budgeted(text, &MemoryBudget::new(usize::MAX), || Ok(()))?
124 .into_parts()
125 .0)
126 }
127
128 pub fn analyze_tokens_budgeted(
145 &self,
146 text: &str,
147 budget: &MemoryBudget,
148 mut poll: impl FnMut() -> AnalysisResult<()>,
149 ) -> AnalysisResult<Budgeted<AnalyzedText>> {
150 poll()?;
151 let mut filtered = FilteredText::new(text);
152 for filter in &self.char_filters {
153 filtered = filter.filter_mapped_budgeted(filtered, budget, &mut poll)?;
154 }
155 let mut tokens = self
156 .tokenizer
157 .tokenize_mapped_for_filters_budgeted(&filtered, budget, &mut poll)?;
158 drop(filtered);
159 for filter in &self.token_filters {
160 tokens = filter.filter_analyzed_budgeted(tokens, &mut poll)?;
161 }
162 #[cfg(feature = "kuromoji")]
163 if matches!(self.tokenizer, PreparedTokenizer::Kuromoji(_)) {
164 tokens.validate_japanese_attributes(&mut poll)?;
165 }
166 poll()?;
167 Ok(tokens)
168 }
169
170 pub fn analyze(&self, text: &str) -> AnalysisResult<Vec<String>> {
171 self.analyze_tokens(text)?.into_terms()
172 }
173
174 pub fn normalize(&self, text: &str) -> AnalysisResult<String> {
176 Ok(self
177 .normalize_budgeted(text, &MemoryBudget::new(usize::MAX), || Ok(()))?
178 .into_parts()
179 .0)
180 }
181
182 pub fn normalize_budgeted(
184 &self,
185 text: &str,
186 budget: &MemoryBudget,
187 mut poll: impl FnMut() -> AnalysisResult<()>,
188 ) -> AnalysisResult<Budgeted<String>> {
189 self.normalization
190 .normalize_budgeted(text, budget, &mut poll)
191 }
192}