Rename: nlg → elp (Engram Language Protocol)
This commit is contained in:
@@ -0,0 +1,454 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user