1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
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);
    }
}