verba 0.5.1

A library for working with Latin words.
Documentation
use smallvec::{smallvec, SmallVec};

use super::{Number, Voice, Tense, Mood, Group, Person};

// A type alias for storing suffixes to be attached to a single stem to form a
// single word. 
pub(super) type Suffix<'a> = SmallVec::<[&'a str; 3]>;
// A type alias for storing multiple groups of suffixes that will eventually be
// combined with a stem to form multiple words. 
pub(super) type Suffixes<'a> = SmallVec::<[Suffix<'a>; 2]>;

/// Most regular verbs in Latin can be generated if you know the conjugation 
/// group, person, number, tense, voice, and mood. This function takes those
/// arguments and attempts to automatically generate the suffixes necessary to
/// inflect a verb. 
fn generate_suffixes<'a>(group: Group, person: Person, number: Number, tense: Tense, voice: Voice, mood: Mood) -> Option<Suffixes<'a>> {
    let mut suffixes = Suffixes::new();

    // If a personal ending doesn't exist for the case, none of the remaining 
    // work needs to be done. 
    let personal_endings = personal_endings(group, person, number, tense, voice, mood)?;
    // personal_endings can return up to two endings. If it returns two endings
    // then there needs to be two suffixes to store them. However, I don't want
    // to add them until the end, so this will create empty SmallVecs. 
    for _ in 0..personal_endings.len() {
        suffixes.push(Suffix::new());
    }

    if let Some(stem_vowel) = stem_vowel(group, person, number, tense, voice, mood) {
        suffixes.iter_mut().for_each(|suffix| suffix.push(stem_vowel));
    }

    if let Some(tense_particle) = tense_particle(group, person, number, tense, voice, mood) {
        suffixes.iter_mut().for_each(|suffix| suffix.push(tense_particle));
    }

    // Push each personal ending to the corresponding (order doesn't matter 
    // since up to this point both suffixes are the same) SmallVec. 
    for (suffixes, personal_ending) in suffixes.iter_mut().zip(personal_endings.iter()) {
        suffixes.push(personal_ending);
    }

    Some(suffixes)
}

/// Latin, being an inflected language, uses personal endings to provide
/// information about the action being taken. For example, laudō, I praise,
/// can be switched to the second person by using the personal ending -mus,
/// which gives us laudāmus, we praise. 
/// 
/// This function takes the person, number, tense, and voice a verb and 
/// provides the appropriate personal ending(s). 
fn personal_endings(group: Group, person: Person, number: Number, tense: Tense, voice: Voice, mood: Mood) -> Option<Suffix<'static>> {
    match (mood, voice, tense, person, number) {
        // Personal endings for the indicative mood that only apply to specific
        // conjugation groups.
        (Mood::Indicative, Voice::Active, Tense::Present, Person::Third, Number::Plural) if group == Group::Third || group == Group::Fourth => Some(smallvec!["unt"]),
        (Mood::Indicative, Voice::Active, Tense::Future, Person::First, Number::Singular) if group == Group::Third || group == Group::Fourth => Some(smallvec!["am"]),
        (Mood::Indicative, Voice::Active, Tense::Future, Person::Third, Number::Plural) if group == Group::First || group == Group::Second => Some(smallvec!["unt"]),
        (Mood::Indicative, Voice::Active, Tense::Perfect, Person::Third, Number::Plural) => Some(smallvec!["ērunt", "ēre"]),
        (Mood::Indicative, Voice::Passive, Tense::Present, Person::Third, Number::Plural) if group == Group::Third || group == Group::Fourth => Some(smallvec!["untur"]),
        (Mood::Indicative, Voice::Passive, Tense::Future, Person::Third, Number::Plural) if group == Group::First || group == Group::Second => Some(smallvec!["untur"]),

        // Personal endings for the indicative mood that apply to all 
        // conjugation groups. 
        (Mood::Indicative, Voice::Active, Tense::Perfect, Person::First, Number::Singular) => Some(smallvec!["ī"]),
        (Mood::Indicative, Voice::Active, Tense::Perfect, Person::First, Number::Plural) => Some(smallvec!["imus"]),
        (Mood::Indicative, Voice::Active, Tense::Perfect, Person::Second, Number::Singular) => Some(smallvec!["istī"]),
        (Mood::Indicative, Voice::Active, Tense::Perfect, Person::Second, Number::Plural) => Some(smallvec!["istis"]),
        (Mood::Indicative, Voice::Active, Tense::Perfect, Person::Third, Number::Singular) => Some(smallvec!["it"]),
        (Mood::Indicative, Voice::Active, Tense::Imperfect, Person::First, Number::Singular) => Some(smallvec!["m"]),
        (Mood::Indicative, Voice::Active, Tense::Pluperfect, Person::First, Number::Singular) => Some(smallvec!["m"]),

        // Personal endings for the subjunctive mood that apply to all 
        // conjugation groups. 
        (Mood::Subjunctive, Voice::Active, _, Person::First, Number::Singular) => Some(smallvec!["m"]),
        (Mood::Subjunctive, Voice::Passive, Tense::Present, Person::First, Number::Singular) => Some(smallvec!["r"]),

        // Personal endings that apply to all moods but only specific
        // conjugation groups. 
        (_, Voice::Passive, Tense::Future, Person::First, Number::Singular) if group == Group::Third || group == Group::Fourth => Some(smallvec!["r"]),

        // Personal endings that apply to all moods and all conjugation groups. 
        (_, Voice::Passive, Tense::Imperfect, Person::First, Number::Singular) => Some(smallvec!["r"]),
        (_, Voice::Active, _, Person::First, Number::Singular) => Some(smallvec!["ō"]),
        (_, Voice::Active, _, Person::First, Number::Plural) => Some(smallvec!["mus"]),
        (_, Voice::Active, _, Person::Second, Number::Singular) => Some(smallvec!["s"]),
        (_, Voice::Active, _, Person::Second, Number::Plural) => Some(smallvec!["tis"]),
        (_, Voice::Active, _, Person::Third, Number::Singular) => Some(smallvec!["t"]),
        (_, Voice::Active, _, Person::Third, Number::Plural) => Some(smallvec!["nt"]),
        (_, Voice::Passive, _, Person::First, Number::Singular) => Some(smallvec!["or"]),
        (_, Voice::Passive, _, Person::First, Number::Plural) => Some(smallvec!["mur"]),
        (_, Voice::Passive, _, Person::Second, Number::Singular) => Some(smallvec!["ris", "re"]),
        (_, Voice::Passive, _, Person::Second, Number::Plural) => Some(smallvec!["minī"]),
        (_, Voice::Passive, _, Person::Third, Number::Singular) => Some(smallvec!["tur"]),
        (_, Voice::Passive, _, Person::Third, Number::Plural) => Some(smallvec!["ntur"]),

        // TODO: While this branch cannot be reached at the time of this writing, it will be reachable when the imperative mood is implemented.
        _ => None,
    }
}

/// Certain tense in Latin verbs are indicated by particles. For example, 
/// the future tense is represented by the particle -bi-. Take laudāmus, we
/// praise, as an example. If we want to instead refer to the future tense,
/// we will praise, we would insert -bi-, which gives us laudābimus.
fn tense_particle(group: Group, person: Person, number: Number, tense: Tense, voice: Voice, mood: Mood) -> Option<&'static str> {
    match (mood, voice, tense, person, number) {
        (Mood::Subjunctive, _, Tense::Imperfect, _, _) => None,
        (Mood::Subjunctive, Voice::Active, Tense::Perfect, _, _) => Some("erī"),
        (Mood::Subjunctive, Voice::Active, Tense::Pluperfect, _, _) => Some("issē"),
        (_, Voice::Passive, Tense::Future, Person::First, Number::Singular) if group == Group::Third || group == Group::Fourth => None,
        (_, Voice::Passive, Tense::Future, Person::Second, Number::Singular) if group == Group::First || group == Group::Second => Some("be"),
        (_, _, Tense::Future, Person::First, Number::Singular) if group == Group::First || group == Group::Second => Some("b"),
        (_, _, Tense::Future, Person::Third, Number::Plural) if group == Group::First || group == Group::Second => Some("b"),
        (_, _, Tense::FuturePerfect, Person::First, Number::Singular) => Some("er"),
        (_, _, Tense::Future, _, _) if group == Group::First || group == Group::Second => Some("bi"),
        (_, _, Tense::Imperfect, _, _) => Some(""),
        (_, _, Tense::Pluperfect, _, _) => Some("erā"),
        (_, _, Tense::FuturePerfect, _, _) => Some("eri"),

        _ => None,
    }
}

/// Returns the stem vowel if one exists.
fn stem_vowel(group: Group, person: Person, number: Number, tense: Tense, voice: Voice, mood: Mood) -> Option<&'static str> {
    match (group, mood, voice, person, number) {
        (Group::First, Mood::Indicative, Voice::Active, Person::First, Number::Singular) if tense == Tense::Present => None,
        (Group::First, Mood::Indicative, Voice::Passive, Person::First, Number::Singular) if tense == Tense::Present => None,
        (Group::First, Mood::Indicative, _, _, _) if tense == Tense::Present || tense == Tense::Imperfect || tense == Tense::Future => Some("ā"),

        (Group::Second, Mood::Indicative, _, _, _) if tense == Tense::Present || tense == Tense::Imperfect || tense == Tense::Future => Some("ē"),

        (Group::Third, Mood::Indicative, Voice::Active, Person::First, Number::Singular) if tense == Tense::Present || tense == Tense::Future => None,
        (Group::Third, Mood::Indicative, Voice::Active, Person::Third, Number::Plural) if tense == Tense::Present => None,
        (Group::Third, Mood::Indicative, Voice::Passive, Person::First, Number::Singular) if tense == Tense::Present => None,
        (Group::Third, Mood::Indicative, Voice::Passive, Person::First, Number::Singular) if tense == Tense::Future => Some("a"),
        (Group::Third, Mood::Indicative, Voice::Passive, Person::Second, Number::Singular) if tense == Tense::Present => Some("e"),
        (Group::Third, Mood::Indicative, Voice::Passive, Person::Third, Number::Plural) if tense == Tense::Present => None,
        (Group::Third, Mood::Indicative, _, _, _) if tense == Tense::Present => Some("i"),
        (Group::Third, Mood::Indicative, _, _, _) if tense == Tense::Imperfect || tense == Tense::Future => Some("ē"),

        (Group::Fourth, Mood::Indicative, Voice::Active, Person::First, Number::Singular) if tense == Tense::Future => Some("ī"),
        (Group::Fourth, Mood::Indicative, Voice::Passive, Person::First, Number::Singular) if tense == Tense::Future => Some("ia"),
        (Group::Fourth, Mood::Indicative, _, _, _) if tense == Tense::Imperfect || tense == Tense::Future => Some(""),
        (Group::Fourth, Mood::Indicative, _, _, _) if tense == Tense::Present => Some("ī"),
        

        (_, Mood::Subjunctive, _, _, _)  if tense == Tense::Imperfect => Some("ē"),
        (Group::First, Mood::Subjunctive, _, _, _)  if tense == Tense::Present => Some("ē"),
        (Group::Second, Mood::Subjunctive, _, _, _)  if tense == Tense::Present => Some(""),
        (Group::Third, Mood::Subjunctive, _, _, _)  if tense == Tense::Present => Some("ā"),
        (Group::Fourth, Mood::Subjunctive, _, _, _) if tense == Tense::Present => Some(""),
        _ => None,
    }
}

pub(super) fn endings<'a>(group: Group, person: Person, number: Number, tense: Tense, voice: Voice, mood: Mood) -> Option<Suffixes<'a>> {
    match (mood, voice, tense, person, number) {
        // Subjunctive tenses that don't exist. 
        (Mood::Subjunctive, _, Tense::Future, _, _) => None,
        (Mood::Subjunctive, _, Tense::FuturePerfect, _, _) => None,

        // Conjugations that include an adjective component and thus cannot be 
        // created by simply combining ending fragments. 
        (Mood::Indicative, Voice::Passive, Tense::Perfect, _, _) => None,
        (Mood::Indicative, Voice::Passive, Tense::Pluperfect, _, _) => None,
        (Mood::Subjunctive, Voice::Passive, Tense::Perfect, _, _) => None,
        (Mood::Subjunctive, Voice::Passive, Tense::Pluperfect, _, _) => None,

        _ => {
            generate_suffixes(group, person, number, tense, voice, mood)
        }
    }
}

/// Provides the proper form of sum for perfect passive conjugations. 
pub(super) fn perfect_passive_sum<'a>(person: Person, number: Number, tense: Tense, mood: Mood) -> Option<&'a str> {
    match (mood, tense, person, number) {
        (Mood::Indicative, Tense::Perfect, Person::First, Number::Singular) => Some("sum"),
        (Mood::Indicative, Tense::Perfect, Person::Second, Number::Singular) => Some("es"),
        (Mood::Indicative, Tense::Perfect, Person::Third, Number::Singular) => Some("est"),
        (Mood::Indicative, Tense::Perfect, Person::First, Number::Plural) => Some("sumus"),
        (Mood::Indicative, Tense::Perfect, Person::Second, Number::Plural) => Some("estis"),
        (Mood::Indicative, Tense::Perfect, Person::Third, Number::Plural) => Some("sunt"),

        (Mood::Indicative, Tense::Pluperfect, Person::First, Number::Singular) => Some("eram"),
        (Mood::Indicative, Tense::Pluperfect, Person::Second, Number::Singular) => Some("erās"),
        (Mood::Indicative, Tense::Pluperfect, Person::Third, Number::Singular) => Some("erat"),
        (Mood::Indicative, Tense::Pluperfect, Person::First, Number::Plural) => Some("erāmus"),
        (Mood::Indicative, Tense::Pluperfect, Person::Second, Number::Plural) => Some("erātis"),
        (Mood::Indicative, Tense::Pluperfect, Person::Third, Number::Plural) => Some("erant"),

        (Mood::Indicative, Tense::FuturePerfect, Person::First, Number::Singular) => Some("erō"),
        (Mood::Indicative, Tense::FuturePerfect, Person::Second, Number::Singular) => Some("eris"),
        (Mood::Indicative, Tense::FuturePerfect, Person::Third, Number::Singular) => Some("erit"),
        (Mood::Indicative, Tense::FuturePerfect, Person::First, Number::Plural) => Some("erimus"),
        (Mood::Indicative, Tense::FuturePerfect, Person::Second, Number::Plural) => Some("eritis"),
        (Mood::Indicative, Tense::FuturePerfect, Person::Third, Number::Plural) => Some("erunt"),

        (Mood::Subjunctive, Tense::Perfect, Person::First, Number::Singular) => Some("sim"),
        (Mood::Subjunctive, Tense::Perfect, Person::Second, Number::Singular) => Some("sīs"),
        (Mood::Subjunctive, Tense::Perfect, Person::Third, Number::Singular) => Some("sit"),
        (Mood::Subjunctive, Tense::Perfect, Person::First, Number::Plural) => Some("sīmus"),
        (Mood::Subjunctive, Tense::Perfect, Person::Second, Number::Plural) => Some("sītis"),
        (Mood::Subjunctive, Tense::Perfect, Person::Third, Number::Plural) => Some("sint"),

        (Mood::Subjunctive, Tense::Pluperfect, Person::First, Number::Singular) => Some("essem"),
        (Mood::Subjunctive, Tense::Pluperfect, Person::Second, Number::Singular) => Some("essēs"),
        (Mood::Subjunctive, Tense::Pluperfect, Person::Third, Number::Singular) => Some("esset"),
        (Mood::Subjunctive, Tense::Pluperfect, Person::First, Number::Plural) => Some("essēmus"),
        (Mood::Subjunctive, Tense::Pluperfect, Person::Second, Number::Plural) => Some("essētis"),
        (Mood::Subjunctive, Tense::Pluperfect, Person::Third, Number::Plural) => Some("essent"),

        _ => None,
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_combine_with_stem() {
        let stem = "laud";
        let stem_vowel = "ā";
        let tense_particle = "";
        let personal_ending = "mus";

        let mut suffix = Suffix::new();
        suffix.push("ō");
        // Ideall the default buffer size for Suffix is large enough for 
        // storing everything on the stack under normal operations. If not, it
        // should be expanded. 
        assert_eq!(suffix.spilled(), false, "Ending spilled onto heap with only one element added.");
        assert_eq!(super::super::conjugate_from_fragments(stem, &suffix), "laudō".to_string(), "Failed to combine verb stem with an ending.");

        let mut suffix = Suffix::new();
        suffix.push(stem_vowel);
        suffix.push(personal_ending);
        assert_eq!(suffix.spilled(), false, "Ending spilled onto heap with only two elements added.");
        assert_eq!(super::super::conjugate_from_fragments(stem, &suffix), "laudāmus".to_string(), "Failed to combine verb stem with an ending.");

        let mut suffix = Suffix::new();
        suffix.push(stem_vowel);
        suffix.push(tense_particle);
        suffix.push(personal_ending);
        assert_eq!(suffix.spilled(), false, "Ending spilled onto heap with only three elements added.");
        assert_eq!(super::super::conjugate_from_fragments(stem, &suffix), "laudābāmus".to_string(), "Failed to combine verb stem with an ending.");

        // Test to ensure the stem vowel `ā` becomes `a` when followed by `nt`.
        match generate_suffixes(Group::First, Person::Third, Number::Plural, Tense::Present, Voice::Active, Mood::Indicative) {
            Some(ref suffix) if suffix.len() == 1=> assert_eq!(super::super::conjugate_from_fragments(stem, &suffix[0]), "laudant" , "The stem vowel `ā` was not converted to `a` when followed with `nt`."),
            _ => panic!("Failed  to generate an Ending for third person plural present active indicative conjugation for the first conjugation group."),
        }

        // Test to ensure the imperfect tenst particle `bā` becomes `ba` when 
        // followed by `nt`.
        match generate_suffixes(Group::First, Person::Third, Number::Plural, Tense::Imperfect, Voice::Active, Mood::Indicative) {
            Some(ref suffix) if suffix.len() == 1 => assert_eq!(super::super::conjugate_from_fragments(stem, &suffix[0]), "laudābant" , "The imperfect tense particle `bā` was not converted to `ba` when followed with `nt`."),
            _ => panic!("Failed  to generate an Ending for third person plural imperfect active indicative conjugation for the first conjugation group."),
        }
    }

}