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
+639
View File
@@ -0,0 +1,639 @@
// morphology-sux.el Sumerian morphology for the NLG engine.
//
// Implements Sumerian noun declension, verb conjugation, and sentence
// construction. Designed as a companion to morphology.el and called by
// the engine when the language profile code is "sux".
//
// Language profile: code=sux, name=Sumerian, morph_type=agglutinative,
// word_order=SOV, question_strategy=particle, script=latin_transliteration,
// family=isolate (no known relatives).
//
// Typological overview:
// Sumerian is the world's oldest attested written language, first recorded
// in Uruk ca. 3500 BCE; active spoken use through ca. 2000 BCE; learned
// scribal language to ca. 100 CE. It is a language isolate unrelated to
// any other known language family.
//
// ERGATIVITY: Sumerian is ergative-absolutive. The subject of a transitive
// verb (the AGENT) takes the ergative case (-e). The subject of an
// intransitive verb and the direct object of a transitive verb share the
// same form: the ABSOLUTIVE (unmarked, no suffix). This is structurally
// the opposite of nominative-accusative languages (like Latin or Greek)
// where the subject of both transitive and intransitive verbs is marked the
// same way. Example: lugal-e é mu-un-dù = "the king built the house",
// where lugal-e = king-ERG (agent of transitive "build") and é = house-ABS
// (patient, unmarked).
//
// NOUN CLASSES: animate (humans, gods) and inanimate (everything else).
// Animate plurals use -ene; inanimate plurals use -a (collective).
//
// VERB CHAIN: Sumerian finite verbs are polysynthetic chains encoding:
// (modal)(negation)(dimensional prefixes)(stem)(voice/causative)(suffixes)
// The dimensional prefix chain includes pronominal agreement markers for
// agent and patient. This module implements a simplified but linguistically
// honest subset: perfective mu-/imperfective i- prefix + personal suffixes.
//
// KEY VERBS: Several high-frequency verbs have frozen citation forms.
// The copula is often omitted in equational sentences; when present it is
// usually the clitic -am or the verb me "to be".
//
// TRANSLITERATION: Standard Assyriological transliteration uses subscript
// numbers for sign disambiguation (dug, tum, am) written here with the
// corresponding Unicode characters (dug4, tum2, am3 using ASCII where the
// subscript forms are unavailable in string literals, or as etc. where
// needed). Special characters: š (sh), ŋ (velar nasal), ĝ (= ŋ alternate),
// (voiceless velar/uvular fricative).
//
// Cases implemented:
// absolutive (), ergative (-e), genitive (-ak), dative (-ra),
// locative (-a), ablative (-ta), comitative (-da), equative (-gin)
//
// Depends on: morphology.el (str_ends_with, str_len, str_slice, str_eq,
// native_list_empty, native_list_append, native_list_get, native_list_len)
// String helpers
fn sux_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn sux_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)
}
fn sux_str_last_char(s: String) -> String {
let n: Int = str_len(s)
if n == 0 {
return ""
}
return str_slice(s, n - 1, n)
}
fn sux_str_last2(s: String) -> String {
let n: Int = str_len(s)
if n < 2 {
return s
}
return str_slice(s, n - 2, n)
}
// Person/number slot
//
// Maps person × number to a 0-based index used in paradigm tables.
// 0 = 1st singular (ĝe / ĝa "I")
// 1 = 2nd singular (za-e "you")
// 2 = 3rd singular (animate: a-ne "he/she"; inanimate implied)
// 3 = 1st plural (me "we")
// 4 = 2nd plural (me-zen "you (pl)")
// 5 = 3rd plural (animate: a-ne-ne "they")
//
// Dual is not grammaticalised distinctly from plural in Sumerian verbal
// morphology; dual inputs fall through to plural.
fn sux_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 person
if str_eq(number, "singular") { return 2 }
return 5
}
// Ergative personal suffixes
//
// These encode the AGENT of a transitive verb when it appears as a standalone
// noun suffix (postposition on the nominal element in the ergative chain).
// In practice the -e ergative postposition on nouns is more common; these
// pronominal suffixes appear in the verbal chain. Provided here for the
// pronominal agent agreement slot.
//
// Ergative suffixes on nominal phrases (the postposition form, not verbal):
// 1sg: -(e)n 2sg: -(e)n 3sg animate: -e
// 1pl: -enden 2pl: -enzen 3pl animate: -eš
fn sux_ergative_suffix(person: String, number: String) -> String {
if str_eq(person, "first") {
if str_eq(number, "singular") { return "-en" }
return "-enden"
}
if str_eq(person, "second") {
if str_eq(number, "singular") { return "-en" }
return "-enzen"
}
// third
if str_eq(number, "singular") { return "-e" }
return "-eš"
}
// Absolutive personal suffixes
//
// The absolutive is the UNMARKED case in Sumerian ergative grammar. It is
// used for:
// (a) the single argument of an intransitive verb (= subject)
// (b) the direct object of a transitive verb (= patient)
//
// There is no overt suffix on the noun in the absolutive. However, the VERB
// agrees with the absolutive argument via a suffix in the verbal chain.
// These are the verbal agreement suffixes for the absolutive participant:
// 1sg: -en 2sg: -en 3sg: (null) 1pl: -enden 2pl: -enzen 3pl:
fn sux_absolutive_suffix(person: String, number: String) -> String {
if str_eq(person, "first") {
if str_eq(number, "singular") { return "-en" }
return "-enden"
}
if str_eq(person, "second") {
if str_eq(number, "singular") { return "-en" }
return "-enzen"
}
// third person: null agreement suffix for 3sg and 3pl
return ""
}
// Canonical verb mapping
//
// The semantic layer may pass English canonical labels. Map these to the
// Sumerian citation stem (the base form as used in transliteration).
fn sux_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "me" }
if str_eq(verb, "say") { return "dug4" }
if str_eq(verb, "go") { return "du" }
if str_eq(verb, "see") { return "igi-bar" }
if str_eq(verb, "do") { return "ak" }
if str_eq(verb, "make") { return "ak" }
if str_eq(verb, "bring") { return "tum2" }
if str_eq(verb, "build") { return "" }
if str_eq(verb, "give") { return "šum2" }
if str_eq(verb, "know") { return "zu" }
if str_eq(verb, "hear") { return "ĝeštug2 ĝar" }
if str_eq(verb, "love") { return "ki-aĝ2" }
if str_eq(verb, "sit") { return "tuš" }
if str_eq(verb, "stand") { return "gub" }
if str_eq(verb, "come") { return "ĝen" }
if str_eq(verb, "eat") { return "gu7" }
if str_eq(verb, "drink") { return "naĝ" }
if str_eq(verb, "write") { return "sar" }
return verb
}
// Verbal personal suffixes (simplified finite chain)
//
// In the simplified model the personal suffix encodes the absolutive argument
// (the subject of intransitive verbs, or the patient of transitive verbs).
// Full Sumerian verbal morphology is polysynthetic and beyond NLG scope; this
// models the most productive layer: stem + absolutive agreement.
//
// Suffix paradigm:
// slot 0 (1sg): -en slot 3 (1pl): -enden
// slot 1 (2sg): -en slot 4 (2pl): -enzen
// slot 2 (3sg): slot 5 (3pl): -eš
fn sux_personal_suffix(slot: Int) -> String {
if slot == 0 { return "en" }
if slot == 1 { return "en" }
if slot == 2 { return "" }
if slot == 3 { return "enden" }
if slot == 4 { return "enzen" }
return ""
}
// Special: me (to be)
//
// The Sumerian copula "me" (to be) has several uses:
// - As a full verb with personal endings in present: me-en (I am), me-en
// (you are), / -am3 (he/she/it is copula often omitted or realised as
// the clitic -am3 attached to the predicate), me-en-dè (we are)
// - As a predicative clitic: noun/adj + -am3
// - In past: ha-ma-an-me (he/she was rare in NLG contexts)
//
// For NLG purposes:
// present: use me + personal suffix; 3sg = omit entirely or "-am3" clitic
// past: ba-me + personal suffix (archaic; approximate)
fn sux_me_present(slot: Int) -> String {
if slot == 0 { return "me-en" }
if slot == 1 { return "me-en" }
if slot == 2 { return "" }
if slot == 3 { return "me-en-dè" }
if slot == 4 { return "me-en-zè-en" }
return "me-eš"
}
fn sux_me_past(slot: Int) -> String {
if slot == 0 { return "ba-me-en" }
if slot == 1 { return "ba-me-en" }
if slot == 2 { return "ba-me" }
if slot == 3 { return "ba-me-en-dè" }
if slot == 4 { return "ba-me-en-zè-en" }
return "ba-me-eš"
}
// Special: dug4/e (to say)
//
// "dug4" is the perfective stem; "e" is the imperfective/present stem.
// Common finite forms:
// present (imperfective): e + personal suffix; 3sg = e
// past (perfective): mu-un-dug4 (3sg agent; simplified)
fn sux_dug4_present(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "e" }
return "e-" + suf
}
fn sux_dug4_past(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "mu-un-dug4" }
return "mu-un-dug4-" + suf
}
// Special: du (to go)
//
// Intransitive motion verb. The agent is in the absolutive.
// present (imperfective): i-du + personal suffix
// past (perfective): mu-un-du + personal suffix (mu- on intransitive
// when the agent is 3rd person; convention varies)
fn sux_du_present(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "i-du" }
return "i-du-" + suf
}
fn sux_du_past(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "mu-un-du" }
return "mu-un-du-" + suf
}
// Special: igi bar (to see "to open the eye")
//
// A compound verb: igi (eye) + bar (to open/spread). The noun igi is the
// incorporated object; bar is the finite verb element.
// present: igi i-bar + personal suffix
// past: igi mu-un-bar + personal suffix
fn sux_igibar_present(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "igi i-bar" }
return "igi i-bar-" + suf
}
fn sux_igibar_past(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "igi mu-un-bar" }
return "igi mu-un-bar-" + suf
}
// Special: ak (to do / make)
//
// High-frequency transitive verb; forms the basis of many compound verbs.
// present: i-ak + personal suffix
// past: mu-un-ak + personal suffix
fn sux_ak_present(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "i-ak" }
return "i-ak-" + suf
}
fn sux_ak_past(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "mu-un-ak" }
return "mu-un-ak-" + suf
}
// Special: tum2/de6 (to bring)
//
// Motion verb with incorporated directionality.
// present: i-tum2 + personal suffix
// past: mu-un-tum2 + personal suffix
fn sux_tum2_present(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "i-tum2" }
return "i-tum2-" + suf
}
fn sux_tum2_past(slot: Int) -> String {
let suf: String = sux_personal_suffix(slot)
if str_eq(suf, "") { return "mu-un-tum2" }
return "mu-un-tum2-" + suf
}
// sux_conjugate: main conjugation entry point
//
// verb: Sumerian verb stem or English canonical label
// tense: "present" | "past"
// (Sumerian distinguishes imperfective/perfective aspect rather than
// tense proper; "present" maps to imperfective i-, "past" to
// perfective mu-)
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns the inflected finite verb form in standard transliteration.
// Falls back to mu- + stem for unknown verbs (productive perfective prefix).
//
// Note on ergativity in the verb chain: the prefix mu- encodes that the
// agent (ergative subject) is 3rd person in the prototypical case. Full
// pronominal infixing in the dimensional prefix chain is not modelled here;
// the returned forms represent the 3rd-person-agent baseline with personal
// suffixes for the absolutive participant.
fn sux_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let v: String = sux_map_canonical(verb)
let slot: Int = sux_slot(person, number)
// "me" copula / to be
if str_eq(v, "me") {
if str_eq(tense, "present") { return sux_me_present(slot) }
if str_eq(tense, "past") { return sux_me_past(slot) }
return sux_me_present(slot)
}
// "dug4" to say
if str_eq(v, "dug4") {
if str_eq(tense, "present") { return sux_dug4_present(slot) }
if str_eq(tense, "past") { return sux_dug4_past(slot) }
return sux_dug4_past(slot)
}
// "du" to go
if str_eq(v, "du") {
if str_eq(tense, "present") { return sux_du_present(slot) }
if str_eq(tense, "past") { return sux_du_past(slot) }
return sux_du_past(slot)
}
// "igi-bar" to see
if str_eq(v, "igi-bar") {
if str_eq(tense, "present") { return sux_igibar_present(slot) }
if str_eq(tense, "past") { return sux_igibar_past(slot) }
return sux_igibar_past(slot)
}
// "ak" to do / make
if str_eq(v, "ak") {
if str_eq(tense, "present") { return sux_ak_present(slot) }
if str_eq(tense, "past") { return sux_ak_past(slot) }
return sux_ak_past(slot)
}
// "tum2" to bring
if str_eq(v, "tum2") {
if str_eq(tense, "present") { return sux_tum2_present(slot) }
if str_eq(tense, "past") { return sux_tum2_past(slot) }
return sux_tum2_past(slot)
}
// Regular fallback: prefix + stem + personal suffix
//
// For verbs not listed above, apply the productive rule:
// imperfective (present): i- + stem + suffix
// perfective (past): mu- + stem + suffix
//
// The "un" infix after mu- is a 3rd-person animate patient marker that
// appears with transitive verbs; we omit it in the generic fallback as we
// cannot know transitivity without a lexicon.
let suf: String = sux_personal_suffix(slot)
if str_eq(tense, "present") {
if str_eq(suf, "") { return "i-" + v }
return "i-" + v + "-" + suf
}
// past / perfective
if str_eq(suf, "") { return "mu-" + v }
return "mu-" + v + "-" + suf
}
// Animacy detection
//
// Sumerian grammatical animacy (ANIMATE vs INANIMATE) is a binary noun class
// distinction affecting plural formation and pronominal reference. Animate
// nouns denote humans and deities; everything else is inanimate.
//
// Heuristic: check for known divine/human markers in the noun:
// - dingir/diĝir prefix (divine determinative, written with sign AN: 𒀭)
// - Ends in -ra, -en (lordly epithets)
// - Known human occupational terms as exact matches
// - Otherwise: inanimate
//
// This is necessarily approximate without a full lexicon. The function errs
// toward inanimate (safer default for open-class nouns).
fn sux_is_animate(noun: String) -> Bool {
// Divine determinative in transliteration
if sux_str_ends(noun, "diĝir") { return true }
if sux_str_ends(noun, "dingir") { return true }
// Common human roles
if str_eq(noun, "lugal") { return true } // king
if str_eq(noun, "nin") { return true } // lady/queen
if str_eq(noun, "en") { return true } // lord
if str_eq(noun, "ensi2") { return true } // ruler/governor
if str_eq(noun, "dumu") { return true } // son/child
if str_eq(noun, "dam") { return true } // spouse/wife
if str_eq(noun, "ama") { return true } // mother
if str_eq(noun, "ad") { return true } // father
if str_eq(noun, "a2-dam") { return true } // wife (alternate)
if str_eq(noun, "lu2") { return true } // man/person
if str_eq(noun, "munus") { return true } // woman
if str_eq(noun, "ur") { return true } // man (archaic)
if str_eq(noun, "saĝ") { return true } // head person (as noun)
if str_eq(noun, "gudu4") { return true } // priest
if str_eq(noun, "sanga") { return true } // temple administrator
if str_eq(noun, "ugula") { return true } // overseer
if str_eq(noun, "dub-sar") { return true } // scribe
if str_eq(noun, "nar") { return true } // singer/musician
if str_eq(noun, "sukkal") { return true } // minister/vizier
// Check for divine name prefix d (determinative before proper names)
if sux_str_ends(noun, "d-") { return true }
// Otherwise: inanimate
return false
}
// Case suffixes
//
// Sumerian is ergative-absolutive. The cases below are postpositional clitics
// attached to the last element of the noun phrase.
//
// absolutive: (unmarked subject of intransitive, object of transitive)
// ergative: -e (subject of transitive verb the AGENT)
// genitive: -ak (possession, association; often written -a(k) before consonant)
// dative: -ra (indirect object, beneficiary, "for/to")
// locative: -a (location "in/at/on"; -e before certain consonants)
// ablative: -ta (source, separation "from")
// comitative: -da (accompaniment "with")
// equative: -gin (comparison "like/as")
//
// Note: in connected speech the genitive -ak loses its final -k before a
// consonant; this simplification uses the full form throughout.
fn sux_case_suffix(gram_case: String) -> String {
if str_eq(gram_case, "absolutive") { return "" }
if str_eq(gram_case, "ergative") { return "-e" }
if str_eq(gram_case, "genitive") { return "-ak" }
if str_eq(gram_case, "dative") { return "-ra" }
if str_eq(gram_case, "locative") { return "-a" }
if str_eq(gram_case, "ablative") { return "-ta" }
if str_eq(gram_case, "comitative") { return "-da" }
if str_eq(gram_case, "equative") { return "-gin" }
// Terminative (-še3: "to/toward") also attested
if str_eq(gram_case, "terminative") { return "-še" }
return ""
}
// sux_decline: noun declension
//
// noun: base form of the noun (absolutive singular = citation form)
// gram_case: one of the cases listed above
// number: "singular" | "plural"
//
// Plural formation:
// Animate nouns: base + -ene (e.g. lugal-ene "kings")
// Inanimate nouns: base + -a (collective/inanimate plural; less common,
// many inanimate nouns are not pluralised at all in Sumerian)
//
// The case suffix attaches AFTER the plural marker.
//
// Absolutive singular = the bare stem (no suffix added).
fn sux_decline(noun: String, gram_case: String, number: String) -> String {
let csuf: String = sux_case_suffix(gram_case)
if str_eq(number, "singular") {
// Absolutive singular: bare stem with no suffix at all
if str_eq(gram_case, "absolutive") { return noun }
// Strip the leading dash from the suffix for direct concatenation
let suf_len: Int = str_len(csuf)
let bare_suf: String = str_slice(csuf, 1, suf_len)
return noun + bare_suf
}
// Plural
let animate: Bool = sux_is_animate(noun)
let plural_stem: String = ""
if animate {
let plural_stem = noun + "ene"
}
if !animate {
let plural_stem = noun + "a"
}
if str_eq(gram_case, "absolutive") { return plural_stem }
let suf_len2: Int = str_len(csuf)
let bare_suf2: String = str_slice(csuf, 1, suf_len2)
return plural_stem + bare_suf2
}
// sux_noun_phrase: noun phrase builder
//
// Sumerian has NO articles (definite or indefinite). Determinateness is
// expressed through context, position, or the genitive construction.
// The "definite" parameter is accepted for interface compatibility but is
// ignored the returned form is always the declined noun alone.
//
// noun: base (absolutive) form
// gram_case: grammatical case string
// number: "singular" | "plural"
// definite: ignored (Sumerian has no articles)
fn sux_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
return sux_decline(noun, gram_case, number)
}
// sux_verb_chain: build the finite verbal complex
//
// Constructs the full Sumerian SOV clause for a transitive or intransitive
// predication. Sumerian word order is strict SOV.
//
// For TRANSITIVE sentences (ergative construction):
// agent (in ergative case: noun + -e) + patient (absolutive) + verb
// Example: lugal-e é mu-un-dù
// king-ERG house-ABS built
// "The king built the house."
//
// For INTRANSITIVE sentences:
// agent (in absolutive case: bare noun) + verb
// Example: lugal du
// king-ABS went
// "The king went."
//
// agent: the subject noun (base/absolutive form); ergative suffix applied here
// verb: Sumerian verb stem or canonical English label
// patient: patient noun (absolutive; pass "" for intransitive)
// tense: "present" | "past"
//
// Note: this function handles third-person singular agreement throughout, which
// is the most common narrative form in Sumerian texts. The conjugated verb
// form uses sux_conjugate with "third"/"singular" for the absolutive agreement.
fn sux_verb_chain(agent: String, verb: String, patient: String, tense: String) -> String {
let conjugated: String = sux_conjugate(verb, tense, "third", "singular")
// Intransitive: agent in absolutive + verb
if str_eq(patient, "") {
return agent + " " + conjugated
}
// Transitive: agent in ergative + patient in absolutive + verb
// Apply ergative suffix directly (no leading dash needed it is -e)
let agent_erg: String = agent + "e"
return agent_erg + " " + patient + " " + conjugated
}
// sux_realize_sentence: top-level sentence realizer
//
// Assembles a complete Sumerian sentence from semantic components.
//
// intent: "assert" | "question" | "describe"
// agent: subject noun (base form)
// predicate: verb stem / canonical label (for assert/question) OR
// adjective/noun predicate (for describe / nominal sentence)
// patient: object noun (base form); pass "" for intransitive or nominal sentence
// tense: "present" | "past"
//
// assert (declarative transitive/intransitive)
// Builds the SOV verb chain using sux_verb_chain.
// Ergative marking is applied to the agent when a patient is present.
// Example: lugal-e é mu-un-dù "The king built the house."
//
// question
// Sumerian polar questions are formed by appending the enclitic -a to the
// verb (written -am in some analyses; here "-a" on the verb complex).
// Word order is unchanged (SOV retained).
// Example: lugal-e é mu-un-dù-a? "Did the king build the house?"
//
// describe (nominal/equational sentence)
// Sumerian equational sentences (X is Y) typically omit the copula entirely
// or append -am to the predicate nominal/adjective.
// Form: agent + predicate + "-am3"
// Example: lugal-am3 "He is a king." / lugal kalag-ga-am3 "The king is mighty."
//
// When a patient is provided in describe mode, it is treated as the predicate
// complement: agent + patient-am3.
fn sux_realize_sentence(intent: String, agent: String, predicate: String, patient: String, tense: String) -> String {
if str_eq(intent, "assert") {
return sux_verb_chain(agent, predicate, patient, tense)
}
if str_eq(intent, "question") {
// Build the assertion first, then append the question enclitic -a
let assertion: String = sux_verb_chain(agent, predicate, patient, tense)
return assertion + "-a"
}
if str_eq(intent, "describe") {
// Nominal/equational sentence: agent predicate/patient + copula clitic -am3
// When patient is given it serves as the predicate nominal
if str_eq(patient, "") {
return agent + " " + predicate + "-am3"
}
return agent + " " + patient + "-am3"
}
// Fallback: plain assertion
return sux_verb_chain(agent, predicate, patient, tense)
}