Files
el/elp/src/morphology-gez.el
T
will.anderson c2cd5e01e1
El SDK CI - dev / build-and-test (pull_request) Successful in 3m34s
fix: elb macOS OpenSSL + C master declarations header; add ELP missing imports
elb.el:
- Auto-detect Homebrew OpenSSL (-L$(brew --prefix openssl)/lib) so -lssl
  resolves on macOS without manual flags; no-op on Linux
- Add -include elp-c-decls.h when present in out_dir: resolves undeclared
  cross-module calls in packages like ELP that lack explicit imports

ELP source:
- Add import "morphology.el" to all 29 language morphology modules
- Add language module imports to morphology.el (all langs it dispatches to)
  These were missing since ELP was originally built as a monolithic unit
2026-05-08 19:44:31 -05:00

508 lines
21 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
import "morphology.el"
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
}