use super::error::{ContextError, Result};
use super::{CheckpointStack, Completion, ContextId, ContextTree, DraftBuffer};
use crate::transducer::{Algorithm, Transducer};
use libdictenstein::double_array_trie::char::DoubleArrayTrieChar;
use libdictenstein::double_array_trie::DoubleArrayTrie;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
pub struct StaticContextualCompletionEngine<D = DoubleArrayTrie<Vec<ContextId>>>
where
D: crate::dictionary::MappedDictionary<Value = Vec<ContextId>> + Clone,
{
drafts: Arc<Mutex<HashMap<ContextId, DraftBuffer>>>,
checkpoints: Arc<Mutex<HashMap<ContextId, CheckpointStack>>>,
context_tree: Arc<RwLock<ContextTree>>,
transducer: Arc<RwLock<Transducer<D>>>,
finalized_terms: Arc<RwLock<HashMap<String, Vec<ContextId>>>>,
}
impl StaticContextualCompletionEngine<DoubleArrayTrie<Vec<ContextId>>> {
pub fn with_double_array_trie(
dictionary: DoubleArrayTrie<Vec<ContextId>>,
algorithm: Algorithm,
) -> Self {
Self::with_dictionary(dictionary, algorithm)
}
}
impl StaticContextualCompletionEngine<DoubleArrayTrieChar<Vec<ContextId>>> {
pub fn with_double_array_trie_char(
dictionary: DoubleArrayTrieChar<Vec<ContextId>>,
algorithm: Algorithm,
) -> Self {
Self::with_dictionary(dictionary, algorithm)
}
}
impl<D> StaticContextualCompletionEngine<D>
where
D: crate::dictionary::MappedDictionary<Value = Vec<ContextId>> + Clone,
{
pub fn with_dictionary(dictionary: D, algorithm: Algorithm) -> Self {
let transducer = Transducer::new(dictionary, algorithm);
Self {
drafts: Arc::new(Mutex::new(HashMap::new())),
checkpoints: Arc::new(Mutex::new(HashMap::new())),
context_tree: Arc::new(RwLock::new(ContextTree::new())),
transducer: Arc::new(RwLock::new(transducer)),
finalized_terms: Arc::new(RwLock::new(HashMap::new())),
}
}
#[inline]
pub fn transducer(&self) -> &Arc<RwLock<Transducer<D>>> {
&self.transducer
}
pub fn create_root_context(&self, id: ContextId) -> Result<ContextId> {
let mut tree = self
.context_tree
.write()
.expect("static engine: context_tree RwLock poisoned");
tree.create_root(id);
let mut drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
drafts.insert(id, DraftBuffer::new());
Ok(id)
}
pub fn create_child_context(&self, id: ContextId, parent_id: ContextId) -> Result<ContextId> {
let mut tree = self
.context_tree
.write()
.expect("static engine: context_tree RwLock poisoned");
tree.create_child(id, parent_id)
.map_err(|_| ContextError::ContextNotFound(parent_id))?;
let mut drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
drafts.insert(id, DraftBuffer::new());
Ok(id)
}
pub fn insert_char(&self, context: ContextId, ch: char) -> Result<()> {
let mut drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
let buffer = drafts.entry(context).or_default();
buffer.insert(ch);
Ok(())
}
pub fn insert_str(&self, context: ContextId, s: &str) -> Result<()> {
let mut drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
let buffer = drafts.entry(context).or_default();
for ch in s.chars() {
buffer.insert(ch);
}
Ok(())
}
pub fn delete_char(&self, context: ContextId) -> Result<()> {
let mut drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
if let Some(buffer) = drafts.get_mut(&context) {
buffer.delete();
}
Ok(())
}
pub fn clear_draft(&self, context: ContextId) -> Result<()> {
let mut drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
if let Some(buffer) = drafts.get_mut(&context) {
buffer.clear();
}
Ok(())
}
pub fn get_draft(&self, context: ContextId) -> Result<String> {
let drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
Ok(drafts.get(&context).map(|b| b.as_str()).unwrap_or_default())
}
pub fn finalize(&self, context: ContextId) -> Result<String> {
let mut drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
let buffer = drafts
.get_mut(&context)
.ok_or(ContextError::ContextNotFound(context))?;
let term_owned = buffer.as_str();
let term_clone = term_owned.clone();
buffer.clear();
let mut finalized = self
.finalized_terms
.write()
.expect("static engine: finalized_terms RwLock poisoned");
finalized
.entry(term_clone.clone())
.or_default()
.push(context);
Ok(term_clone)
}
pub fn complete(
&self,
context: ContextId,
query: &str,
max_distance: usize,
) -> Result<Vec<Completion>> {
let mut results = HashMap::new();
let finalized_dict = self.complete_dictionary(context, query, max_distance)?;
for completion in finalized_dict {
results.entry(completion.term.clone()).or_insert(completion);
}
let finalized_hash = self.complete_finalized_terms(context, query, max_distance)?;
for completion in finalized_hash {
results.entry(completion.term.clone()).or_insert(completion);
}
let drafts_results = self.complete_drafts(context, query, max_distance)?;
for completion in drafts_results {
results.insert(completion.term.clone(), completion);
}
let mut final_results: Vec<Completion> = results.into_values().collect();
final_results.sort_by(|a, b| {
a.distance
.cmp(&b.distance)
.then_with(|| a.term.cmp(&b.term))
});
Ok(final_results)
}
fn complete_dictionary(
&self,
context: ContextId,
query: &str,
max_distance: usize,
) -> Result<Vec<Completion>> {
let tree = self
.context_tree
.read()
.expect("static engine: context_tree RwLock poisoned");
let visible = tree.visible_contexts(context);
let transducer = self
.transducer
.read()
.expect("static engine: transducer RwLock poisoned");
let candidates: Vec<_> = transducer
.query_with_distance(query, max_distance)
.collect();
let mut results = Vec::new();
for candidate in candidates {
if let Some(contexts) = transducer.dictionary().get_value(&candidate.term) {
let visible_contexts: Vec<_> = contexts
.iter()
.filter(|ctx| visible.contains(ctx))
.copied()
.collect();
if !visible_contexts.is_empty() {
results.push(Completion {
term: candidate.term.clone(),
distance: candidate.distance,
contexts: visible_contexts,
is_draft: false,
});
}
}
}
Ok(results)
}
fn complete_finalized_terms(
&self,
context: ContextId,
query: &str,
max_distance: usize,
) -> Result<Vec<Completion>> {
let tree = self
.context_tree
.read()
.expect("static engine: context_tree RwLock poisoned");
let visible = tree.visible_contexts(context);
let finalized = self
.finalized_terms
.read()
.expect("static engine: finalized_terms RwLock poisoned");
let mut results = Vec::new();
for (term, contexts) in finalized.iter() {
let distance = Self::levenshtein_distance(query, term);
if distance <= max_distance {
let visible_contexts: Vec<_> = contexts
.iter()
.filter(|ctx| visible.contains(ctx))
.copied()
.collect();
if !visible_contexts.is_empty() {
results.push(Completion {
term: term.clone(),
distance,
contexts: visible_contexts,
is_draft: false,
});
}
}
}
Ok(results)
}
fn complete_drafts(
&self,
context: ContextId,
query: &str,
max_distance: usize,
) -> Result<Vec<Completion>> {
let tree = self
.context_tree
.read()
.expect("static engine: context_tree RwLock poisoned");
let visible = tree.visible_contexts(context);
let drafts = self
.drafts
.lock()
.expect("static engine: drafts Mutex poisoned");
let mut results = Vec::new();
for &ctx in &visible {
if let Some(buffer) = drafts.get(&ctx) {
let draft_text = buffer.as_str();
if !draft_text.is_empty() {
let distance = Self::levenshtein_distance(query, &draft_text);
if distance <= max_distance {
results.push(Completion {
term: draft_text,
distance,
contexts: vec![ctx],
is_draft: true,
});
}
}
}
}
Ok(results)
}
fn levenshtein_distance(s1: &str, s2: &str) -> usize {
let len1 = s1.chars().count();
let len2 = s2.chars().count();
if len1 == 0 {
return len2;
}
if len2 == 0 {
return len1;
}
let mut matrix = vec![vec![0; len2 + 1]; len1 + 1];
for (i, row) in matrix.iter_mut().enumerate() {
row[0] = i;
}
for (j, cell) in matrix[0].iter_mut().enumerate() {
*cell = j;
}
let s1_chars: Vec<char> = s1.chars().collect();
let s2_chars: Vec<char> = s2.chars().collect();
for i in 1..=len1 {
for j in 1..=len2 {
let cost = if s1_chars[i - 1] == s2_chars[j - 1] {
0
} else {
1
};
matrix[i][j] = std::cmp::min(
std::cmp::min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1),
matrix[i - 1][j - 1] + cost,
);
}
}
matrix[len1][len2]
}
}
impl<D> Clone for StaticContextualCompletionEngine<D>
where
D: crate::dictionary::MappedDictionary<Value = Vec<ContextId>> + Clone,
{
fn clone(&self) -> Self {
Self {
drafts: Arc::clone(&self.drafts),
checkpoints: Arc::clone(&self.checkpoints),
context_tree: Arc::clone(&self.context_tree),
transducer: Arc::clone(&self.transducer),
finalized_terms: Arc::clone(&self.finalized_terms),
}
}
}