interslavic/
lib.rs

1mod case_endings;
2use case_endings::*;
3mod verb_endings;
4use verb_endings::*;
5mod dictionary_initialization;
6mod irregular_verbs;
7use dictionary_initialization::*;
8use irregular_verbs::*;
9
10#[derive(Debug, Clone, Default)]
11pub struct ISV {
12    pub animate_nouns: Vec<String>,
13    pub nonanimate_nouns: Vec<String>,
14    pub feminine_nouns: Vec<String>,
15    pub neuter_nouns: Vec<String>,
16}
17
18pub struct ISVUTILS {}
19
20#[derive(Debug, PartialEq, Clone)]
21pub struct ComplexNoun {
22    pub head_noun: String,
23    pub adjective: Vec<String>,
24}
25
26impl Default for ComplexNoun {
27    fn default() -> Self {
28        Self {
29            head_noun: "exemplum".into(),
30
31            adjective: Vec::new(),
32        }
33    }
34}
35
36#[derive(Debug, PartialEq, Clone)]
37pub enum Number {
38    Singular,
39    Plural,
40}
41#[derive(Debug, PartialEq, Clone)]
42pub enum Case {
43    Nom,
44    Acc,
45    Gen,
46    Loc,
47    Dat,
48    Ins,
49    //vocative will be handle seperately
50}
51#[derive(Debug, PartialEq, Clone)]
52pub enum Gender {
53    Masculine,
54    Feminine,
55    Neuter,
56}
57
58#[derive(Debug, PartialEq, Clone)]
59pub enum Person {
60    First,
61    Second,
62    Third,
63}
64#[derive(Debug, PartialEq, Clone)]
65pub enum Conjugation {
66    First,
67    Second,
68}
69#[derive(Debug, PartialEq, Clone)]
70pub enum Tense {
71    Present,
72    Imperfect,
73    Future,
74    Perfect,
75    PluPerfect,
76    Conditional,
77}
78
79impl CaseEndings {
80    pub fn ending(&self, case: &Case, number: &Number) -> &str {
81        match number {
82            Number::Singular => match case {
83                Case::Nom => self.nom_sg,
84                Case::Acc => self.acc_sg,
85                Case::Gen => self.gen_sg,
86                Case::Loc => self.loc_sg,
87                Case::Dat => self.dat_sg,
88                Case::Ins => self.ins_sg,
89            },
90            Number::Plural => match case {
91                Case::Nom => self.nom_pl,
92                Case::Acc => self.acc_pl,
93                Case::Gen => self.gen_pl,
94                Case::Loc => self.loc_pl,
95                Case::Dat => self.dat_pl,
96                Case::Ins => self.ins_pl,
97            },
98        }
99    }
100}
101impl VerbEndings {
102    pub fn ending(&self, person: &Person, number: &Number) -> &str {
103        match (person, number) {
104            (Person::First, Number::Singular) => self.first_singular,
105            (Person::Second, Number::Singular) => self.second_singular,
106            (Person::Third, Number::Singular) => self.third_singular,
107            (Person::First, Number::Plural) => self.first_plural,
108            (Person::Second, Number::Plural) => self.second_plural,
109            (Person::Third, Number::Plural) => self.third_plural,
110        }
111    }
112}
113
114pub type Noun = (String, Gender);
115pub type Adjective = String;
116pub type Verb = String;
117
118pub const VOWELS: &[char] = &[
119    'a', 'e', 'i', 'í', 'ó', 'o', 'u', 'å', 'ą', 'ę', 'ė', 'é', 'ȯ', 'ų', 'ů', 'ú', 'ý', 'y', 'ě',
120    'A', 'E', 'I', 'O', 'U', 'á',
121];
122
123pub const HARD_CONSONANTS: &[char] = &[
124    'p', 'b', 'f', 'v', 'm', 's', 'z', 't', 'd', 'r', 'n', 'l', 'k', 'g', 'h',
125];
126
127pub const J_MERGE_CHARS: &[&str] = &["st", "zd", "sk", "zg", "s", "z", "t", "d", "k", "g", "h"];
128
129//VERB STUFF
130impl ISV {
131    pub fn get_present_tense_stem(infinitive: &str) -> (String, Conjugation) {
132        let infinitive_stem = ISV::get_infinitive_stem(infinitive);
133        let irregular = irregular_present_stem(infinitive);
134
135        if irregular != "" {
136            if irregular.ends_with("me") {
137                return (
138                    ISVUTILS::replace_last_occurence(&irregular, "me", "m"),
139                    Conjugation::First,
140                );
141            }
142            if irregular.ends_with("ne") {
143                return (
144                    ISVUTILS::replace_last_occurence(&irregular, "ne", "n"),
145                    Conjugation::First,
146                );
147            }
148
149            if irregular.ends_with("je") {
150                return (
151                    ISVUTILS::replace_last_occurence(&irregular, "je", "j"),
152                    Conjugation::First,
153                );
154            }
155
156            if irregular.ends_with("e") {
157                return (
158                    ISVUTILS::replace_last_occurence(&irregular, "e", ""),
159                    Conjugation::First,
160                );
161            }
162            if irregular.ends_with("i") {
163                return (
164                    ISVUTILS::replace_last_occurence(&irregular, "i", ""),
165                    Conjugation::Second,
166                );
167            }
168        }
169
170        if ISVUTILS::is_consonant(&ISVUTILS::last_in_stringslice(&infinitive_stem)) {
171            (infinitive_stem, Conjugation::First)
172        } else if infinitive.ends_with("ovati") {
173            (infinitive.replace("ovati", "uj"), Conjugation::First)
174        } else if infinitive.ends_with("nųti") {
175            (infinitive.replace("nųti", "n"), Conjugation::First)
176        } else if infinitive.ends_with("ati") {
177            (
178                ISVUTILS::replace_last_occurence(infinitive, "ati", "aj"),
179                Conjugation::First,
180            )
181        } else if infinitive.ends_with("eti") {
182            (
183                ISVUTILS::replace_last_occurence(infinitive, "eti", "ej"),
184                Conjugation::First,
185            )
186        } else if infinitive.ends_with("ęti") {
187            (
188                ISVUTILS::replace_last_occurence(infinitive, "ęti", "n"),
189                Conjugation::First,
190            )
191        } else if infinitive.ends_with("yti") {
192            (
193                ISVUTILS::replace_last_occurence(infinitive, "yti", "yj"),
194                Conjugation::First,
195            )
196        } else if infinitive.ends_with("uti") {
197            (
198                ISVUTILS::replace_last_occurence(infinitive, "uti", "uj"),
199                Conjugation::First,
200            )
201        } else if infinitive.ends_with("iti") {
202            (
203                ISVUTILS::replace_last_occurence(infinitive, "iti", ""),
204                Conjugation::Second,
205            )
206        } else if infinitive.ends_with("ěti") {
207            (
208                ISVUTILS::replace_last_occurence(infinitive, "ěti", ""),
209                Conjugation::Second,
210            )
211        } else {
212            panic!("IMPROPER PERSENT TENSE STEM: {}", infinitive);
213        }
214    }
215    pub fn get_infinitive_stem(word: &str) -> String {
216        ISVUTILS::string_without_last_n(word, 2)
217    }
218
219    pub fn conjugate_verb(
220        &self,
221        word: &str,
222        person: &Person,
223        number: &Number,
224        gender: &Gender,
225        tense: &Tense,
226    ) -> Verb {
227        let word = word.to_lowercase();
228        let (present_stem, conjugation) = ISV::get_present_tense_stem(&word);
229        let infinitive_stem = ISV::get_infinitive_stem(&word);
230
231        let endings = match conjugation {
232            Conjugation::First => &FIRST_CONJUGATION,
233            Conjugation::Second => &SECOND_CONJUGATION,
234        };
235
236        match tense {
237            Tense::Present => {
238                let ending = endings.ending(person, number);
239                let merged = ISVUTILS::iotation_merge(&present_stem, ending);
240                merged
241            }
242
243            _ => panic!("TENSE NOT IMPLEMENTED"),
244        }
245    }
246    pub fn l_participle(&self, word: &str, gender: &Gender, number: &Number) -> Verb {
247        if word == "idti" {
248            match number {
249                Number::Singular => String::from("šli"),
250                Number::Plural => match gender {
251                    Gender::Masculine => String::from("šėl"),
252                    Gender::Feminine => String::from("šla"),
253                    Gender::Neuter => String::from("šlo"),
254                },
255            }
256        } else {
257            let infinitive_stem = ISV::get_infinitive_stem(word);
258            match number {
259                Number::Plural => {
260                    format!("{}{}", infinitive_stem, "li")
261                }
262                Number::Singular => match gender {
263                    Gender::Masculine => {
264                        format!("{}{}", infinitive_stem, "l")
265                    }
266                    Gender::Feminine => {
267                        format!("{}{}", infinitive_stem, "la")
268                    }
269                    Gender::Neuter => {
270                        format!("{}{}", infinitive_stem, "lo")
271                    }
272                },
273            }
274        }
275    }
276}
277
278// ADJECTIVE STUFF
279impl ISV {
280    pub fn decline_adj(
281        &self,
282        word: &str,
283        case: &Case,
284        number: &Number,
285        gender: &Gender,
286        animate: bool,
287    ) -> Adjective {
288        let word = word.to_lowercase();
289        let stem_is_soft = ISV::stem_of_adj_is_soft(&word);
290        let adj_stem = ISV::get_adj_stem(&word);
291
292        let endings = match gender {
293            Gender::Masculine => {
294                if animate {
295                    if stem_is_soft {
296                        &ADJ_ANIMATE_SOFT_ENDINGS
297                    } else {
298                        &ADJ_ANIMATE_HARD_ENDINGS
299                    }
300                } else {
301                    if stem_is_soft {
302                        &ADJ_INANIMATE_SOFT_ENDINGS
303                    } else {
304                        &ADJ_INANIMATE_HARD_ENDINGS
305                    }
306                }
307            }
308            Gender::Feminine => {
309                if stem_is_soft {
310                    &ADJ_FEMININE_SOFT_ENDINGS
311                } else {
312                    &ADJ_FEMININE_HARD_ENDINGS
313                }
314            }
315            Gender::Neuter => {
316                if stem_is_soft {
317                    &ADJ_NEUTER_SOFT_ENDINGS
318                } else {
319                    &ADJ_NEUTER_HARD_ENDINGS
320                }
321            }
322        };
323        let ending = endings.ending(case, number);
324        let merged = format!("{}{}", adj_stem, ending);
325        return merged;
326    }
327
328    pub fn stem_of_adj_is_soft(word: &str) -> bool {
329        if word.ends_with("i") {
330            true
331        } else {
332            false
333        }
334    }
335    pub fn get_adj_stem(word: &str) -> String {
336        let mut adj_stem = word.to_string();
337        adj_stem.pop();
338        adj_stem
339    }
340}
341
342//NOUN STUFF
343impl ISV {
344    pub fn decline_noun(&self, word: &str, case: &Case, number: &Number) -> Noun {
345        let word = word.to_lowercase();
346        let gender = self.guess_gender(&word);
347        let word_is_animate = self.noun_is_animate(&word);
348        let word_stem_is_soft = ISV::stem_of_noun_is_soft(&word);
349        let word_stem = ISV::get_noun_stem(&word, number);
350
351        let endings = if ISV::is_ost_class(&word) {
352            &OST_ENDINGS
353        } else {
354            match gender {
355                Gender::Masculine => {
356                    if word_is_animate {
357                        if word_stem_is_soft {
358                            &ANIMATE_SOFT_ENDINGS
359                        } else {
360                            &ANIMATE_HARD_ENDINGS
361                        }
362                    } else {
363                        if word_stem_is_soft {
364                            &INANIMATE_SOFT_ENDINGS
365                        } else {
366                            &INANIMATE_HARD_ENDINGS
367                        }
368                    }
369                }
370                Gender::Feminine => {
371                    if word_stem_is_soft {
372                        &FEMININE_SOFT_ENDINGS
373                    } else {
374                        &FEMININE_HARD_ENDINGS
375                    }
376                }
377                Gender::Neuter => {
378                    if word_stem_is_soft {
379                        &NEUTER_SOFT_ENDINGS
380                    } else {
381                        &NEUTER_HARD_ENDINGS
382                    }
383                }
384            }
385        };
386
387        let ending = endings.ending(case, number);
388        let merged = format!("{}{}", word_stem, ending);
389        return (merged, gender.clone());
390    }
391    pub fn noun_is_animate(&self, word: &str) -> bool {
392        self.animate_nouns.contains(&word.to_string())
393    }
394
395    pub fn guess_gender(&self, word: &str) -> Gender {
396        let word_string = word.to_string();
397        if self.animate_nouns.contains(&word_string) || self.nonanimate_nouns.contains(&word_string)
398        {
399            return Gender::Masculine;
400        } else if self.feminine_nouns.contains(&word_string) {
401            return Gender::Feminine;
402        } else if self.neuter_nouns.contains(&word_string) {
403            return Gender::Neuter;
404        }
405        let last_one = ISV::last_n_chars(word, 1);
406
407        if ISV::is_ost_class(word) || (last_one == "a") || (last_one == "i") {
408            return Gender::Feminine;
409        } else if (last_one == "o") || (last_one == "e") {
410            return Gender::Neuter;
411        } else {
412            return Gender::Masculine;
413        }
414    }
415
416    pub fn last_n_chars(word: &str, n: usize) -> String {
417        let split_pos = word.char_indices().nth_back(n - 1).unwrap_or((0, 'a')).0;
418        word[split_pos..].into()
419    }
420    pub fn is_ost_class(word: &str) -> bool {
421        word.ends_with("ost́")
422            || word.ends_with("ěć")
423            || word.ends_with("ěč")
424            || word.ends_with("eć")
425            || word.ends_with("at́")
426    }
427
428    pub fn get_noun_stem(word: &str, number: &Number) -> String {
429        if word.ends_with("anin") && number == &Number::Plural {
430            return ISVUTILS::string_without_last_n(word, 2);
431        }
432        if word.ends_with("anina") && number == &Number::Plural {
433            return ISVUTILS::string_without_last_n(word, 3);
434        }
435
436        if ISVUTILS::is_vowel(&ISVUTILS::last_in_stringslice(word)) {
437            return ISVUTILS::string_without_last_n(word, 1);
438        } else {
439            return String::from(word);
440        }
441    }
442    pub fn stem_of_noun_is_soft(word: &str) -> bool {
443        ISVUTILS::ends_with_soft_consonant(&ISV::get_noun_stem(word, &Number::Singular))
444    }
445}
446
447impl ISVUTILS {
448    pub fn replace_last_occurence(input: &str, pattern: &str, replacement: &str) -> String {
449        if let Some(last_index) = input.rfind(pattern) {
450            let (before_last, _after_last) = input.split_at(last_index);
451            format!("{}{}", before_last, replacement)
452        } else {
453            input.into()
454        }
455    }
456    pub fn iotation_merge(root: &str, suffix: &str) -> String {
457        if suffix.chars().nth(0) == Some('j') {
458            for entry in J_MERGE_CHARS {
459                if root.ends_with(entry) {
460                    let new_root = match *entry {
461                        "st" => ISVUTILS::replace_last_occurence(root, entry, "šć"),
462                        "zd" => ISVUTILS::replace_last_occurence(root, entry, "ždž"),
463                        "sk" => ISVUTILS::replace_last_occurence(root, entry, "šč"),
464                        "zg" => ISVUTILS::replace_last_occurence(root, entry, "žž"),
465                        "s" => ISVUTILS::replace_last_occurence(root, entry, "š"),
466                        "z" => ISVUTILS::replace_last_occurence(root, entry, "ž"),
467                        "t" => ISVUTILS::replace_last_occurence(root, entry, "ć"),
468                        "d" => ISVUTILS::replace_last_occurence(root, entry, "dž"),
469                        "k" => ISVUTILS::replace_last_occurence(root, entry, "č"),
470                        "g" => ISVUTILS::replace_last_occurence(root, entry, "ž"),
471                        "h" => ISVUTILS::replace_last_occurence(root, entry, "š"),
472                        _ => root.to_string(),
473                    };
474                    let new_suffix = suffix.get(1..).unwrap();
475                    return format!("{new_root}{new_suffix}");
476                }
477            }
478
479            format!("{root}{suffix}")
480        } else {
481            format!("{root}{suffix}")
482        }
483    }
484
485    pub fn is_vowel(c: &char) -> bool {
486        VOWELS.contains(c)
487    }
488
489    pub fn ends_with_soft_consonant(word: &str) -> bool {
490        ISVUTILS::is_soft_consonant(&ISVUTILS::last_in_stringslice(word))
491    }
492
493    pub fn is_hard_consonant(c: &char) -> bool {
494        HARD_CONSONANTS.contains(c)
495    }
496
497    pub fn is_soft_consonant(c: &char) -> bool {
498        !ISVUTILS::is_hard_consonant(c) && !ISVUTILS::is_vowel(c)
499    }
500    pub fn last_in_stringslice(s: &str) -> char {
501        s.to_string().pop().unwrap_or(' ')
502    }
503    pub fn is_consonant(c: &char) -> bool {
504        !ISVUTILS::is_vowel(c)
505    }
506    pub fn string_without_last_n(s: &str, n: i64) -> String {
507        let mut stringik = s.to_string();
508        for _ in 0..n {
509            stringik.pop();
510        }
511
512        stringik
513    }
514}