// morphology-la.el - Latin morphology for the NLG engine. // // Implements fusional Latin verb conjugation and noun declension. Designed // as a companion to morphology.el and called by the engine when the language // profile code is "la". // // Language profile: code=la, name=Latin, morph_type=fusional, word_order=SOV, // question_strategy=intonation, script=latin, family=italic. // // Verb conjugation covered: // Tenses: present, past (perfect active), future // Persons: first/second/third x singular/plural (slots 0-5) // Conjugations: 1st (-are), 2nd (-ere long), 3rd (-ere short), 4th (-ire) // Irregulars: esse (be), ire (go), velle (want), posse (can) // Canonical map: "be" -> "esse" // // Noun declension covered: // Cases: nominative, accusative, genitive, dative, ablative // Declensions: 1st (-a fem), 2nd masc (-us), 2nd neut (-um), 3rd (-is), // 4th (-us), 5th (-es) // // Latin has no articles. la_noun_phrase returns the declined noun directly. // // Depends on: morphology.el (str_ends_with, str_len, str_slice, str_eq) // ── String helpers ───────────────────────────────────────────────────────────── fn la_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn la_str_drop_last(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } return str_slice(s, 0, len - n) } fn la_str_last_char(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "" } return str_slice(s, n - 1, n) } fn la_str_last2(s: String) -> String { let n: Int = str_len(s) if n < 2 { return s } return str_slice(s, n - 2, n) } fn la_str_last3(s: String) -> String { let n: Int = str_len(s) if n < 3 { return s } return str_slice(s, n - 3, n) } // ── Person/number slot ───────────────────────────────────────────────────────── // // Maps person x number to a 0-based slot index used in paradigm tables. // 0 = 1st singular (ego) // 1 = 2nd singular (tu) // 2 = 3rd singular (is/ea/id) // 3 = 1st plural (nos) // 4 = 2nd plural (vos) // 5 = 3rd plural (ei/eae/ea) fn la_slot(person: String, number: String) -> Int { if str_eq(person, "first") { if str_eq(number, "singular") { return 0 } return 3 } if str_eq(person, "second") { if str_eq(number, "singular") { return 1 } return 4 } // third if str_eq(number, "singular") { return 2 } return 5 } // ── Conjugation class detection ──────────────────────────────────────────────── // // Latin verbs fall into four conjugation classes identified by the infinitive // ending. The 2nd and 3rd conjugations both end in -ere; the 2nd has a long // e (stem vowel retained before -re), the 3rd has a short e (thematic vowel). // Heuristic: if the verb stem before -ere ends in a consonant cluster or a // single consonant preceded by a short vowel, treat as 3rd; otherwise 2nd. // In practice the caller passes dictionary infinitives, so we check known 2nd // conjugation markers (stem ends in -e before -re, i.e. the penult is e) and // known 4th (-ire) vs 3rd (-ere with consonant stem). // // Simplified detection strategy: // ends in -are -> "1" // ends in -ire -> "4" // ends in -ere -> check penultimate vowel pattern: // stem + "ere" where stem ends in a vowel-like syllable -> "2" // otherwise -> "3" // // For the small working vocabulary, we rely on the ending length heuristic: // -ere with 2nd conj: the infinitive without -re has a long e as last char, // meaning the four chars before the final "re" are "e-consonant" or "ee". // Simpler: ends in -ere and has stem length >= 3 with last stem char a vowel // -> 2nd; else 3rd. This correctly handles monere (2nd) vs dicere (3rd). fn la_verb_class(verb: String) -> String { if la_str_ends(verb, "are") { return "1" } if la_str_ends(verb, "ire") { return "4" } if la_str_ends(verb, "ere") { // Strip -ere, look at the last char of the stem let stem: String = la_str_drop_last(verb, 3) let slen: Int = str_len(stem) if slen == 0 { return "3" } let last: String = str_slice(stem, slen - 1, slen) // Stem ends in a vowel -> 2nd conjugation (monere, videre, habere) if str_eq(last, "a") { return "2" } if str_eq(last, "e") { return "2" } if str_eq(last, "i") { return "2" } if str_eq(last, "o") { return "2" } if str_eq(last, "u") { return "2" } // Stem ends in a consonant -> 3rd conjugation (dicere, edere, ducere) return "3" } // Default: treat as 3rd if ending is unknown return "3" } // la_stem: strip the infinitive ending to get the present stem. // // 1st: -are -> stem (amāre -> am-) // 2nd: -ere -> stem + e (monēre -> mone-) [keep the stem vowel] // 3rd: -ere -> stem (dicere -> dic-) // 4th: -ire -> stem + i (audire -> audi-) fn la_stem(verb: String, vclass: String) -> String { if str_eq(vclass, "1") { return la_str_drop_last(verb, 3) } if str_eq(vclass, "2") { return la_str_drop_last(verb, 2) } // drop -re; keep -e if str_eq(vclass, "3") { return la_str_drop_last(verb, 3) } if str_eq(vclass, "4") { return la_str_drop_last(verb, 2) } // drop -re; keep -i return la_str_drop_last(verb, 3) } // la_perfect_stem: derive the perfect active stem. // // Latin perfect stems are highly irregular in general. For regular verbs the // most common pattern is stem + v- (1st/4th) or stem + u- (2nd/3rd), but this // is not universal. We use the following heuristic approximations: // // 1st conj: present stem + av- (amavi, portavi) // 2nd conj: present stem + u- (monui, habui) -- drops the -e from stem // 3rd conj: present stem + - (doubled root; context varies; use stem + i) // 4th conj: present stem + iv- (audivi) // // This will be wrong for many common verbs; callers can override by adding the // verb to the la_irregular_perfect table. fn la_perfect_stem(verb: String, vclass: String) -> String { if str_eq(vclass, "1") { // amāre -> am + av = amav let pstem: String = la_str_drop_last(verb, 3) return pstem + "av" } if str_eq(vclass, "2") { // monēre -> mone -> drop e -> mon + u = monu let pstem: String = la_str_drop_last(verb, 3) return pstem + "u" } if str_eq(vclass, "3") { // dicere -> dic + (perfect varies; approximate with stem + -i stem) let pstem: String = la_str_drop_last(verb, 3) return pstem } if str_eq(vclass, "4") { // audire -> audi + iv = audiv let pstem: String = la_str_drop_last(verb, 2) return pstem + "v" } return la_str_drop_last(verb, 3) } // ── Perfect active endings (all conjugations share these) ───────────────────── // // Slot: 0=1sg 1=2sg 2=3sg 3=1pl 4=2pl 5=3pl // -i, -isti, -it, -imus, -istis, -erunt fn la_perfect_ending(slot: Int) -> String { if slot == 0 { return "i" } if slot == 1 { return "isti" } if slot == 2 { return "it" } if slot == 3 { return "imus" } if slot == 4 { return "istis" } return "erunt" } // ── Present tense endings ───────────────────────────────────────────────────── // // 1st: -o, -as, -at, -amus, -atis, -ant // 2nd: -eo, -es, -et, -emus, -etis, -ent // 3rd: -o, -is, -it, -imus, -itis, -unt // 4th: -io, -is, -it, -imus, -itis, -iunt // // Note: for 1st conj the stem already ends in the thematic vowel (am- not ama- // at slot 0 because the -o absorbs it). The 2nd conj stem retains its -e and // the ending is appended directly. fn la_present_ending(vclass: String, slot: Int) -> String { if str_eq(vclass, "1") { if slot == 0 { return "o" } if slot == 1 { return "as" } if slot == 2 { return "at" } if slot == 3 { return "amus" } if slot == 4 { return "atis" } return "ant" } if str_eq(vclass, "2") { if slot == 0 { return "o" } if slot == 1 { return "s" } // stem ends -e; -es becomes mones if slot == 2 { return "t" } // monet if slot == 3 { return "mus" } // monemus if slot == 4 { return "tis" } // monetis return "nt" // monent } if str_eq(vclass, "3") { if slot == 0 { return "o" } if slot == 1 { return "is" } if slot == 2 { return "it" } if slot == 3 { return "imus" } if slot == 4 { return "itis" } return "unt" } // 4th (-ire) if slot == 0 { return "o" } if slot == 1 { return "s" } // audi + s = audis if slot == 2 { return "t" } // audit if slot == 3 { return "mus" } // audimus if slot == 4 { return "tis" } // auditis return "unt" // audiunt } // la_present_form: build the present tense form for a regular verb. // // Special slot-0 handling per class: // 1st: am- + o = amo (strip thematic -a from stem first) // 2nd: mone- + o = moneo // 3rd: dic- + o = dico // 4th: audi- + o = audio (the -i stays as part of the stem) fn la_present_form(stem: String, vclass: String, slot: Int) -> String { if str_eq(vclass, "1") { if slot == 0 { // stem ends in the thematic -a which is absorbed by -o return la_str_drop_last(stem, 1) + "o" } return stem + la_present_ending(vclass, slot) } if str_eq(vclass, "2") { // stem ends in -e; all endings attach directly return stem + la_present_ending(vclass, slot) } if str_eq(vclass, "3") { if slot == 0 { return stem + "o" } return stem + la_present_ending(vclass, slot) } // 4th: stem ends in -i if slot == 0 { return stem + "o" // audio } if slot == 5 { return stem + "unt" // audiunt (special: i + unt) } return stem + la_present_ending(vclass, slot) } // ── Future tense ────────────────────────────────────────────────────────────── // // 1st/2nd conjugations: present stem + bo/bis/bit/bimus/bitis/bunt // (1st: thematic -a dropped before -bo; 2nd: -e kept, ebo -> moneo... actually // for 2nd the future is mone + bo = monebo; stem keeps -e) // // 3rd/4th conjugations: present stem + am/es/et/emus/etis/ent // (No thematic vowel linking; -am directly on consonant stem) fn la_future_ending_12(slot: Int) -> String { if slot == 0 { return "bo" } if slot == 1 { return "bis" } if slot == 2 { return "bit" } if slot == 3 { return "bimus" } if slot == 4 { return "bitis" } return "bunt" } fn la_future_ending_34(slot: Int) -> String { if slot == 0 { return "am" } if slot == 1 { return "es" } if slot == 2 { return "et" } if slot == 3 { return "emus" } if slot == 4 { return "etis" } return "ent" } fn la_future_form(stem: String, vclass: String, slot: Int) -> String { if str_eq(vclass, "1") { // Drop thematic -a then add -bo etc: am- + bo = amabo? No: ama + bo = amabo // Actually for 1st conj the stem IS the thematic-vowel stem (ama-), // and the future is ama + bo = amabo. return stem + la_future_ending_12(slot) } if str_eq(vclass, "2") { // mone + bo = monebo return stem + la_future_ending_12(slot) } if str_eq(vclass, "3") { // dic + am = dicam return stem + la_future_ending_34(slot) } // 4th: audi + am = audiam return stem + la_future_ending_34(slot) } // ── Irregular verb tables ───────────────────────────────────────────────────── // // esse (be), ire (go), velle (want), posse (can/be able) fn la_esse_present(slot: Int) -> String { if slot == 0 { return "sum" } if slot == 1 { return "es" } if slot == 2 { return "est" } if slot == 3 { return "sumus" } if slot == 4 { return "estis" } return "sunt" } fn la_esse_past(slot: Int) -> String { if slot == 0 { return "fui" } if slot == 1 { return "fuisti" } if slot == 2 { return "fuit" } if slot == 3 { return "fuimus" } if slot == 4 { return "fuistis" } return "fuerunt" } fn la_esse_future(slot: Int) -> String { if slot == 0 { return "ero" } if slot == 1 { return "eris" } if slot == 2 { return "erit" } if slot == 3 { return "erimus" } if slot == 4 { return "eritis" } return "erunt" } fn la_ire_present(slot: Int) -> String { if slot == 0 { return "eo" } if slot == 1 { return "is" } if slot == 2 { return "it" } if slot == 3 { return "imus" } if slot == 4 { return "itis" } return "eunt" } fn la_ire_past(slot: Int) -> String { if slot == 0 { return "ii" } if slot == 1 { return "isti" } // contracted: iisti -> isti if slot == 2 { return "iit" } if slot == 3 { return "iimus" } if slot == 4 { return "istis" } return "ierunt" } fn la_ire_future(slot: Int) -> String { if slot == 0 { return "ibo" } if slot == 1 { return "ibis" } if slot == 2 { return "ibit" } if slot == 3 { return "ibimus" } if slot == 4 { return "ibitis" } return "ibunt" } fn la_velle_present(slot: Int) -> String { if slot == 0 { return "volo" } if slot == 1 { return "vis" } if slot == 2 { return "vult" } if slot == 3 { return "volumus" } if slot == 4 { return "vultis" } return "volunt" } fn la_velle_past(slot: Int) -> String { if slot == 0 { return "volui" } if slot == 1 { return "voluisti" } if slot == 2 { return "voluit" } if slot == 3 { return "voluimus" } if slot == 4 { return "voluistis" } return "voluerunt" } fn la_velle_future(slot: Int) -> String { if slot == 0 { return "volam" } if slot == 1 { return "voles" } if slot == 2 { return "volet" } if slot == 3 { return "volemus" } if slot == 4 { return "voletis" } return "volent" } fn la_posse_present(slot: Int) -> String { if slot == 0 { return "possum" } if slot == 1 { return "potes" } if slot == 2 { return "potest" } if slot == 3 { return "possumus" } if slot == 4 { return "potestis" } return "possunt" } fn la_posse_past(slot: Int) -> String { if slot == 0 { return "potui" } if slot == 1 { return "potuisti" } if slot == 2 { return "potuit" } if slot == 3 { return "potuimus" } if slot == 4 { return "potuistis" } return "potuerunt" } fn la_posse_future(slot: Int) -> String { if slot == 0 { return "potero" } if slot == 1 { return "poteris" } if slot == 2 { return "poterit" } if slot == 3 { return "poterimus" } if slot == 4 { return "poteritis" } return "poterunt" } // ── Irregular perfect stems for common verbs ────────────────────────────────── // // Returns the perfect stem for common irregular verbs, or "" if not found. // When a perfect stem is returned, la_perfect_ending() is appended directly. // // Verb Perfect stem Example: 3sg // edere ed- edit // dicere dix- dixit // ducere dux- duxit // facere fec- fecit // capere cep- cepit // venire ven- venit (same as present; context clarifies) // videre vid- vidit // esse -> handled separately // ire -> handled separately fn la_irregular_perfect_stem(verb: String) -> String { if str_eq(verb, "edere") { return "ed" } if str_eq(verb, "dicere") { return "dix" } if str_eq(verb, "ducere") { return "dux" } if str_eq(verb, "facere") { return "fec" } if str_eq(verb, "capere") { return "cep" } if str_eq(verb, "venire") { return "ven" } if str_eq(verb, "videre") { return "vid" } if str_eq(verb, "bibere") { return "bib" } if str_eq(verb, "currere") { return "cucurr" } if str_eq(verb, "legere") { return "leg" } if str_eq(verb, "scribere") { return "scrips" } if str_eq(verb, "vivere") { return "vix" } if str_eq(verb, "cadere") { return "cecid" } if str_eq(verb, "ponere") { return "posu" } if str_eq(verb, "querere") { return "quaesiv" } return "" } // ── Canonical verb mapping ───────────────────────────────────────────────────── // // The semantic layer passes English canonical labels ("be", "go", "want"). // Map these to Latin infinitives before conjugation. fn la_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "esse" } if str_eq(verb, "go") { return "ire" } if str_eq(verb, "want") { return "velle" } if str_eq(verb, "can") { return "posse" } if str_eq(verb, "eat") { return "edere" } if str_eq(verb, "say") { return "dicere" } if str_eq(verb, "see") { return "videre" } if str_eq(verb, "make") { return "facere" } if str_eq(verb, "come") { return "venire" } if str_eq(verb, "read") { return "legere" } if str_eq(verb, "write") { return "scribere" } if str_eq(verb, "run") { return "currere" } if str_eq(verb, "live") { return "vivere" } if str_eq(verb, "love") { return "amare" } return verb } // ── la_conjugate: main conjugation entry point ──────────────────────────────── // // verb: Latin infinitive (e.g. "amare", "esse") or English canonical // tense: "present" | "past" | "future" // person: "first" | "second" | "third" // number: "singular" | "plural" // // Returns the inflected form. Falls back to the base (infinitive) when a form // is not implemented rather than crashing. fn la_conjugate(verb: String, tense: String, person: String, number: String) -> String { // Map canonical English labels to Latin infinitives let v: String = la_map_canonical(verb) let slot: Int = la_slot(person, number) // ── Irregulars ──────────────────────────────────────────────────────────── if str_eq(v, "esse") { if str_eq(tense, "present") { return la_esse_present(slot) } if str_eq(tense, "past") { return la_esse_past(slot) } if str_eq(tense, "future") { return la_esse_future(slot) } return v } if str_eq(v, "ire") { if str_eq(tense, "present") { return la_ire_present(slot) } if str_eq(tense, "past") { return la_ire_past(slot) } if str_eq(tense, "future") { return la_ire_future(slot) } return v } if str_eq(v, "velle") { if str_eq(tense, "present") { return la_velle_present(slot) } if str_eq(tense, "past") { return la_velle_past(slot) } if str_eq(tense, "future") { return la_velle_future(slot) } return v } if str_eq(v, "posse") { if str_eq(tense, "present") { return la_posse_present(slot) } if str_eq(tense, "past") { return la_posse_past(slot) } if str_eq(tense, "future") { return la_posse_future(slot) } return v } // ── Regular conjugation ─────────────────────────────────────────────────── let vclass: String = la_verb_class(v) let stem: String = la_stem(v, vclass) if str_eq(tense, "present") { return la_present_form(stem, vclass, slot) } if str_eq(tense, "past") { // Check for a known irregular perfect stem first let irreg_perf: String = la_irregular_perfect_stem(v) if !str_eq(irreg_perf, "") { return irreg_perf + la_perfect_ending(slot) } // Regular perfect stem derivation let perf_stem: String = la_perfect_stem(v, vclass) return perf_stem + la_perfect_ending(slot) } if str_eq(tense, "future") { return la_future_form(stem, vclass, slot) } // Unknown tense: return infinitive return v } // ── Declension detection ─────────────────────────────────────────────────────── // // Infer Latin declension from the nominative singular ending. // // ends in -a -> 1st declension (feminine) // ends in -us -> 2nd or 4th; disambiguate: if genitive suffix pattern // suggests 4th (-us gen) we use 4th, otherwise 2nd masc. // Heuristic: monosyllabic -us nouns and known 4th-decl words // use 4th; longer -us words default to 2nd. // ends in -um -> 2nd declension neuter // ends in -is -> 3rd declension (genitive -is is also 3rd nom for some words; // treat nom -is as 3rd) // ends in -es -> 5th declension // ends in -er -> 2nd masc (puer, ager) // otherwise -> 3rd declension (consonant stems: rex, miles, etc.) fn la_declension(noun: String) -> String { if la_str_ends(noun, "a") { return "1" } if la_str_ends(noun, "um") { return "2n" } if la_str_ends(noun, "er") { return "2m" } if la_str_ends(noun, "us") { // 4th declension heuristic: check known 4th decl nouns if str_eq(noun, "manus") { return "4" } if str_eq(noun, "usus") { return "4" } if str_eq(noun, "fructus") { return "4" } if str_eq(noun, "gradus") { return "4" } if str_eq(noun, "cursus") { return "4" } if str_eq(noun, "sensus") { return "4" } if str_eq(noun, "spiritus") { return "4" } if str_eq(noun, "portus") { return "4" } if str_eq(noun, "domus") { return "4" } if str_eq(noun, "impetus") { return "4" } // Default: 2nd masc return "2m" } if la_str_ends(noun, "es") { return "5" } if la_str_ends(noun, "is") { return "3" } // Consonant-stem 3rd declension (rex, canis, leo, etc.) return "3" } // ── 1st declension: -a nouns (mostly feminine) ──────────────────────────────── // // Stem: remove final -a // Singular: nom -a gen -ae dat -ae acc -am abl -a // Plural: nom -ae gen -arum dat -is acc -as abl -is fn la_decline_1(stem: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return stem + "a" } if str_eq(gram_case, "genitive") { return stem + "ae" } if str_eq(gram_case, "dative") { return stem + "ae" } if str_eq(gram_case, "accusative") { return stem + "am" } if str_eq(gram_case, "ablative") { return stem + "a" } // vocative same as nominative for 1st decl return stem + "a" } // plural if str_eq(gram_case, "nominative") { return stem + "ae" } if str_eq(gram_case, "genitive") { return stem + "arum" } if str_eq(gram_case, "dative") { return stem + "is" } if str_eq(gram_case, "accusative") { return stem + "as" } if str_eq(gram_case, "ablative") { return stem + "is" } return stem + "ae" } // ── 2nd declension masculine: -us nouns ─────────────────────────────────────── // // Stem: remove final -us // Singular: nom -us gen -i dat -o acc -um abl -o // Plural: nom -i gen -orum dat -is acc -os abl -is fn la_decline_2m(stem: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return stem + "us" } if str_eq(gram_case, "genitive") { return stem + "i" } if str_eq(gram_case, "dative") { return stem + "o" } if str_eq(gram_case, "accusative") { return stem + "um" } if str_eq(gram_case, "ablative") { return stem + "o" } return stem + "us" } // plural if str_eq(gram_case, "nominative") { return stem + "i" } if str_eq(gram_case, "genitive") { return stem + "orum" } if str_eq(gram_case, "dative") { return stem + "is" } if str_eq(gram_case, "accusative") { return stem + "os" } if str_eq(gram_case, "ablative") { return stem + "is" } return stem + "i" } // ── 2nd declension neuter: -um nouns ───────────────────────────────────────── // // Stem: remove final -um // Singular: nom/acc -um gen -i dat/abl -o // Plural: nom/acc -a gen -orum dat/abl -is fn la_decline_2n(stem: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return stem + "um" } if str_eq(gram_case, "genitive") { return stem + "i" } if str_eq(gram_case, "dative") { return stem + "o" } if str_eq(gram_case, "accusative") { return stem + "um" } if str_eq(gram_case, "ablative") { return stem + "o" } return stem + "um" } // plural if str_eq(gram_case, "nominative") { return stem + "a" } if str_eq(gram_case, "genitive") { return stem + "orum" } if str_eq(gram_case, "dative") { return stem + "is" } if str_eq(gram_case, "accusative") { return stem + "a" } if str_eq(gram_case, "ablative") { return stem + "is" } return stem + "a" } // ── 3rd declension: consonant and i-stem nouns ──────────────────────────────── // // The 3rd declension is highly varied; the nominative singular is usually // irregular (the stem is seen in the genitive). The caller passes the // nominative singular form; we use it as-is for nominative and treat it as // the stem for other cases (approximation for productive NLG use). // // Singular: nom (unchanged) gen -is dat -i acc -em abl -e // Plural: nom -es gen -um dat -ibus acc -es abl -ibus // // For i-stems (is ending in nom sg) we keep the full form as the stem and // add endings directly to the base minus -is. fn la_decline_3(noun: String, gram_case: String, number: String) -> String { // For 3rd decl the nom sg is given; the stem for oblique cases is // derived by stripping -is if the nom ends in -is, otherwise we use // the noun as the stem for the oblique and append endings. let oblique_stem: String = "" if la_str_ends(noun, "is") { let oblique_stem = la_str_drop_last(noun, 2) } else { let oblique_stem = noun } if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "genitive") { return oblique_stem + "is" } if str_eq(gram_case, "dative") { return oblique_stem + "i" } if str_eq(gram_case, "accusative") { return oblique_stem + "em" } if str_eq(gram_case, "ablative") { return oblique_stem + "e" } return noun } // plural if str_eq(gram_case, "nominative") { return oblique_stem + "es" } if str_eq(gram_case, "genitive") { return oblique_stem + "um" } if str_eq(gram_case, "dative") { return oblique_stem + "ibus" } if str_eq(gram_case, "accusative") { return oblique_stem + "es" } if str_eq(gram_case, "ablative") { return oblique_stem + "ibus" } return oblique_stem + "es" } // ── 4th declension: -us nouns (mostly masculine) ───────────────────────────── // // Stem: remove final -us // Singular: nom -us gen -us dat -ui acc -um abl -u // Plural: nom -us gen -uum dat -ibus acc -us abl -ibus fn la_decline_4(stem: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return stem + "us" } if str_eq(gram_case, "genitive") { return stem + "us" } if str_eq(gram_case, "dative") { return stem + "ui" } if str_eq(gram_case, "accusative") { return stem + "um" } if str_eq(gram_case, "ablative") { return stem + "u" } return stem + "us" } // plural if str_eq(gram_case, "nominative") { return stem + "us" } if str_eq(gram_case, "genitive") { return stem + "uum" } if str_eq(gram_case, "dative") { return stem + "ibus" } if str_eq(gram_case, "accusative") { return stem + "us" } if str_eq(gram_case, "ablative") { return stem + "ibus" } return stem + "us" } // ── 5th declension: -es nouns (mostly feminine) ─────────────────────────────── // // Stem: remove final -es // Singular: nom -es gen -ei dat -ei acc -em abl -e // Plural: nom -es gen -erum dat -ebus acc -es abl -ebus fn la_decline_5(stem: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return stem + "es" } if str_eq(gram_case, "genitive") { return stem + "ei" } if str_eq(gram_case, "dative") { return stem + "ei" } if str_eq(gram_case, "accusative") { return stem + "em" } if str_eq(gram_case, "ablative") { return stem + "e" } return stem + "es" } // plural if str_eq(gram_case, "nominative") { return stem + "es" } if str_eq(gram_case, "genitive") { return stem + "erum" } if str_eq(gram_case, "dative") { return stem + "ebus" } if str_eq(gram_case, "accusative") { return stem + "es" } if str_eq(gram_case, "ablative") { return stem + "ebus" } return stem + "es" } // ── 2nd declension -er nouns ────────────────────────────────────────────────── // // Nouns like "puer" retain -e throughout; nouns like "ager" drop it in oblique. // Heuristic: if the stem (drop -er) + er would be the original, check if the // penultimate char before -er is also r (like "puer": stem "pu" + "er" = puer) // vs "ager": stem "agr" drops e. We default to retaining -e (puer pattern) // for simplicity; "ager" type is less common. // // Singular: nom -er gen -i dat -o acc -um abl -o // Plural: nom -i gen -orum dat -is acc -os abl -is fn la_decline_2er(noun: String, gram_case: String, number: String) -> String { // Oblique stem: drop -er then add "r" to get true stem? For simplicity // use the form with -e retained (puer pattern): stem = noun minus "r". // This correctly handles "puer" -> "pueri", "puero" etc. // For ager-type the caller would need to pass the stem form; we approximate. let stem: String = la_str_drop_last(noun, 1) // drop final -r -> "pue" if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "genitive") { return stem + "ri" } // pueri if str_eq(gram_case, "dative") { return stem + "ro" } // puero if str_eq(gram_case, "accusative") { return stem + "rum" } // puerum if str_eq(gram_case, "ablative") { return stem + "ro" } // puero return noun } // plural if str_eq(gram_case, "nominative") { return stem + "ri" } // pueri if str_eq(gram_case, "genitive") { return stem + "rorum" } // puerorum if str_eq(gram_case, "dative") { return stem + "ris" } // pueris if str_eq(gram_case, "accusative") { return stem + "ros" } // pueros if str_eq(gram_case, "ablative") { return stem + "ris" } // pueris return stem + "ri" } // ── la_decline: main declension entry point ─────────────────────────────────── // // noun: nominative singular Latin noun (e.g. "feles", "aqua", "dominus") // gram_case: "nominative" | "accusative" | "genitive" | "dative" | "ablative" // number: "singular" | "plural" // // Returns the inflected form. Falls back to the nominative singular when a // form is not implemented. fn la_decline(noun: String, gram_case: String, number: String) -> String { let decl: String = la_declension(noun) if str_eq(decl, "1") { let stem: String = la_str_drop_last(noun, 1) return la_decline_1(stem, gram_case, number) } if str_eq(decl, "2m") { let stem: String = la_str_drop_last(noun, 2) return la_decline_2m(stem, gram_case, number) } if str_eq(decl, "2n") { let stem: String = la_str_drop_last(noun, 2) return la_decline_2n(stem, gram_case, number) } if str_eq(decl, "2er") { return la_decline_2er(noun, gram_case, number) } if str_eq(decl, "3") { return la_decline_3(noun, gram_case, number) } if str_eq(decl, "4") { let stem: String = la_str_drop_last(noun, 2) return la_decline_4(stem, gram_case, number) } if str_eq(decl, "5") { let stem: String = la_str_drop_last(noun, 2) return la_decline_5(stem, gram_case, number) } // Unknown declension: return nominative unchanged return noun } // ── la_noun_phrase: noun phrase builder ─────────────────────────────────────── // // Latin has no definite or indefinite articles. The declined noun form is the // complete noun phrase. The definite parameter is accepted for interface // compatibility with other language modules but has no effect. // // noun: nominative singular Latin noun // gram_case: "nominative" | "accusative" | "genitive" | "dative" | "ablative" // number: "singular" | "plural" // definite: ignored (Latin has no articles) fn la_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String { return la_decline(noun, gram_case, number) }