// morphology-sga.el - Old Irish morphology for the NLG engine. // // Implements Old Irish verb conjugation, noun declension, and initial consonant // lenition for the ca. 600-900 CE period. Designed as a companion to // morphology.el and called by the engine when the language profile code is "sga". // // Language profile: code=sga, name=Old Irish, morph_type=fusional, word_order=VSO, // question_strategy=particle, script=latin, family=celtic. // // Verb conjugation covered: // Tenses: present, past // Persons: first/second/third x singular/plural (slots 0-5) // Forms: absolute (verb-initial position only; conjunct not implemented) // Irregulars: bith (to be, substantive), téit (to go), gaibid (to take/hold), // ad·cí (to see), as·beir (to say) // Copula: "is" — invariant in 3sg; used as the default copular form // Canonical map: "be" -> copula "is" (3sg) / "bith" (substantive be) // // Noun declension covered: // o-stem masculine (like "fer" — man) // ā-stem feminine (like "ben" — woman) // Cases: nominative, vocative, accusative, genitive, dative // Numbers: singular, plural // Definite article: simplified "in" prefix (article + noun) // // Lenition (initial mutation): // b->bh, c->ch, d->dh, f->fh, g->gh, m->mh, p->ph, s->sh, t->th // Other initial consonants and vowels are returned unchanged. // // Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with) // ── String helpers ───────────────────────────────────────────────────────────── fn sga_drop(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } return str_slice(s, 0, len - n) } fn sga_first(s: String) -> String { if str_len(s) == 0 { return "" } return str_slice(s, 0, 1) } fn sga_rest(s: String) -> String { let n: Int = str_len(s) if n <= 1 { return "" } return str_slice(s, 1, n) } // ── Person/number slot ───────────────────────────────────────────────────────── // // Maps person x number to a 0-based paradigm slot. // 0 = 1st singular (mé) // 1 = 2nd singular (tú) // 2 = 3rd singular (é/sí/ed) // 3 = 1st plural (sní) // 4 = 2nd plural (sí) // 5 = 3rd plural (é/sí/ed) fn sga_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 } // ── Lenition ─────────────────────────────────────────────────────────────────── // // Initial consonant lenition (séimhiú) — the most common mutation in Old Irish. // Applies after certain prepositions, the genitive of feminine nouns, and many // other grammatical environments. Simplified: only the first consonant is mutated. // // Lenition table (b c d f g m p s t): // b -> bh c -> ch d -> dh f -> fh g -> gh // m -> mh p -> ph s -> sh t -> th // Vowels and other consonants: unchanged. fn sga_lenite(word: String) -> String { let init: String = sga_first(word) let tail: String = sga_rest(word) if str_eq(init, "b") { return "bh" + tail } if str_eq(init, "c") { return "ch" + tail } if str_eq(init, "d") { return "dh" + tail } if str_eq(init, "f") { return "fh" + tail } if str_eq(init, "g") { return "gh" + tail } if str_eq(init, "m") { return "mh" + tail } if str_eq(init, "p") { return "ph" + tail } if str_eq(init, "s") { return "sh" + tail } if str_eq(init, "t") { return "th" + tail } // Vowels and unlenitable consonants (l, n, r, …): unchanged return word } // ── Copula paradigm ──────────────────────────────────────────────────────────── // // The Old Irish copula "is" is largely invariant in the present tense in its // most common (3rd singular assertive) use. The full paradigm has variants // (am, at, is in sg; ammi, adib, it in pl) but for NLG purposes we return "is" // for 3sg and the simplified forms elsewhere. fn sga_copula_present(slot: Int) -> String { if slot == 0 { return "am" } if slot == 1 { return "at" } if slot == 2 { return "is" } if slot == 3 { return "am" } if slot == 4 { return "adib" } return "it" } // ── Substantive "bith" (to be) ───────────────────────────────────────────────── // // "Bith" is the substantive/existential verb for being. It differs from the // copula "is" in that it can carry tense inflection and express existence rather // than predication. // // Present: am, at, is, am, adib, at // Past: ba, ba, ba, bámmar, bádaid, batar fn sga_bith_present(slot: Int) -> String { if slot == 0 { return "am" } if slot == 1 { return "at" } if slot == 2 { return "is" } if slot == 3 { return "am" } if slot == 4 { return "adib" } return "at" } fn sga_bith_past(slot: Int) -> String { if slot == 0 { return "ba" } if slot == 1 { return "ba" } if slot == 2 { return "ba" } if slot == 3 { return "bámmar" } if slot == 4 { return "bádaid" } return "batar" } // ── Irregular verb tables ────────────────────────────────────────────────────── // téit (to go) — strong verb, highly suppletive in the past // Present: tíagu, téit, téit, tíagmai, tíagid, tíagat // Past: lod, lod, luid, lodmar, lodaid, lotar fn sga_teit_present(slot: Int) -> String { if slot == 0 { return "tíagu" } if slot == 1 { return "téit" } if slot == 2 { return "téit" } if slot == 3 { return "tíagmai" } if slot == 4 { return "tíagid" } return "tíagat" } fn sga_teit_past(slot: Int) -> String { if slot == 0 { return "lod" } if slot == 1 { return "lod" } if slot == 2 { return "luid" } if slot == 3 { return "lodmar" } if slot == 4 { return "lodaid" } return "lotar" } // gaibid (to take/hold) — AI class strong verb // Present: gaibim, gaibi, gaibid, gaibmi, gaibthe, gaibid fn sga_gaibid_present(slot: Int) -> String { if slot == 0 { return "gaibim" } if slot == 1 { return "gaibi" } if slot == 2 { return "gaibid" } if slot == 3 { return "gaibmi" } if slot == 4 { return "gaibthe" } return "gaibid" } // ad·cí (to see) — compound verb with deuterotonic stress // Present: ad·ciu, ad·cí, ad·cí, ad·cími, ad·cíthe, ad·ciat fn sga_adci_present(slot: Int) -> String { if slot == 0 { return "ad·ciu" } if slot == 1 { return "ad·cí" } if slot == 2 { return "ad·cí" } if slot == 3 { return "ad·cími" } if slot == 4 { return "ad·cíthe" } return "ad·ciat" } // as·beir (to say) — compound verb // Present: as·biur, as·beir, as·beir, as·beram, as·berid, as·berat fn sga_asbeir_present(slot: Int) -> String { if slot == 0 { return "as·biur" } if slot == 1 { return "as·beir" } if slot == 2 { return "as·beir" } if slot == 3 { return "as·beram" } if slot == 4 { return "as·berid" } return "as·berat" } // ── Canonical verb mapping ───────────────────────────────────────────────────── // // Maps English semantic labels to Old Irish infinitives / citation forms. // Old Irish verbs are cited by 3sg present absolute (the most stable form). fn sga_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "is" } if str_eq(verb, "go") { return "téit" } if str_eq(verb, "take") { return "gaibid" } if str_eq(verb, "hold") { return "gaibid" } if str_eq(verb, "see") { return "ad·cí" } if str_eq(verb, "say") { return "as·beir" } return verb } // ── Regular AI-class verb conjugation ───────────────────────────────────────── // // The AI (first) conjugation is the most productive class in Old Irish. // Stems ending in a broad consonant take broad endings; slender take slender // endings. For simplicity this module uses a single generic set. // // Present absolute (verb-initial position): // Sg: 1st -aim, 2nd -ai, 3rd -aid // Pl: 1st -am, 2nd -aid, 3rd -at // // There is no regular past tense for AI verbs in the simple sense — the engine // falls back to the citation form when past is requested for unknown verbs. fn sga_ai_present(stem: String, slot: Int) -> String { if slot == 0 { return stem + "aim" } if slot == 1 { return stem + "ai" } if slot == 2 { return stem + "aid" } if slot == 3 { return stem + "am" } if slot == 4 { return stem + "aid" } return stem + "at" } // ── sga_conjugate: main conjugation entry point ─────────────────────────────── // // verb: Old Irish citation form or English canonical label // tense: "present" | "past" // person: "first" | "second" | "third" // number: "singular" | "plural" // // Returns the inflected absolute form. Unknown tenses fall back to the citation // form. Conjunct forms (after particles) are not implemented; only absolute // forms are produced. fn sga_conjugate(verb: String, tense: String, person: String, number: String) -> String { let v: String = sga_map_canonical(verb) let slot: Int = sga_slot(person, number) // ── Copula "is" ─────────────────────────────────────────────────────────── if str_eq(v, "is") { if str_eq(tense, "present") { return sga_copula_present(slot) } // Past copula: "ba" is the standard past (invariant simplification) return "ba" } // ── Substantive bith ────────────────────────────────────────────────────── if str_eq(v, "bith") { if str_eq(tense, "present") { return sga_bith_present(slot) } if str_eq(tense, "past") { return sga_bith_past(slot) } return v } // ── téit (to go) ────────────────────────────────────────────────────────── if str_eq(v, "téit") { if str_eq(tense, "present") { return sga_teit_present(slot) } if str_eq(tense, "past") { return sga_teit_past(slot) } return v } // ── gaibid (to take/hold) ───────────────────────────────────────────────── if str_eq(v, "gaibid") { if str_eq(tense, "present") { return sga_gaibid_present(slot) } // Past gaibid: gab- preterite (simplified to slot 2 form for all) return "gab" } // ── ad·cí (to see) ──────────────────────────────────────────────────────── if str_eq(v, "ad·cí") { if str_eq(tense, "present") { return sga_adci_present(slot) } // Past: ro·acc forms exist but are complex; return citation form return v } // ── as·beir (to say) ────────────────────────────────────────────────────── if str_eq(v, "as·beir") { if str_eq(tense, "present") { return sga_asbeir_present(slot) } return v } // ── Regular AI-class verb ───────────────────────────────────────────────── // // Citation form for AI verbs ends in -id (3sg pres abs). Strip -id to get // the stem, then apply AI endings. If the verb doesn't end in -id, use the // whole form as the stem (best-effort). if str_ends_with(v, "id") { let stem: String = sga_drop(v, 2) if str_eq(tense, "present") { return sga_ai_present(stem, slot) } // No regular past implemented: return citation form return v } // Unknown verb: return citation form unchanged return v } // ── o-stem masculine declension ("fer" — man) ───────────────────────────────── // // The o-stem is the most common masculine declension class. // // Singular: nom fer voc a fhir acc fer gen fir dat fiur // Plural: nom fir voc a firu acc firu gen fer dat feraib fn sga_decline_ostem(noun: String, gram_case: String, number: String) -> String { if str_eq(noun, "fer") { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "fer" } if str_eq(gram_case, "vocative") { return "fhir" } if str_eq(gram_case, "accusative") { return "fer" } if str_eq(gram_case, "genitive") { return "fir" } if str_eq(gram_case, "dative") { return "fiur" } return "fer" } if str_eq(gram_case, "nominative") { return "fir" } if str_eq(gram_case, "vocative") { return "firu" } if str_eq(gram_case, "accusative") { return "firu" } if str_eq(gram_case, "genitive") { return "fer" } if str_eq(gram_case, "dative") { return "feraib" } return "fir" } // Generic o-stem masculine: append case endings to noun base. // Nom/Acc sg take no ending; Gen sg syncopates (approximated by +a removed); // the engine uses safe suffix-only approximations here. if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "vocative") { return sga_lenite(noun) } if str_eq(gram_case, "accusative") { return noun } if str_eq(gram_case, "genitive") { return noun + "a" } if str_eq(gram_case, "dative") { return noun + "u" } return noun } if str_eq(gram_case, "nominative") { return noun + "i" } if str_eq(gram_case, "vocative") { return noun + "u" } if str_eq(gram_case, "accusative") { return noun + "u" } if str_eq(gram_case, "genitive") { return noun } if str_eq(gram_case, "dative") { return noun + "aib" } return noun + "i" } // ── ā-stem feminine declension ("ben" — woman) ──────────────────────────────── // // The ā-stem is the most common feminine declension class. It shows heavy // syncope and internal change (ben -> mná in oblique forms). // // Singular: nom ben voc a ben acc bein gen mná dat mnáib // Plural: nom mná voc a mná acc mná gen ban dat mnáib fn sga_decline_astem(noun: String, gram_case: String, number: String) -> String { if str_eq(noun, "ben") { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "ben" } if str_eq(gram_case, "vocative") { return "ben" } if str_eq(gram_case, "accusative") { return "bein" } if str_eq(gram_case, "genitive") { return "mná" } if str_eq(gram_case, "dative") { return "mnáib" } return "ben" } if str_eq(gram_case, "nominative") { return "mná" } if str_eq(gram_case, "vocative") { return "mná" } if str_eq(gram_case, "accusative") { return "mná" } if str_eq(gram_case, "genitive") { return "ban" } if str_eq(gram_case, "dative") { return "mnáib" } return "mná" } // Generic ā-stem feminine: suffix-based approximation. if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "vocative") { return noun } if str_eq(gram_case, "accusative") { return noun + "i" } if str_eq(gram_case, "genitive") { return noun + "e" } if str_eq(gram_case, "dative") { return noun + "aib" } return noun } if str_eq(gram_case, "nominative") { return noun + "a" } if str_eq(gram_case, "vocative") { return noun + "a" } if str_eq(gram_case, "accusative") { return noun + "a" } if str_eq(gram_case, "genitive") { return noun } if str_eq(gram_case, "dative") { return noun + "aib" } return noun + "a" } // ── Gender detection heuristic ───────────────────────────────────────────────── // // Ideally gender is supplied by the lexicon. Known exemplars are routed // explicitly; everything else defaults to o-stem masculine. fn sga_detect_gender(noun: String) -> String { if str_eq(noun, "ben") { return "feminine" } if str_eq(noun, "mná") { return "feminine" } // Default: masculine o-stem return "masculine" } // ── sga_decline: main declension entry point ────────────────────────────────── // // noun: nominative singular Old Irish noun (e.g. "fer", "ben") // gram_case: "nominative" | "vocative" | "accusative" | "genitive" | "dative" // number: "singular" | "plural" // // Returns the inflected form. Falls back to nominative singular on unknown input. fn sga_decline(noun: String, gram_case: String, number: String) -> String { let gender: String = sga_detect_gender(noun) if str_eq(gender, "masculine") { return sga_decline_ostem(noun, gram_case, number) } if str_eq(gender, "feminine") { return sga_decline_astem(noun, gram_case, number) } // Fallback return noun } // ── sga_noun_phrase: noun phrase builder ────────────────────────────────────── // // Old Irish has a definite article that agrees in case, number, and gender. // The article has many allomorphs (int, in, ind, na, …). For NLG purposes this // module uses the simplified invariant form "in" for all definite contexts, // placed before the declined noun. // // Indefinite nouns carry no article (like Latin). // // noun: nominative singular Old Irish noun // gram_case: "nominative" | "vocative" | "accusative" | "genitive" | "dative" // number: "singular" | "plural" // definite: "true" | "false" // // Returns the complete noun phrase string. fn sga_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String { let base: String = sga_decline(noun, gram_case, number) if !str_eq(definite, "true") { return base } // Definite: prepend simplified article "in" return "in " + base }