use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hasher};
use super::rules::token;
use super::{Analysis, Dictionary, Entry, Source, normalized_input};
use crate::Error;
use crate::alphabet::Key;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Sample<'a> {
pub word: &'a str,
pub pos: Option<&'a str>,
}
impl<'a> From<&'a str> for Sample<'a> {
fn from(word: &'a str) -> Self {
Sample { word, pos: None }
}
}
impl<'a> From<(&'a str, &'a str)> for Sample<'a> {
fn from((word, pos): (&'a str, &'a str)) -> Self {
Sample {
word,
pos: Some(pos),
}
}
}
pub(super) type OwnWords = HashMap<Box<[u8]>, Vec<OwnForm>, BuildHasherDefault<KeyHasher>>;
#[derive(Clone, PartialEq)]
pub(super) struct OwnForm {
yo: u64,
spelling: String,
entry: Entry,
}
#[derive(Clone, Default)]
pub(super) struct KeyHasher(u64);
impl Hasher for KeyHasher {
fn write(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.0 = (self.0.rotate_left(5) ^ u64::from(byte)).wrapping_mul(0x517c_c1b7_2722_0a95);
}
}
fn finish(&self) -> u64 {
self.0
}
}
impl Dictionary {
pub fn with_words<'a, S: Into<Sample<'a>>>(
mut self,
words: impl IntoIterator<Item = (&'a str, S)>,
) -> Result<Self, Error> {
for (word, sample) in words {
for (spelling, entry) in self.sample_forms(word, sample.into())? {
if !self.alphabet.extend(&spelling) {
return Err(Error::Word(format!(
"В слове «{spelling}» слишком много букв, которых нет в словаре"
)));
}
let Some(key) = self.alphabet.input_key(&spelling) else {
return Err(Error::Word(format!(
"Слово «{spelling}» длиннее {} букв",
crate::alphabet::MAX_KEY
)));
};
let form = OwnForm {
yo: key.yo,
spelling,
entry,
};
let forms = self.own_words.entry(key.bytes().into()).or_default();
if !forms.contains(&form) {
forms.push(form);
}
}
}
Ok(self)
}
fn sample_forms(&self, word: &str, sample: Sample<'_>) -> Result<Vec<(String, Entry)>, Error> {
let normal = |text: &str| {
normalized_input(text)
.as_deref()
.unwrap_or(text)
.to_lowercase()
};
let (word, like) = (normal(word), normal(sample.word));
if word.trim().is_empty() {
return Err(Error::Word("Свое слово пустое".into()));
}
if !token(&word.chars().collect::<Vec<_>>()) {
return Err(Error::Word(format!(
"Свое слово «{}» с пробелом, управляющим или невидимым символом не найдется \
в тексте",
word.escape_debug()
)));
}
let pos = |analysis: &Analysis| self.ud_tag(analysis.ud).map_or("", |tag| tag.pos());
let mut analyses: Vec<Analysis> = self
.candidates(&like)
.into_iter()
.filter(|analysis| analysis.entry.is_some())
.collect();
if analyses.is_empty() {
return Err(Error::Word(format!("Образца «{like}» нет в словаре")));
}
let lemma =
|analysis: &Analysis| analysis.lemma.replace('ё', "е") == like.replace('ё', "е");
if analyses.iter().any(lemma) {
analyses.retain(lemma);
}
let mut found: Vec<&str> = analyses.iter().map(pos).collect();
found.sort_unstable();
found.dedup();
match sample.pos {
Some(wanted) => {
analyses.retain(|analysis| pos(analysis) == wanted);
if analyses.is_empty() {
return Err(Error::Word(format!(
"У образца «{like}» нет лексем {wanted}, есть {}",
found.join(", ")
)));
}
}
None if found.len() > 1 => {
return Err(Error::Word(format!(
"У образца «{like}» лексемы разных частей речи ({}): укажите часть речи",
found.join(", ")
)));
}
None => {}
}
let mut forms = Vec::new();
let mut lexemes: Vec<(u16, &str)> = Vec::new();
for analysis in &analyses {
let Some(entry) = analysis.entry else {
continue;
};
let Some(sample_lexeme) = self.lexeme(entry.table, entry.form, &analysis.word) else {
continue;
};
let (prefix, suffix) = sample_lexeme.affixes(entry.form);
let Some(stem) = word
.strip_prefix(self.prefixes.at(prefix))
.and_then(|rest| rest.strip_suffix(self.suffixes.at(suffix)))
.filter(|stem| !stem.is_empty())
else {
continue;
};
if lexemes.contains(&(entry.table, stem)) {
continue;
}
lexemes.push((entry.table, stem));
let Some(lexeme) = self.table_lexeme(entry.table, stem) else {
continue;
};
for form in 0..lexeme.cells.len() as u16 {
let entry = Entry {
table: entry.table,
form,
};
if self.has_record(&self.spell(&sample_lexeme, form), entry) {
forms.push((self.spell(&lexeme, form), entry));
}
}
}
if forms.is_empty() {
return Err(Error::Word(format!(
"«{word}» не изменяется по образцу «{like}»: у слова и образца должны быть одна \
форма и одно окончание, а у слова - основа"
)));
}
Ok(forms)
}
fn has_record(&self, spelling: &str, entry: Entry) -> bool {
let Some(key) = self.alphabet.input_key(spelling) else {
return false;
};
let packed = (u32::from(entry.table) << 16) | u32::from(entry.form);
let recorded = self.spellings(&key).is_some_and(|spellings| {
spellings.variants().any(|(yo, records)| {
yo == key.yo
&& self
.records_at(records)
.is_some_and(|records| records.contains(&packed))
})
});
recorded || self.own_entries(&key).any(|own| own == entry)
}
pub(super) fn own_candidates(&self, key: &Key, analyses: &mut Vec<Analysis>) {
let Some(forms) = self.own_words.get(key.bytes()) else {
return;
};
let core = |ud: u16| self.ud_core.get(usize::from(ud)).copied();
for own in forms {
if own.yo & key.yo != key.yo {
continue;
}
let entry = own.entry;
let Some(lexeme) = self.lexeme(entry.table, entry.form, &own.spelling) else {
continue;
};
let (lemma, ud) = (self.lemma(&lexeme, entry.form), lexeme.ud(entry.form));
let known = analyses.iter().any(|analysis| {
analysis.word == own.spelling
&& analysis.lemma == lemma
&& core(analysis.ud) == core(ud)
});
if known {
continue;
}
analyses.push(Analysis {
word: own.spelling.clone(),
lemma,
ud,
tag: Some(lexeme.tag(entry.form)),
prob: 0.0,
source: Source::Dictionary,
entry: Some(entry),
});
}
}
pub(super) fn is_own(&self, key: &Key) -> bool {
self.own_words
.get(key.bytes())
.is_some_and(|forms| forms.iter().any(|own| own.yo & key.yo == key.yo))
}
pub(super) fn own_entries<'a>(&'a self, key: &'a Key) -> impl Iterator<Item = Entry> + 'a {
(self.own_words.get(key.bytes()).into_iter().flatten())
.filter(|own| own.yo == key.yo)
.map(|own| own.entry)
}
}