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 }
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
129impl 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
278impl 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
342impl 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}