use std::sync::Arc;
use uqa_core::memory::{Budgeted, MemoryBudget};
use super::Analyzer;
use crate::{
char_filter::PreparedCharFilter, token_filter::PreparedTokenFilter,
tokenizer::PreparedTokenizer, AnalysisResult, AnalyzedText, AnalyzerDescriptor,
AnalyzerResources, FilteredText,
};
#[derive(Debug)]
pub struct CompiledAnalyzer {
descriptor: Arc<AnalyzerDescriptor>,
char_filters: Vec<PreparedCharFilter<'static>>,
tokenizer: PreparedTokenizer,
token_filters: Vec<PreparedTokenFilter<'static>>,
#[cfg(feature = "nori")]
normalizer: Option<Arc<crate::nori::ResolvedDictionary>>,
}
impl Analyzer {
pub fn compile(&self) -> AnalysisResult<Arc<CompiledAnalyzer>> {
AnalyzerResources::default().compile(self)
}
pub fn compile_with_resources(
&self,
resources: &AnalyzerResources,
) -> AnalysisResult<Arc<CompiledAnalyzer>> {
resources.compile(self)
}
}
impl CompiledAnalyzer {
pub(crate) fn prepare(
descriptor: Arc<AnalyzerDescriptor>,
#[cfg(feature = "nori")] nori: crate::nori::pipeline::ResolvedNoriPipeline,
) -> AnalysisResult<Self> {
let config = descriptor.configuration()?;
let char_filters = config
.char_filters
.iter()
.map(|filter| filter.prepare().map(PreparedCharFilter::into_owned))
.collect::<AnalysisResult<_>>()?;
#[cfg(feature = "nori")]
let tokenizer = match &nori.tokenizer {
Some(tokenizer) => PreparedTokenizer::Nori(tokenizer.clone()),
None => config.tokenizer.prepare()?,
};
#[cfg(not(feature = "nori"))]
let tokenizer = config.tokenizer.prepare()?;
let token_filters = config
.token_filters
.iter()
.map(|filter| {
#[cfg(feature = "nori")]
if let Some(filter) = nori.filter(filter)? {
return Ok(PreparedTokenFilter::Nori(filter));
}
filter.prepare().map(PreparedTokenFilter::into_owned)
})
.collect::<AnalysisResult<_>>()?;
Ok(Self {
descriptor,
char_filters,
tokenizer,
token_filters,
#[cfg(feature = "nori")]
normalizer: nori.normalizer,
})
}
pub fn descriptor(&self) -> &Arc<AnalyzerDescriptor> {
&self.descriptor
}
pub fn analyze_tokens(&self, text: &str) -> AnalysisResult<AnalyzedText> {
Ok(self
.analyze_tokens_budgeted(text, &MemoryBudget::new(usize::MAX), || Ok(()))?
.into_parts()
.0)
}
pub fn analyze_tokens_budgeted(
&self,
text: &str,
budget: &MemoryBudget,
mut poll: impl FnMut() -> AnalysisResult<()>,
) -> AnalysisResult<Budgeted<AnalyzedText>> {
poll()?;
let mut filtered = FilteredText::new(text);
for filter in &self.char_filters {
filtered = filter.filter_mapped_budgeted(filtered, budget, &mut poll)?;
}
let mut tokens = self
.tokenizer
.tokenize_mapped_budgeted(&filtered, budget, &mut poll)?;
drop(filtered);
for filter in &self.token_filters {
tokens = filter.filter_analyzed_budgeted(tokens, &mut poll)?;
}
poll()?;
Ok(tokens)
}
pub fn analyze(&self, text: &str) -> AnalysisResult<Vec<String>> {
self.analyze_tokens(text)?.into_terms()
}
#[cfg(feature = "nori")]
pub fn normalize(&self, text: &str) -> AnalysisResult<String> {
Ok(self
.normalize_budgeted(text, &MemoryBudget::new(usize::MAX), || Ok(()))?
.into_parts()
.0)
}
#[cfg(feature = "nori")]
pub fn normalize_budgeted(
&self,
text: &str,
budget: &MemoryBudget,
mut poll: impl FnMut() -> AnalysisResult<()>,
) -> AnalysisResult<Budgeted<String>> {
let model = self
.normalizer
.as_ref()
.ok_or(crate::AnalysisError::NormalizationUnavailable)?;
crate::nori::pipeline::normalize_budgeted(text, model, budget, &mut poll)
}
}