use std::fmt;
use std::borrow::Cow;
use std::collections::HashMap;
use crate::unicode as U;
use crate::noun::endings as E;
use crate::inflection as I;
use crate::decline as D;
use super::{Group, Number, Gender, Case};
#[derive(Clone, Debug)]
pub struct Irregular {
declensions: HashMap<(Number, Case), Option<Vec<String>>>,
gender: Gender,
stem: String,
group: Group,
alt_declension: bool,
}
#[derive(Clone, Debug)]
pub enum IrregularError {
EmptyDeclension((Number, Case)),
EmptyDeclensionValue((Number, Case)),
}
impl fmt::Display for IrregularError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
IrregularError::EmptyDeclension((number, case)) => {
write!(f, "The declined form for the {} {} is empty", number, case)
},
IrregularError::EmptyDeclensionValue((number, case)) => {
write!(f, "The declined form for the {} {} contains an empty string.", number, case)
},
}
}
}
impl Irregular {
fn normalize_declensions(declensions: &mut HashMap<(Number, Case), Option<Vec<String>>>) {
for maybe_words in declensions.values_mut() {
if let Some(words) = maybe_words {
for word in words.iter_mut() {
if let Cow::Owned(normalized) = U::normalize_if_needed(word) {
*word = normalized;
}
}
}
}
}
fn declensions_not_empty(declensions: &HashMap<(Number, Case), Option<Vec<String>>>) -> Result<(), IrregularError> {
for (key, maybe_declension) in declensions.iter() {
if let Some(declension) = maybe_declension {
if declension.is_empty() {
return Err(IrregularError::EmptyDeclension(*key))
} else {
for word in declension {
if word.is_empty() {
return Err(IrregularError::EmptyDeclensionValue(*key))
}
}
}
}
}
Ok(())
}
pub fn new(mut declensions: HashMap<(Number, Case), Option<Vec<String>>>, gender: Gender, stem: String, group: Group) -> Result<Irregular, IrregularError> {
Irregular::normalize_declensions(&mut declensions);
let stem = U::normalize(stem);
match Irregular::declensions_not_empty(&declensions) {
Ok(()) => {
Ok(Irregular {
declensions,
gender,
stem,
group,
alt_declension: false,
})
},
Err(error) => Err(error),
}
}
fn new_alt_declension(declensions: HashMap<(Number, Case), Option<Vec<String>>>, gender: Gender, stem: String, group: Group) -> Result<Irregular, IrregularError> {
match Irregular::new(declensions, gender, stem, group) {
Ok(mut noun) => {
noun.alt_declension = true;
Ok(noun)
},
Err(error) => Err(error),
}
}
pub fn new_second_ius(declensions: HashMap<(Number, Case), Option<Vec<String>>>, gender: Gender, stem: String) -> Result<Irregular, IrregularError> {
Irregular::new_alt_declension(declensions, gender, stem, Group::Second)
}
pub fn new_third_i_stem(declensions: HashMap<(Number, Case), Option<Vec<String>>>, gender: Gender, stem: String) -> Result<Irregular, IrregularError> {
Irregular::new_alt_declension(declensions, gender, stem, Group::Third)
}
pub fn new_fourth_u(declensions: HashMap<(Number, Case), Option<Vec<String>>>, gender: Gender, stem: String) -> Result<Irregular, IrregularError> {
Irregular::new_alt_declension(declensions, gender, stem, Group::Fourth)
}
fn endings(&self, number: Number, case: Case) -> Option<E::Suffixes> {
match self.group {
Group::First => E::first_endings(number, case, self.gender),
Group::Second if self.alt_declension => E::second_ius_endings(number, case, self.gender),
Group::Second => E::second_endings(number, case, self.gender),
Group::Third if self.alt_declension => E::third_i_stem_endings(number, case, self.gender),
Group::Third => E::third_endings(number, case, self.gender),
Group::Fourth if self.alt_declension => E::fourth_u_endings(number, case, self.gender),
Group::Fourth => E::fourth_endings(number, case, self.gender),
Group::Fifth if D::does_end_with_vowel(&self.stem) => E::fifth_vowel_stem_endings(number, case, self.gender),
Group::Fifth => E::fifth_endings(number, case, self.gender),
}
}
}
impl super::Noun for Irregular {
fn gender(&self) -> Gender {
self.gender
}
fn stem(&self) -> Option<&str> {
Some(&self.stem)
}
fn group(&self) -> Option<Group> {
Some(self.group)
}
fn decline(&self, number: Number, case: Case) -> Option<Vec<String>> {
if let Some(declension) = self.declensions.get(&(number, case)) {
match declension {
Some(words) => Some(words.to_vec()),
None => None,
}
} else {
match self.endings(number, case) {
Some(suffixes) => {
match self.stem() {
Some(stem) => Some(I::stem_with_endings(stem, &suffixes)),
None => None,
}
},
None => None,
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::noun::{Noun, Group, Number, Gender, Case};
use unicode_normalization::{UnicodeNormalization, is_nfc};
fn verify_normalization(declensions: &HashMap<(Number, Case), Option<Vec<String>>>) {
for key in declensions.keys() {
if let Some(Some(words)) = declensions.get(key) {
for word in words {
if is_nfc(word) == false {
panic!("Word {} was not in NFC form.", word);
}
}
}
}
}
fn verify_declension(declension: &Option<Vec<String>>, correct: &Option<Vec<String>>) {
match (declension, correct) {
(Some(declension), Some(correct)) => {
assert!(declension.len() == correct.len(), "The Vecs `declension` and `correct` are not the same length.");
for (index, form) in declension.iter().enumerate() {
assert!(is_nfc(form), "{} is not in normalized composed form.", form);
assert!(form == &correct[index], "Element {}, {}, from `declension` is not equal to element {}, {}, from `correct`.", index, form, index, &correct[index]);
}
},
(Some(_), None) => panic!("Vec `declension` contained data but `correct` was None."),
(None, Some(_)) => panic!("Vec `declension` was None but `correct` contained data."),
(None, None) => (),
}
}
#[test]
fn test_normalize_declensions() {
let mut declensions = HashMap::new();
declensions.insert((Number::Singular, Case::Nominative), Some(vec!["cornū".nfd().collect::<String>()]));
declensions.insert((Number::Singular, Case::Genitive), Some(vec!["cornūs".nfd().collect::<String>()]));
declensions.insert((Number::Singular, Case::Dative), Some(vec!["cornū".nfd().collect::<String>(), "cornūs".nfd().collect::<String>()]));
declensions.insert((Number::Singular, Case::Accusative), Some(vec!["cornū".nfd().collect::<String>()]));
declensions.insert((Number::Singular, Case::Ablative), Some(vec!["cornū".nfd().collect::<String>()]));
declensions.insert((Number::Singular, Case::Vocative), Some(vec!["cornū".nfd().collect::<String>()]));
declensions.insert((Number::Plural, Case::Nominative), Some(vec!["cornū".nfd().collect::<String>()]));
declensions.insert((Number::Plural, Case::Genitive), Some(vec!["cornūs".nfd().collect::<String>()]));
declensions.insert((Number::Plural, Case::Dative), Some(vec!["cornū".nfd().collect::<String>(), "cornūs".nfd().collect::<String>()]));
declensions.insert((Number::Plural, Case::Accusative), Some(vec!["cornū".nfd().collect::<String>()]));
declensions.insert((Number::Plural, Case::Ablative), Some(vec!["cornū".nfd().collect::<String>()]));
declensions.insert((Number::Plural, Case::Vocative), Some(vec!["cornū".nfd().collect::<String>()]));
Irregular::normalize_declensions(&mut declensions);
verify_normalization(&declensions);
}
#[test]
fn test_irregular_vis() {
let mut declension = HashMap::new();
declension.insert((Number::Singular, Case::Nominative), Some(vec!["vīs".to_string()]));
declension.insert((Number::Singular, Case::Genitive), Some(vec!["vīs".to_string()]));
declension.insert((Number::Singular, Case::Dative), Some(vec!["vī".to_string()]));
declension.insert((Number::Singular, Case::Accusative), Some(vec!["vim".to_string()]));
declension.insert((Number::Singular, Case::Ablative), Some(vec!["vī".to_string()]));
declension.insert((Number::Singular, Case::Vocative), Some(vec!["vīs".to_string()]));
declension.insert((Number::Plural, Case::Genitive), Some(vec!["vīrium".to_string()]));
declension.insert((Number::Plural, Case::Accusative), Some(vec!["vīrēs".to_string(), "vīrīs".to_string()]));
match Irregular::new(declension, Gender::Feminine, "vīr".to_string(), Group::Third) {
Ok(noun) => {
verify_declension(&noun.decline(Number::Singular, Case::Nominative), &Some(vec!["vīs".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Genitive), &Some(vec!["vīs".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Dative), &Some(vec!["vī".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Accusative), &Some(vec!["vim".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Ablative), &Some(vec!["vī".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Vocative), &Some(vec!["vīs".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Nominative), &Some(vec!["vīrēs".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Genitive), &Some(vec!["vīrium".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Dative), &Some(vec!["vīribus".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Accusative), &Some(vec!["vīrēs".to_string(), "vīrīs".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Ablative), &Some(vec!["vīribus".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Vocative), &Some(vec!["vīrēs".to_string()]));
},
Err(_) => panic!("Failed to create irregular noun vīs, vīs"),
}
}
#[test]
fn test_irregular_i_stem_vis() {
let mut declension = HashMap::new();
declension.insert((Number::Singular, Case::Nominative), Some(vec!["vīs".to_string()]));
declension.insert((Number::Singular, Case::Genitive), Some(vec!["vīs".to_string()]));
declension.insert((Number::Singular, Case::Dative), Some(vec!["vī".to_string()]));
declension.insert((Number::Singular, Case::Accusative), Some(vec!["vim".to_string()]));
declension.insert((Number::Singular, Case::Ablative), Some(vec!["vī".to_string()]));
declension.insert((Number::Singular, Case::Vocative), Some(vec!["vīs".to_string()]));
declension.insert((Number::Plural, Case::Genitive), Some(vec!["vīrium".to_string()]));
declension.insert((Number::Plural, Case::Accusative), Some(vec!["vīrēs".to_string(), "vīrīs".to_string()]));
match Irregular::new_third_i_stem(declension, Gender::Feminine, "vīr".to_string()) {
Ok(noun) => {
verify_declension(&noun.decline(Number::Singular, Case::Nominative), &Some(vec!["vīs".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Genitive), &Some(vec!["vīs".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Dative), &Some(vec!["vī".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Accusative), &Some(vec!["vim".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Ablative), &Some(vec!["vī".to_string()]));
verify_declension(&noun.decline(Number::Singular, Case::Vocative), &Some(vec!["vīs".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Nominative), &Some(vec!["vīrēs".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Genitive), &Some(vec!["vīrium".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Dative), &Some(vec!["vīribus".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Accusative), &Some(vec!["vīrēs".to_string(), "vīrīs".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Ablative), &Some(vec!["vīribus".to_string()]));
verify_declension(&noun.decline(Number::Plural, Case::Vocative), &Some(vec!["vīrēs".to_string()]));
},
Err(_) => panic!("Failed to create irregular noun vīs, vīs"),
}
}
}