verba 0.5.1

A library for working with Latin words.
Documentation
use std::fmt;
use smallvec::{smallvec};

use crate::decline as D;
use crate::inflection as I;
use crate::noun::endings as E;
use crate::unicode as U;

use super::{Group, Number, Gender, Case};

#[derive(Clone, Debug)]
pub struct Regular {
    nominative: String,
    genitive: String,
    gender: Gender,
    i_stem: bool,
}

impl fmt::Display for Regular {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}, {}", &self.nominative, &self.genitive)
    }
}

#[derive(Clone, Debug)]
pub enum RegularError {
    InvalidGenitiveEnding(),
    InvalidThirdDeclensionSingularGenitive(),
    InvalidSingularNominative(),
    InvalidSingularGenitive(),
    InvalidSingularNominativeAndGenitive(),
    InconsistenGrouping(Option<Group>),
}

impl fmt::Display for RegularError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self {
            RegularError::InvalidGenitiveEnding() => {
                write!(f, "The ending of the singular genitive form does not match that of any declension group.")
            },
            RegularError::InvalidThirdDeclensionSingularGenitive() => {
                write!(f, "The ending of the singular genitive form is not valid for third declension nouns.")
            },
            RegularError::InvalidSingularNominative() => {
                write!(f, "The provided singular nominative form is invalid.")
            },
            RegularError::InvalidSingularGenitive() => {
                write!(f, "The provided singular genitive form is invalid.")
            },
            RegularError::InvalidSingularNominativeAndGenitive() => {
                write!(f, "Both the provided singular nominative and singular genitive forms are invalid.")
            },
            RegularError::InconsistenGrouping(group) => {
                match group {
                    Some(Group::First) => write!(f, "Singular nominative and genitive forms are inconsistent. First declension nouns should have a singular nominative ending in 'a'."),
                    Some(Group::Second) => write!(f, "Singular nominative and genitive forms are inconsistent. Second declension nouns should have a singular nominative ending in 'us' or 'um'."),
                    Some(Group::Third) => write!(f, "Singular nominative is empty."),
                    Some(Group::Fourth) => write!(f, "Singular nominative and genitive forms are inconsistent. Fourth declension nouns should have a singular nominative ending in 'us' or 'ū'."),
                    Some(Group::Fifth) => write!(f, "Singular nominative and genitive forms are inconsistent. Fifth declension nouns should have a singular nominative ending in 'ēs'."),
                    None => write!(f, "Singular genitive form did not match any regular declension group."),
                }

            }
        }
    }
}

impl Regular {
    /// Creates a regular Latin noun.
    /// 
    /// The dictionary form of a regular Latin noun consistes of its nominative
    /// and genitive forms. For example, for the second declension neuter noun
    /// bellum, war, the dictionary form would be bellum, bellī. Latin nouns 
    /// are also gendered. Latin, like German, has three genders: masculine, 
    /// feminine, and neuter. 
    /// 
    /// This function takes a nominative and genitive form and a gender. With 
    /// this information, the correct declined form for any case can be 
    /// determined. 
    /// 
    /// # Warning
    /// 
    /// Third declension nouns can be either consonant-stem or i-stem. Third 
    /// declension nouns created with this function will decline as consonant-
    /// stem. When creating a third declension i-stem noun, use 
    /// [`new_third_i_stem`] instead.
    /// 
    /// # Example
    /// ```
    /// use verba::noun as N;
    /// 
    /// let nominative = "bellum".to_string();
    /// let genitive = "bellī".to_string();
    /// 
    /// let noun = N::Regular::new(nominative, genitive, N::Gender::Neuter);
    /// ```
    pub fn new(nominative: String, genitive: String, gender: Gender) -> Result<Regular, RegularError> {
        // Make sure nom and gen are in NFC form before verifying their
        // contents. 
        let nominative = U::normalize(nominative);
        let genitive = U::normalize(genitive);

        match Regular::verify_nom_and_gen(&nominative, &genitive, gender) {
            Ok(maybe) => {
                if maybe {
                    Ok(Regular {
                        nominative,
                        genitive,
                        gender,
                        i_stem: false,
                    })
                } else {
                    Err(RegularError::InconsistenGrouping(super::group(&genitive)))
                }
            }
            Err(error) => Err(error),
        }
    }

    /// Creates a regular third declension Latin noun that declines as an
    /// i-stem rather than a consonant stem. 
    /// 
    /// # Example
    /// ```
    /// use verba::noun as N;
    /// use verba::noun::{Noun};
    /// 
    /// let nominative = "animal".to_string();
    /// let genitive = "animālis".to_string();
    /// 
    /// let noun = N::Regular::new_third_i_stem(nominative, genitive, N::Gender::Neuter).unwrap();
    /// 
    /// assert_eq!(noun.decline(N::Number::Plural, N::Case::Genitive), Some(vec!["animālium".to_string()]));
    /// ```
    pub fn new_third_i_stem(nominative: String, genitive: String, gender: Gender) -> Result<Regular, RegularError> {
        let nominative = U::normalize(nominative);
        let genitive = U::normalize(genitive);

        match super::not_normalized_group(&genitive) {
            Some(Group::Third) => {
                if nominative.is_empty() {
                    Err(RegularError::InvalidSingularNominative())
                } else {
                    Ok(Regular {
                        nominative,
                        genitive,
                        gender,
                        i_stem: true,
                    })
                }
            },
            _ => Err(RegularError::InvalidThirdDeclensionSingularGenitive()),
        }
    }

    /// This function verifies that the nom and gen combination makes sense. 
    /// For example, if you called Regular::new with "porta" and "portī", 
    /// this function would return false, since the ending for nom is clearly 
    /// from a first declension noun while the ending for gen is clearly from a
    /// second declension noun. 
    /// 
    /// If gen's declension group cannot be determined, an error is returned to
    /// indicate an invalid singular genitive form. 
    fn verify_nom_and_gen(nominative: &str, genitive: &str, gender: Gender) -> Result<bool, RegularError> {
        match super::group(&genitive) {
            Some(Group::First) => Ok(Regular::nom_has_ending(nominative, E::first_endings(Number::Singular, Case::Nominative, gender))),
            Some(Group::Second) => {
                Ok(
                    Regular::nom_has_ending(nominative, E::second_endings(Number::Singular, Case::Nominative, gender)) || // There is no need to check for -ius and -ium endings here.
                    Regular::nom_has_ending(nominative, Some(smallvec!["r"])) // E::second_r_endings returns None for the singular nominative so it must be overridden with a correct value here. 
                )
            }, 
            Some(Group::Third) => Ok(!nominative.is_empty()), // The singular nominative for third declension nouns is inconsistent, so if the string isn't empty, it's considered good enough. 
            Some(Group::Fourth) => {
                Ok(
                    Regular::nom_has_ending(nominative, E::fourth_endings(Number::Singular, Case::Nominative, gender)) ||
                    Regular::nom_has_ending(nominative, E::fourth_u_endings(Number::Singular, Case::Nominative, gender))
                )
            },
            Some(Group::Fifth) => Ok(Regular::nom_has_ending(nominative, E::fifth_endings(Number::Singular, Case::Nominative, gender))),
            None => Err(RegularError::InvalidSingularGenitive()),
        }
    }

    /// Verifies that nom's ending appears in the list of valid endings. For
    /// example, if you call this with "porta" and Some(vec!["ae", "a"]), this
    /// function will return true.
    /// 
    /// This function is designed to be called with value returned from one of
    /// the functions in crate::noun::endings, which is why it takes an Option
    /// for its endings value. If endings is None, this function returns false.
    fn nom_has_ending(nominative: &str, endings: Option<E::Suffixes>) -> bool {
        match endings {
            Some(endings) => endings.iter().any(|&ending| nominative.ends_with(ending)),
            None => false,
        }
    }

    /// The singular genitive and dative endings for fifth declension nouns 
    /// differ depending on whether the stem ends with a consonant or a vowel. 
    /// This function determines if the stem ends with a vowel so the correct
    /// singular genitive and dative endings can be obtained. 
    fn stem_ends_with_vowel(&self) -> bool {
        match super::not_normalized_stem(&self.nominative, &self.genitive) {
            Some(stem) => D::does_end_with_vowel(stem),
            // Realistically H::stem should never return false when called
            // inside of Regular. But if it does, it obviously doesn't end 
            // in a so this should return false.
            None => false,
        }
    }

    fn endings(&self, number: Number, case: Case) -> Option<E::Suffixes> {
        match super::not_normalized_group(&self.genitive) {
            Some(Group::First) => E::first_endings(number, case, self.gender),
            Some(Group::Second) if D::is_ius(&self.nominative) => E::second_ius_endings(number, case, self.gender),
            Some(Group::Second) if D::is_r(&self.nominative) => E::second_r_endings(number, case, self.gender),
            Some(Group::Second) => E::second_endings(number, case, self.gender),
            Some(Group::Third) if self.i_stem => E::third_i_stem_endings(number, case, self.gender),
            Some(Group::Third) => E::third_endings(number, case, self.gender),
            Some(Group::Fourth) if self.nominative.ends_with("ū") => E::fourth_u_endings(number, case, self.gender),
            Some(Group::Fourth) => E::fourth_endings(number, case, self.gender),
            Some(Group::Fifth) if self.stem_ends_with_vowel() => E::fifth_vowel_stem_endings(number, case, self.gender),
            Some(Group::Fifth) => E::fifth_endings(number, case, self.gender),
            None => None,
        }
    }
}

impl super::Noun for Regular {
    fn gender(&self) -> Gender {
        self.gender
    }

    fn group(&self) -> Option<Group> {
        super::not_normalized_group(&self.genitive)
    }

    fn stem(&self) -> Option<&str> {
        super::not_normalized_stem(&self.nominative, &self.genitive)
    }

    fn decline(&self, number: Number, case: Case) -> Option<Vec<String>> {
        // There are a handful of declined forms for regular nouns that cannot 
        // be constructed from the combination of a stem and ending. These are
        // overridden here. If none of the override cases apply, then this 
        // function obtains the correct endings and calls I::Stem_with_endings. 
        match (self.group(), number, case) {
            (Some(Group::Second), Number::Singular, Case::Nominative) if D::is_r(&self.nominative) => Some(vec![self.nominative.to_string()]),
            (Some(Group::Second), Number::Singular, Case::Vocative) if D::is_r(&self.nominative) => Some(vec![self.nominative.to_string()]),
            // The singular nominative and vocative form for all third 
            // declension nouns are all irregular. The same is true of the 
            // singular accusative for neuter nouns. In all cases the nom 
            // field in the struct are cloned and returned. 
            (Some(Group::Third), Number::Singular, Case::Nominative) => Some(vec![self.nominative.to_string()]),
            (Some(Group::Third), Number::Singular, Case::Vocative) => Some(vec![self.nominative.to_string()]),
            (Some(Group::Third), Number::Singular, Case::Accusative) if self.gender() == Gender::Neuter => Some(vec![self.nominative.to_string()]),
            _ => {
                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::{is_nfc};

    /// Verifies that a Regular's nom and gen values are in NFC form.
    fn verify_normalization(noun: &Regular) {
        assert!(is_nfc(&noun.nominative), "The singular nominative form of {}, {} is not stored in NFC form.", noun.nominative, noun.genitive);
        assert!(is_nfc(&noun.genitive), "The singular genitive form of {}, {} is not stored in NFC form.", noun.nominative, noun.genitive);
    }

    /// Verifies the internal values of a struct are correct.
    fn verify_struct(noun: &Regular, nominative: &str, genitive: &str, gender: D::Gender) {
        assert_eq!(noun.nominative, nominative.to_string(), "The stored singular nominative is incorrect.");
        assert_eq!(noun.genitive, genitive.to_string(), "The stored singular genitive is incorrect.");
        assert_eq!(noun.gender, gender, "The stored gender is incorrect.");
    }

    /// Creates a Regular and verifies that its String values are in NFC 
    /// form, its contents are correct, and that its gender(), group(), and 
    /// stem() functions return the correct values. 
    fn verify_regular(nominative: &str, genitive: &str, stem: &str, gender: Gender, group: Group) {
        match Regular::new(nominative.to_string(), genitive.to_string(), gender) {
            Ok(noun) => {
                verify_normalization(&noun);
                verify_struct(&noun, nominative, genitive, gender);
                assert_eq!(noun.gender(), gender);
                assert_eq!(noun.group(), Some(group));
                assert_eq!(noun.stem(), Some(stem));
            },
            Err(error) => panic!("Failed to create regular noun {}, {}. Received the following error: {}", nominative, genitive, error),
        }
    }

    #[test]
    fn test_new_first() {
        verify_regular("porta", "portae", "port", Gender::Feminine, Group::First);
    }

    #[test]
    fn test_new_second_masculine() {
        verify_regular("dominus", "dominī", "domin", Gender::Masculine, Group::Second);
    }

    #[test]
    fn test_new_second_neuter() {
        verify_regular("bellum", "bellī", "bell", Gender::Neuter, Group::Second);
    }

    #[test]
    fn test_new_second_r() {
        verify_regular("puer", "puerī", "puer", Gender::Masculine, Group::Second);
    }

    #[test]
    fn test_new_third_masculine() {
        verify_regular("dux", "ducis", "duc", Gender::Masculine, Group::Third);
    }

    #[test]
    fn test_new_third_neuter() {
        verify_regular("nōmen", "nōminis", "nōmin", Gender::Neuter, Group::Third);
    }

    #[test]
    fn test_new_fourth() {
        verify_regular("portus", "portūs", "port", Gender::Masculine, Group::Fourth);
    }

    #[test]
    fn test_new_fifth() {
        verify_regular("rēs", "reī", "r", Gender::Feminine, Group::Fifth);
    }

    #[test]
    fn test_new_fifth_ei() {
        verify_regular("diēs", "diēī", "di", Gender::Feminine, Group::Fifth);
    }
}