Rename: nlg → elp (Engram Language Protocol)

This commit is contained in:
Will Anderson
2026-05-02 22:15:25 -05:00
parent cbb27b8d87
commit 34725a3988
125 changed files with 129214 additions and 780 deletions
+506
View File
@@ -0,0 +1,506 @@
// morphology-gez.el - Ge'ez morphology for the NLG engine.
// ግዕዝ Classical Ethiopic, the liturgical language of the
// Ethiopian Orthodox Church and ancestor of Amharic/Tigrinya.
//
// Implements Ge'ez verb conjugation (perfect and imperfect, basic G-stem),
// noun declension (nominative, accusative, construct; singular and plural),
// and noun-phrase construction.
//
// Ge'ez (also: Classical Ethiopic, gǝʿǝz) is the ancient Semitic language
// of the Kingdom of Axum (ca. 300900 CE active liturgy; ca. 1st century BCE
// epigraphic attestation). It remains the liturgical language of the
// Ethiopian and Eritrean Orthodox Churches. Modern Ethiopian Semitic languages
// (Amharic, Tigrinya, Tigre) descend from Ge'ez or a closely related ancestor.
//
// Script: Ge'ez Fidel (ፊደል) an Ethiopic abugida (Unicode U+1200U+137F).
// Each Fidel character encodes a consonant + vowel combination (7 orders per
// consonant). String literals in this file use actual Unicode characters.
//
// NOTE on El runtime: the El VM currently outputs non-ASCII as numeric hashes
// (runtime limitation for non-Latin scripts). str_eq and string comparisons
// work correctly internally. When the VM adds full UTF-8 output, all Ge'ez
// strings will display correctly automatically.
//
// Language profile:
// code=gez, name=Ge'ez, morph_type=semitic, word_order=SOV,
// script=ethiopic-fidel, family=semitic/south-ethiopic-semitic
//
// Key grammatical facts:
// - SOV word order unusual for Semitic (Arabic, Hebrew, Akkadian are VSO);
// Ge'ez shares SOV with modern Ethiopian Semitic descendants
// - Semitic trilateral root system (root + vowel pattern = word)
// - Gender: masculine (default) / feminine (often marked with -t suffix)
// - Number: singular / plural; dual vestigial
// - Cases: nominative (unmarked), accusative -a (animate masc nouns),
// construct/genitive (various; simplified to base form here)
// - Plural: no single rule; common patterns: -āt (fem/animate), -ān (masc),
// broken (internal vowel change, unpredictable without lexicon)
// - Verb system:
// Perfect (suffix conjugation): completed action
// Imperfect (prefix conjugation): ongoing / future / habitual
// Four derived stems: basic (G), causative (ʾa-), intensive (doubling),
// passive (te-); this file implements basic G-stem throughout
// - No definite article (unlike Amharic which has suffixed -u/-wa/-itu etc.)
// - Copula: root kwn (ሆነ honä) "to be / become"
//
// Verb conjugation conventions:
// person: "first" | "second" | "third"
// gender: "m" | "f"
// number: "singular" | "plural"
// tense: "perfect" | "imperfect"
//
// Noun declension conventions:
// gram_case: "nom" | "acc" | "construct"
// number: "singular" | "plural"
//
// Verbs covered (root / Fidel form / transliteration):
// "kwn" / ሆነ (honä) to be / become (copula)
// "hlw" / ሀሎ (hallo) to exist / there is
// "hbl" / ሰጠ (säṭṭä) to give
// "rʾy" / አየ (ʾayyä) to see
// "qwl" / ተናገረ (tänagärä) to speak
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with)
// String helpers
fn gez_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn gez_str_len(s: String) -> Int {
return str_len(s)
}
fn gez_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)
}
// Slot index
//
// Maps person × number to a 0-based slot for table lookups.
// Gender is handled separately for third-person disambiguation.
//
// Slot layout (6 primary cells):
// 0 = 1sg (I)
// 1 = 2sg (you sg gender note: Ge'ez distinguishes 2sg m/f in perfect)
// 2 = 3sg m (he)
// 3 = 3sg f (she)
// 4 = 1pl (we)
// 5 = 3pl (they default masc)
fn gez_slot(person: String, number: String) -> Int {
if str_eq(person, "first") {
if str_eq(number, "plural") { return 4 }
return 0
}
if str_eq(person, "second") {
return 1
}
// third
if str_eq(number, "plural") { return 5 }
return 2
}
// gez_slot_g: gender-sensitive slot for third-person singular.
fn gez_slot_g(person: String, gender: String, number: String) -> Int {
let base: Int = gez_slot(person, number)
if str_eq(person, "third") {
if str_eq(number, "singular") {
if str_eq(gender, "f") { return 3 }
}
}
return base
}
// Copula: kwn / ሆነ to be / become
//
// Perfect paradigm (suffix forms):
// 3sg m: ሆነ honä (base form)
// 3sg f: ሆነት honät (-t suffix)
// 2sg m: ሆንከ honkä (-kä suffix)
// 2sg f: ሆንኪ honki (-ki suffix)
// 1sg: ሆንኩ honku (-ku suffix)
// 3pl m: ሆኑ honu (-u suffix)
// 3pl f: ሆና honā ( suffix)
// 2pl: ሆንክሙ honkǝmu (-kǝmu suffix)
// 1pl: ሆንነ honna (-na suffix)
//
// Imperfect paradigm (prefix forms):
// 3sg m: ይሆን yǝhon (yǝ- prefix)
// 3sg f: ትሆን tǝhon (tǝ- prefix)
// 2sg: ትሆን tǝhon (tǝ- prefix)
// 1sg: እሆን ʾǝhon (ʾǝ- prefix)
// 3pl m: ይሆኑ yǝhonu (yǝ- + -u suffix)
// 1pl: ንሆን nǝhon (nǝ- prefix)
fn gez_kwn_perfect(slot: Int) -> String {
if slot == 0 { return "ሆንኩ" } // 1sg honku
if slot == 1 { return "ሆንከ" } // 2sg m honkä (default; fem: ሆንኪ)
if slot == 2 { return "ሆነ" } // 3sg m honä
if slot == 3 { return "ሆነት" } // 3sg f honät
if slot == 4 { return "ሆንነ" } // 1pl honna
return "ሆኑ" // 3pl honu
}
fn gez_kwn_imperfect(slot: Int) -> String {
if slot == 0 { return "እሆን" } // 1sg ʾǝhon
if slot == 1 { return "ትሆን" } // 2sg tǝhon
if slot == 2 { return "ይሆን" } // 3sg m yǝhon
if slot == 3 { return "ትሆን" } // 3sg f tǝhon (same prefix as 2sg)
if slot == 4 { return "ንሆን" } // 1pl nǝhon
return "ይሆኑ" // 3pl yǝhonu
}
fn gez_is_copula(verb: String) -> Bool {
if str_eq(verb, "kwn") { return true }
if str_eq(verb, "ሆነ") { return true }
if str_eq(verb, "hona") { return true }
if str_eq(verb, "be") { return true }
return false
}
fn gez_conjugate_copula(tense: String, slot: Int) -> String {
if str_eq(tense, "imperfect") { return gez_kwn_imperfect(slot) }
return gez_kwn_perfect(slot)
}
// hlw / ሀሎ to exist / there is
//
// hallo is an existential copula used for "there is/are".
// In Ge'ez it is largely invariant in its classical usage (presentational).
// We provide a minimal paradigm; for existential use, hallo is returned for
// all slots in the "perfect" (existential present).
fn gez_hlw_perfect(slot: Int) -> String {
// Invariant existential for most purposes
if slot == 0 { return "ሀሎኩ" } // 1sg halloku
if slot == 1 { return "ሀሎከ" } // 2sg hallokä
if slot == 2 { return "ሀሎ" } // 3sg m hallo
if slot == 3 { return "ሀለወት" } // 3sg f halläwät
if slot == 4 { return "ሀሎነ" } // 1pl hallonä
return "ሀሉ" // 3pl hallu
}
fn gez_hlw_imperfect(slot: Int) -> String {
if slot == 0 { return "እሀሉ" } // 1sg
if slot == 1 { return "ትሀሉ" } // 2sg
if slot == 2 { return "ይሀሉ" } // 3sg m
if slot == 3 { return "ትሀሉ" } // 3sg f
if slot == 4 { return "ንሀሉ" } // 1pl
return "ይሀልዉ" // 3pl
}
// hbl / ሰጠ to give
//
// Perfect: säṭṭä (3sg m base); standard G-stem suffix paradigm.
// Imperfect: yǝsäṭ (3sg m).
fn gez_hbl_perfect(slot: Int) -> String {
if slot == 0 { return "ሰጠኩ" } // 1sg säṭṭäku
if slot == 1 { return "ሰጠከ" } // 2sg säṭṭäkä
if slot == 2 { return "ሰጠ" } // 3sg m säṭṭä
if slot == 3 { return "ሰጠት" } // 3sg f säṭṭät
if slot == 4 { return "ሰጠነ" } // 1pl säṭṭänä
return "ሰጡ" // 3pl säṭṭu
}
fn gez_hbl_imperfect(slot: Int) -> String {
if slot == 0 { return "እሰጥ" } // 1sg ʾǝsäṭ
if slot == 1 { return "ትሰጥ" } // 2sg tǝsäṭ
if slot == 2 { return "ይሰጥ" } // 3sg m yǝsäṭ
if slot == 3 { return "ትሰጥ" } // 3sg f tǝsäṭ
if slot == 4 { return "ንሰጥ" } // 1pl nǝsäṭ
return "ይሰጡ" // 3pl yǝsäṭu
}
// rʾy / አየ to see
//
// Third-weak verb (final ʾ). Perfect: ʾayyä (3sg m); imperfect: yāy (3sg m).
fn gez_ray_perfect(slot: Int) -> String {
if slot == 0 { return "አየኩ" } // 1sg ʾayyäku
if slot == 1 { return "አየከ" } // 2sg ʾayyäkä
if slot == 2 { return "አየ" } // 3sg m ʾayyä
if slot == 3 { return "አየት" } // 3sg f ʾayyät
if slot == 4 { return "አየነ" } // 1pl ʾayyänä
return "አዩ" // 3pl ʾayyu
}
fn gez_ray_imperfect(slot: Int) -> String {
if slot == 0 { return "እያይ" } // 1sg ʾǝyāy
if slot == 1 { return "ትያይ" } // 2sg tǝyāy
if slot == 2 { return "ያይ" } // 3sg m yāy
if slot == 3 { return "ትያይ" } // 3sg f tǝyāy
if slot == 4 { return "ንያይ" } // 1pl nǝyāy
return "ያዩ" // 3pl yāyu
}
// qwl / ተናገረ to speak
//
// tänagärä is actually a derived (reciprocal / D-stem) form of the root ngrǝ,
// used as the ordinary word for "to speak" in Classical Ge'ez.
// Perfect: tänagärä (3sg m); imperfect: yǝnagär (3sg m).
fn gez_qwl_perfect(slot: Int) -> String {
if slot == 0 { return "ተናገርኩ" } // 1sg tänagärku
if slot == 1 { return "ተናገርከ" } // 2sg tänagärkä
if slot == 2 { return "ተናገረ" } // 3sg m tänagärä
if slot == 3 { return "ተናገረት" } // 3sg f tänagärät
if slot == 4 { return "ተናገርነ" } // 1pl tänagärnä
return "ተናገሩ" // 3pl tänagäru
}
fn gez_qwl_imperfect(slot: Int) -> String {
if slot == 0 { return "እናገር" } // 1sg ʾǝnagär
if slot == 1 { return "ትናገር" } // 2sg tǝnagär
if slot == 2 { return "ይናገር" } // 3sg m yǝnagär
if slot == 3 { return "ትናገር" } // 3sg f tǝnagär
if slot == 4 { return "ንናገር" } // 1pl nǝnagär
return "ይናገሩ" // 3pl yǝnagäru
}
// Generic G-stem paradigm
//
// For regular strong verbs not in the lookup table.
// Ge'ez perfect suffixes: 1sg -ku, 2sg -kä, 3sg m , 3sg f -at, 1pl -nä, 3pl -u
// Ge'ez imperfect prefixes: 1sg ʾǝ-, 2sg tǝ-, 3sg m yǝ-, 3sg f tǝ-, 1pl nǝ-
fn gez_generic_perfect(base3sg: String, slot: Int) -> String {
if slot == 0 { return base3sg + "" } // -ku
if slot == 1 { return base3sg + "" } // -kä
if slot == 2 { return base3sg } //
if slot == 3 { return base3sg + "" } // -at (simplified: -t Fidel)
if slot == 4 { return base3sg + "" } // -nä
return base3sg + "" // -u (3pl)
}
fn gez_generic_imperfect(base3sg: String, slot: Int) -> String {
// base3sg is the 3sg m imperfect form (with yǝ- prefix)
// We heuristically return the stem with different prefixes.
if slot == 0 { return "" + base3sg } // ʾǝ-
if slot == 1 { return "" + base3sg } // tǝ-
if slot == 2 { return "" + base3sg } // yǝ-
if slot == 3 { return "" + base3sg } // tǝ-
if slot == 4 { return "" + base3sg } // nǝ-
return "" + base3sg + "" // yǝ- + -u (3pl)
}
// Known-verb dispatcher
fn gez_known_verb(verb: String, tense: String, slot: Int) -> String {
// kwn / ሆነ to be
if str_eq(verb, "kwn") {
return gez_conjugate_copula(tense, slot)
}
if str_eq(verb, "ሆነ") {
return gez_conjugate_copula(tense, slot)
}
if str_eq(verb, "hona") {
return gez_conjugate_copula(tense, slot)
}
// hlw / ሀሎ to exist
if str_eq(verb, "hlw") {
if str_eq(tense, "imperfect") { return gez_hlw_imperfect(slot) }
return gez_hlw_perfect(slot)
}
if str_eq(verb, "ሀሎ") {
if str_eq(tense, "imperfect") { return gez_hlw_imperfect(slot) }
return gez_hlw_perfect(slot)
}
if str_eq(verb, "hallo") {
if str_eq(tense, "imperfect") { return gez_hlw_imperfect(slot) }
return gez_hlw_perfect(slot)
}
// hbl / ሰጠ to give
if str_eq(verb, "hbl") {
if str_eq(tense, "imperfect") { return gez_hbl_imperfect(slot) }
return gez_hbl_perfect(slot)
}
if str_eq(verb, "ሰጠ") {
if str_eq(tense, "imperfect") { return gez_hbl_imperfect(slot) }
return gez_hbl_perfect(slot)
}
if str_eq(verb, "sätta") {
if str_eq(tense, "imperfect") { return gez_hbl_imperfect(slot) }
return gez_hbl_perfect(slot)
}
// rʾy / አየ to see
if str_eq(verb, "rʾy") {
if str_eq(tense, "imperfect") { return gez_ray_imperfect(slot) }
return gez_ray_perfect(slot)
}
if str_eq(verb, "አየ") {
if str_eq(tense, "imperfect") { return gez_ray_imperfect(slot) }
return gez_ray_perfect(slot)
}
if str_eq(verb, "ʾayya") {
if str_eq(tense, "imperfect") { return gez_ray_imperfect(slot) }
return gez_ray_perfect(slot)
}
// qwl / ተናገረ to speak
if str_eq(verb, "qwl") {
if str_eq(tense, "imperfect") { return gez_qwl_imperfect(slot) }
return gez_qwl_perfect(slot)
}
if str_eq(verb, "ተናገረ") {
if str_eq(tense, "imperfect") { return gez_qwl_imperfect(slot) }
return gez_qwl_perfect(slot)
}
if str_eq(verb, "tänagärä") {
if str_eq(tense, "imperfect") { return gez_qwl_imperfect(slot) }
return gez_qwl_perfect(slot)
}
return ""
}
// Main conjugation entry point
//
// gez_conjugate: conjugate a Ge'ez verb (G-stem / basic stem).
//
// verb: root (e.g. "kwn", "rʾy"), Fidel citation form (e.g. "ሆነ"),
// or transliterated 3sg perfect (e.g. "hona", "ʾayya")
// tense: "perfect" | "imperfect"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns:
// - Fidel string (Unicode) for known verbs
// - verb unchanged as safe fallback for unknown verbs
fn gez_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let slot: Int = gez_slot(person, number)
if gez_is_copula(verb) {
return gez_conjugate_copula(tense, slot)
}
let known: String = gez_known_verb(verb, tense, slot)
if !str_eq(known, "") {
return known
}
return verb
}
// Noun declension
//
// gez_decline: decline a Ge'ez noun for gram_case and number.
//
// Case system (simplified for this engine):
// Nominative: base form (unmarked the Fidel form as given)
// Accusative: base + -a (for animate masculine nouns; inanimate often same)
// Construct: base form (genitive/construct; detailed vowel alternations
// are lexically irregular simplified to base here)
//
// Plural patterns (highly irregular in Ge'ez, like Arabic broken plurals):
// Common productive suffixes:
// Feminine/animate: -āt (ሀዋርያት hawāryāt apostles)
// Masculine: -ān (ነቢያን nabiyān prophets)
// Broken plurals: unpredictable must come from vocabulary layer.
// Fallback: base + "āt" ( default)
//
// noun: base singular form (Fidel or transliterated)
// gram_case: "nom" | "acc" | "construct"
// number: "singular" | "plural"
fn gez_is_fidel(noun: String) -> Bool {
// Ethiopic Unicode block starts at U+1200 ().
// We detect by checking a set of common Fidel first characters.
let n: Int = gez_str_len(noun)
if n == 0 { return false }
let first: String = str_slice(noun, 0, 1)
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
if str_eq(first, "") { return true }
return false
}
fn gez_decline(noun: String, gram_case: String, number: String) -> String {
if str_eq(number, "plural") {
// Plural: default suffix -āt (Fidel: append transliterated marker for
// non-Fidel input; for Fidel input append ት as the -āt marker)
if gez_is_fidel(noun) {
return noun + "ዎች" // simplified -woc plural marker (Ge'ez -āt )
}
return noun + "āt"
}
// Singular
if str_eq(gram_case, "acc") {
// Accusative: add -a suffix
if gez_is_fidel(noun) {
return noun + "" // accusative object marker (simplified)
}
return noun + "a"
}
// Nominative and construct: return base form
return noun
}
// Noun phrase
//
// gez_noun_phrase: produce the surface noun phrase.
//
// Ge'ez has no definite article (unlike Amharic's suffix -u/-wa/-itu).
// Definiteness is expressed through word order and context.
// The definite parameter is accepted for interface uniformity but has no
// surface effect.
//
// noun: base noun (Fidel string or transliteration)
// gram_case: "nom" | "acc" | "construct"
// number: "singular" | "plural"
// definite: "true" | "false" (no surface effect in Ge'ez)
fn gez_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
return gez_decline(noun, gram_case, number)
}
// Canonical verb mapping
//
// gez_map_canonical: map cross-lingual English canonical verb labels to
// Ge'ez roots or citation forms.
fn gez_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "kwn" }
if str_eq(verb, "exist") { return "hlw" }
if str_eq(verb, "give") { return "hbl" }
if str_eq(verb, "see") { return "rʾy" }
if str_eq(verb, "speak") { return "qwl" }
if str_eq(verb, "say") { return "qwl" }
return verb
}