Files
el/elp/src/morphology-egy.el
T
2026-05-02 22:15:25 -05:00

571 lines
24 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-egy.el - Ancient Egyptian (Middle Egyptian) morphology for the NLG engine.
//
// Implements Middle Egyptian verb conjugation (sdm=f / sdm.n=f paradigm),
// noun number marking, suffix pronouns, and noun phrase assembly.
// Designed as a companion to morphology.el; called when language code is "egy".
//
// Language profile: code=egy, name=Ancient Egyptian, morph_type=agglutinative,
// word_order=SVO (Middle Egyptian nominal sentences), question_strategy=particle,
// script=hieroglyphic (transliterated here as ASCII), family=afro-asiatic-egyptian.
//
// Script note: Classical transliteration uses special characters ( š q ).
// This engine uses a safe ASCII mapping:
// A = (aleph/glottal stop) a = (ayin)
// H = (h with dot) x = (velar fricative)
// X = (emphatic h) sh = š (sh sound)
// q = q (emphatic k) T = (tj sound)
// D = (dj sound)
// This mapping keeps all string literals ASCII-safe for the El runtime.
//
// Grammatical notes (Middle Egyptian, ca. 20001300 BCE):
// - Aspectual system: Imperfective (sdm=f), Perfective (sdm.n=f), Prospective
// - "tense" labels used here: "present" (imperfective), "past" (perfective),
// "future" (prospective/sdm.xr=f), "infinitive"
// - Two grammatical genders: masculine (unmarked) and feminine (suffix -t)
// - Number: singular (unmarked), dual (-wy masc / -ty fem), plural (-w masc / -wt fem)
// - No case endings syntactic role expressed by word order and prepositions
// - No definite/indefinite article in Middle Egyptian (Late Egyptian introduced pꜣ/tꜣ/nꜣ)
// - Zero copula: adjectival predicates need no verb "to be" ("nfr sw" = "he is good")
// - Suffix pronouns attach directly to the verb stem with = (e.g. sdm=f "he hears")
//
// Persons/numbers covered (suffix pronoun paradigm):
// person: "first" | "second" | "third"
// gender: "m" | "f" (relevant for 2sg, 3sg; 1sg and plurals often unmarked)
// number: "singular" | "dual" | "plural"
//
// Verbs covered (ASCII transliteration gloss):
// wnn to be/exist (copular auxiliary)
// rdi / di to give
// mAA to see
// Dd to say
// Sm to go
// iri to do / make
// sdm to hear (the paradigm verb for the sdm=f construction)
//
// Canonical English Egyptian mapping:
// "be" wnn / zero copula "give" rdi
// "see" mAA "say" Dd
// "go" Sm "do" iri
// "make" iri "hear" sdm
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with)
// String helpers
fn egy_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn egy_str_len(s: String) -> Int {
return str_len(s)
}
fn egy_drop(s: String, n: Int) -> String {
let len: Int = str_len(s)
if n >= len { return "" }
return str_slice(s, 0, len - n)
}
fn egy_last_char(s: String) -> String {
let n: Int = str_len(s)
if n == 0 { return "" }
return str_slice(s, n - 1, n)
}
// Person/number slot
//
// Maps person × gender × number to a 0-based index used in paradigm tables.
// Egyptian suffix pronouns distinguish gender in 2nd and 3rd person singular.
//
// Slot layout:
// 0 = 1sg (=i)
// 1 = 2sg masc (=k)
// 2 = 2sg fem (=T)
// 3 = 3sg masc (=f)
// 4 = 3sg fem (=s)
// 5 = 1pl (=n)
// 6 = 2pl (=Tn)
// 7 = 3pl (=sn)
// 8 = 1du / 2du / 3du (=sny simplified; dual pronouns are rare in sources)
//
// Dual falls through to slot 8 (a single dual pronoun slot for all persons).
fn egy_slot(person: String, number: String) -> Int {
if str_eq(number, "dual") { return 8 }
if str_eq(person, "first") {
if str_eq(number, "plural") { return 5 }
return 0
}
if str_eq(person, "second") {
if str_eq(number, "plural") { return 6 }
return 1
}
// third person
if str_eq(number, "plural") { return 7 }
return 3
}
// egy_slot_with_gender: slot variant that factors in gender for 2sg and 3sg.
fn egy_slot_with_gender(person: String, gender: String, number: String) -> Int {
if str_eq(number, "dual") { return 8 }
if str_eq(person, "first") {
if str_eq(number, "plural") { return 5 }
return 0
}
if str_eq(person, "second") {
if str_eq(number, "plural") { return 6 }
if str_eq(gender, "f") { return 2 }
return 1
}
// third person
if str_eq(number, "plural") { return 7 }
if str_eq(gender, "f") { return 4 }
return 3
}
// Suffix pronouns
//
// Egyptian suffix pronouns attach to verbs, nouns, and prepositions.
// Written with = before the pronoun in transliteration (e.g. =f = "his / he").
//
// Standard Middle Egyptian paradigm:
// 1sg: =i ("I / me / my")
// 2sg m: =k ("you / your" masc)
// 2sg f: =T ("you / your" fem, in classical)
// 3sg m: =f ("he / him / his")
// 3sg f: =s ("she / her")
// 1pl: =n ("we / us / our")
// 2pl: =Tn ("you all / your" plural)
// 3pl: =sn ("they / them / their")
// dual: =sny (simplified dual rare in Middle Egyptian texts)
fn egy_conjugate_pronoun(person: String, number: String) -> String {
let slot: Int = egy_slot(person, number)
if slot == 0 { return "=i" }
if slot == 1 { return "=k" }
if slot == 5 { return "=n" }
if slot == 6 { return "=Tn" }
if slot == 7 { return "=sn" }
if slot == 8 { return "=sny" }
// slots 24 need gender; default to masc for slot 3
return "=f"
}
fn egy_suffix_pronoun(slot: Int) -> String {
if slot == 0 { return "=i" }
if slot == 1 { return "=k" }
if slot == 2 { return "=T" }
if slot == 3 { return "=f" }
if slot == 4 { return "=s" }
if slot == 5 { return "=n" }
if slot == 6 { return "=Tn" }
if slot == 7 { return "=sn" }
// dual (slot 8)
return "=sny"
}
// Copula detection
//
// In Middle Egyptian the verb "to be" as predicate is often omitted in the
// present (zero copula for adjective predicates). The auxiliary wnn is used
// for existence / substantive "to be" and in subordinate clauses.
// Canonical English label "be" maps to zero copula in the present.
fn egy_is_copula(verb: String) -> Bool {
if str_eq(verb, "wnn") { return true }
if str_eq(verb, "be") { return true }
return false
}
// Copula conjugation
//
// Present ("imperfective"): zero copula for adjectival predicate return "".
// The auxiliary iw...wnn is used in certain syntactic environments but the
// bare zero is the canonical Middle Egyptian form.
// Past ("perfective"): wnn.n (with perfective suffix .n)
// Future ("prospective"): wnn.xr (prospective form, simplified)
fn egy_conjugate_copula(tense: String, slot: Int) -> String {
if str_eq(tense, "present") { return "" }
if str_eq(tense, "past") {
return "wnn.n" + egy_suffix_pronoun(slot)
}
if str_eq(tense, "future") {
return "wnn.xr" + egy_suffix_pronoun(slot)
}
if str_eq(tense, "infinitive") { return "wnn" }
// Default: zero copula
return ""
}
// Irregular verb: rdi / di (to give)
//
// rdi is the full form; di is the common abbreviated written form.
// Imperfective (present): di=f (3sg m), with full pronoun for other persons.
// Perfective (past): di.n=f
// Prospective (future): di.xr=f
// Infinitive: rdi
fn egy_rdi_present(slot: Int) -> String {
return "di" + egy_suffix_pronoun(slot)
}
fn egy_rdi_past(slot: Int) -> String {
return "di.n" + egy_suffix_pronoun(slot)
}
fn egy_rdi_future(slot: Int) -> String {
return "di.xr" + egy_suffix_pronoun(slot)
}
// Irregular verb: mAA (to see)
//
// mAA is a geminated root (m-AA).
// Present: mAA=f; Past: mAA.n=f; Future: mAA.xr=f
fn egy_mAA_present(slot: Int) -> String {
return "mAA" + egy_suffix_pronoun(slot)
}
fn egy_mAA_past(slot: Int) -> String {
return "mAA.n" + egy_suffix_pronoun(slot)
}
fn egy_mAA_future(slot: Int) -> String {
return "mAA.xr" + egy_suffix_pronoun(slot)
}
// Irregular verb: Dd (to say)
//
// Present: Dd=f; Past: Dd.n=f; Future: Dd.xr=f
// Infinitive: Dd
fn egy_Dd_present(slot: Int) -> String {
return "Dd" + egy_suffix_pronoun(slot)
}
fn egy_Dd_past(slot: Int) -> String {
return "Dd.n" + egy_suffix_pronoun(slot)
}
fn egy_Dd_future(slot: Int) -> String {
return "Dd.xr" + egy_suffix_pronoun(slot)
}
// Irregular verb: Sm (to go)
//
// Present: Sm=f; Past: Sm.n=f; Future: Sm.xr=f
// (Note: the verb Smt "to go" appears in texts; Sm is the most common short form.)
fn egy_Sm_present(slot: Int) -> String {
return "Sm" + egy_suffix_pronoun(slot)
}
fn egy_Sm_past(slot: Int) -> String {
return "Sm.n" + egy_suffix_pronoun(slot)
}
fn egy_Sm_future(slot: Int) -> String {
return "Sm.xr" + egy_suffix_pronoun(slot)
}
// Irregular verb: iri (to do / make)
//
// iri has a contracted 3-radical stem ir- before pronouns.
// Present: ir=f; Past: ir.n=f; Future: ir.xr=f
// Infinitive: iri
fn egy_iri_present(slot: Int) -> String {
return "ir" + egy_suffix_pronoun(slot)
}
fn egy_iri_past(slot: Int) -> String {
return "ir.n" + egy_suffix_pronoun(slot)
}
fn egy_iri_future(slot: Int) -> String {
return "ir.xr" + egy_suffix_pronoun(slot)
}
// Regular verb: sdm (to hear)
//
// sdm (to hear) is the paradigm verb used in grammar textbooks to illustrate
// all Egyptian verb forms. The sdm=f construction names the imperfective suffix
// verb pattern itself.
// Present: sdm=f; Past: sdm.n=f; Future: sdm.xr=f
// Infinitive: sdm
fn egy_sdm_present(slot: Int) -> String {
return "sdm" + egy_suffix_pronoun(slot)
}
fn egy_sdm_past(slot: Int) -> String {
return "sdm.n" + egy_suffix_pronoun(slot)
}
fn egy_sdm_future(slot: Int) -> String {
return "sdm.xr" + egy_suffix_pronoun(slot)
}
// Known-verb dispatcher
//
// Returns the inflected form for a known verb, or "" if unknown.
// Accepts both canonical English labels and Egyptian transliterations.
fn egy_known_verb(verb: String, tense: String, slot: Int) -> String {
// rdi / di to give
if str_eq(verb, "rdi") {
if str_eq(tense, "present") { return egy_rdi_present(slot) }
if str_eq(tense, "past") { return egy_rdi_past(slot) }
if str_eq(tense, "future") { return egy_rdi_future(slot) }
if str_eq(tense, "infinitive") { return "rdi" }
return egy_rdi_present(slot)
}
if str_eq(verb, "di") {
if str_eq(tense, "present") { return egy_rdi_present(slot) }
if str_eq(tense, "past") { return egy_rdi_past(slot) }
if str_eq(tense, "future") { return egy_rdi_future(slot) }
if str_eq(tense, "infinitive") { return "rdi" }
return egy_rdi_present(slot)
}
if str_eq(verb, "give") {
if str_eq(tense, "present") { return egy_rdi_present(slot) }
if str_eq(tense, "past") { return egy_rdi_past(slot) }
if str_eq(tense, "future") { return egy_rdi_future(slot) }
if str_eq(tense, "infinitive") { return "rdi" }
return egy_rdi_present(slot)
}
// mAA to see
if str_eq(verb, "mAA") {
if str_eq(tense, "present") { return egy_mAA_present(slot) }
if str_eq(tense, "past") { return egy_mAA_past(slot) }
if str_eq(tense, "future") { return egy_mAA_future(slot) }
if str_eq(tense, "infinitive") { return "mAA" }
return egy_mAA_present(slot)
}
if str_eq(verb, "see") {
if str_eq(tense, "present") { return egy_mAA_present(slot) }
if str_eq(tense, "past") { return egy_mAA_past(slot) }
if str_eq(tense, "future") { return egy_mAA_future(slot) }
if str_eq(tense, "infinitive") { return "mAA" }
return egy_mAA_present(slot)
}
// Dd to say
if str_eq(verb, "Dd") {
if str_eq(tense, "present") { return egy_Dd_present(slot) }
if str_eq(tense, "past") { return egy_Dd_past(slot) }
if str_eq(tense, "future") { return egy_Dd_future(slot) }
if str_eq(tense, "infinitive") { return "Dd" }
return egy_Dd_present(slot)
}
if str_eq(verb, "say") {
if str_eq(tense, "present") { return egy_Dd_present(slot) }
if str_eq(tense, "past") { return egy_Dd_past(slot) }
if str_eq(tense, "future") { return egy_Dd_future(slot) }
if str_eq(tense, "infinitive") { return "Dd" }
return egy_Dd_present(slot)
}
// Sm to go
if str_eq(verb, "Sm") {
if str_eq(tense, "present") { return egy_Sm_present(slot) }
if str_eq(tense, "past") { return egy_Sm_past(slot) }
if str_eq(tense, "future") { return egy_Sm_future(slot) }
if str_eq(tense, "infinitive") { return "Sm" }
return egy_Sm_present(slot)
}
if str_eq(verb, "go") {
if str_eq(tense, "present") { return egy_Sm_present(slot) }
if str_eq(tense, "past") { return egy_Sm_past(slot) }
if str_eq(tense, "future") { return egy_Sm_future(slot) }
if str_eq(tense, "infinitive") { return "Sm" }
return egy_Sm_present(slot)
}
// iri to do / make
if str_eq(verb, "iri") {
if str_eq(tense, "present") { return egy_iri_present(slot) }
if str_eq(tense, "past") { return egy_iri_past(slot) }
if str_eq(tense, "future") { return egy_iri_future(slot) }
if str_eq(tense, "infinitive") { return "iri" }
return egy_iri_present(slot)
}
if str_eq(verb, "do") {
if str_eq(tense, "present") { return egy_iri_present(slot) }
if str_eq(tense, "past") { return egy_iri_past(slot) }
if str_eq(tense, "future") { return egy_iri_future(slot) }
if str_eq(tense, "infinitive") { return "iri" }
return egy_iri_present(slot)
}
if str_eq(verb, "make") {
if str_eq(tense, "present") { return egy_iri_present(slot) }
if str_eq(tense, "past") { return egy_iri_past(slot) }
if str_eq(tense, "future") { return egy_iri_future(slot) }
if str_eq(tense, "infinitive") { return "iri" }
return egy_iri_present(slot)
}
// sdm to hear
if str_eq(verb, "sdm") {
if str_eq(tense, "present") { return egy_sdm_present(slot) }
if str_eq(tense, "past") { return egy_sdm_past(slot) }
if str_eq(tense, "future") { return egy_sdm_future(slot) }
if str_eq(tense, "infinitive") { return "sdm" }
return egy_sdm_present(slot)
}
if str_eq(verb, "hear") {
if str_eq(tense, "present") { return egy_sdm_present(slot) }
if str_eq(tense, "past") { return egy_sdm_past(slot) }
if str_eq(tense, "future") { return egy_sdm_future(slot) }
if str_eq(tense, "infinitive") { return "sdm" }
return egy_sdm_present(slot)
}
// Verb not in table
return ""
}
// Regular verb conjugation
//
// For verbs not in the explicit table, apply the productive suffix-verb pattern:
// Present (imperfective sdm=f): stem + pronoun suffix
// Past (perfective sdm.n=f): stem + ".n" + pronoun suffix
// Future (prospective sdm.xr=f): stem + ".xr" + pronoun suffix
// Infinitive: stem unchanged
//
// This covers the vast majority of strong (sound) verb roots.
fn egy_regular_present(stem: String, slot: Int) -> String {
return stem + egy_suffix_pronoun(slot)
}
fn egy_regular_past(stem: String, slot: Int) -> String {
return stem + ".n" + egy_suffix_pronoun(slot)
}
fn egy_regular_future(stem: String, slot: Int) -> String {
return stem + ".xr" + egy_suffix_pronoun(slot)
}
// egy_conjugate: main conjugation entry point
//
// verb: Egyptian verb (ASCII transliteration) or English canonical label
// tense: "present" | "past" | "future" | "infinitive"
// person: "first" | "second" | "third"
// number: "singular" | "dual" | "plural"
//
// Returns:
// - "" for present copula (zero copula caller omits the verb)
// - inflected form (stem + .n + suffix, etc.) for all other cases
// - verb + regular suffix for unknown verbs (productive fallback)
fn egy_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let slot: Int = egy_slot(person, number)
// Handle copula (wnn / "be")
if egy_is_copula(verb) {
return egy_conjugate_copula(tense, slot)
}
// Try the known-verb table
let known: String = egy_known_verb(verb, tense, slot)
if !str_eq(known, "") {
return known
}
// Infinitive: return unchanged
if str_eq(tense, "infinitive") { return verb }
// Regular verb: apply productive sdm=f / sdm.n=f pattern
if str_eq(tense, "present") { return egy_regular_present(verb, slot) }
if str_eq(tense, "past") { return egy_regular_past(verb, slot) }
if str_eq(tense, "future") { return egy_regular_future(verb, slot) }
// Unknown tense: return verb unchanged as safe fallback
return verb
}
// Noun number marking
//
// Middle Egyptian nouns are invariant for case syntactic role is expressed by
// word order and prepositions, not noun endings. Number is marked by suffix:
//
// Singular: base form (no suffix)
// Dual: masc + wy / fem + ty (wy and ty in ASCII transliteration)
// Plural: masc + w / fem + wt
//
// Many common nouns have suppletive or irregular plurals (recorded in the
// vocabulary layer). This function implements the productive regular pattern.
//
// gram_case: accepted for API symmetry but has no effect (Egyptian is caseless).
fn egy_decline(noun: String, gram_case: String, number: String) -> String {
if str_eq(number, "singular") { return noun }
if str_eq(number, "dual") {
// Feminine dual: if noun ends in t (feminine marker), replace with ty
if egy_str_ends(noun, "t") {
let stem: String = egy_drop(noun, 1)
return stem + "ty"
}
return noun + "wy"
}
// Plural
if egy_str_ends(noun, "t") {
// Feminine noun: add wt
return noun + "wt"
}
// Masculine noun: add w
return noun + "w"
}
// Feminine derivation
//
// egy_fem: derive the feminine form of a noun or adjective by appending -t.
//
// In Middle Egyptian, the feminine gender marker is the suffix -t (written with
// the bread-loaf hieroglyph, Gardiner X1). If the base already ends in -t the
// form is returned unchanged to avoid double-suffixing.
fn egy_fem(noun: String) -> String {
if egy_str_ends(noun, "t") { return noun }
return noun + "t"
}
// Noun phrase assembly
//
// egy_noun_phrase: return the surface form of a noun phrase.
//
// noun: base noun (ASCII transliteration)
// gram_case: passed for API symmetry; has no effect (Egyptian is caseless)
// number: "singular" | "dual" | "plural"
// definite: "true" | "false" Middle Egyptian has no article; parameter accepted
// for API symmetry. Late Egyptian pꜣ/tꜣ/nꜣ articles are not implemented
// here (they would require knowing the gender of each noun).
//
// Returns the noun in its correct number form.
fn egy_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
return egy_decline(noun, gram_case, number)
}
// Canonical verb mapping
//
// egy_map_canonical: map cross-lingual English canonical verb labels to their
// Middle Egyptian equivalents before dispatching to egy_conjugate.
fn egy_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "wnn" }
if str_eq(verb, "give") { return "rdi" }
if str_eq(verb, "see") { return "mAA" }
if str_eq(verb, "say") { return "Dd" }
if str_eq(verb, "go") { return "Sm" }
if str_eq(verb, "do") { return "iri" }
if str_eq(verb, "make") { return "iri" }
if str_eq(verb, "hear") { return "sdm" }
// Unknown: return as-is; egy_conjugate will apply the regular pattern
return verb
}