Files
el/elp/src/morphology-sux.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

641 lines
28 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-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
import "morphology.el"
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)
}