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

529 lines
20 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-akk.el - Akkadian morphology for the NLG engine.
// 𒀭𒂗𒍪 Akkadian (akkadû), the language of Babylon and Assyria.
//
// Implements Old Babylonian Akkadian verb conjugation (G-stem / Grundstamm),
// noun declension with mimation, and noun-phrase construction.
//
// Akkadian is the oldest attested Semitic language (ca. 2800100 BCE).
// It uses cuneiform script; we work in standard Latin transliteration
// (Old Babylonian dialect the classical prestige form).
//
// Language profile:
// code=akk, name=Akkadian, morph_type=semitic, word_order=VSO/SOV,
// script=cuneiform (transliterated), family=semitic/east-semitic
//
// Key grammatical facts:
// - Semitic trilateral root system: words built from 3-consonant roots
// by inserting vowel patterns (e.g. root p-r-s iparras "he decides")
// - Grammatical gender: masculine / feminine (no neuter)
// - Cases: nominative (-um), accusative (-am), genitive (-im) "mimation"
// - Number: singular / plural (dual is vestigial in verbs)
// - Verb stems: G (basic), D (intensive), Š (causative), N (passive);
// this file implements G-stem throughout
// - Two main tense/aspect systems:
// Present-future (iparras pattern): action in progress or future
// Perfect (iptaras pattern): completed action with present relevance
// Stative (paris pattern): resultant state, often adjectival
// - No definite or indefinite article; case endings convey
// determination contextually
// - Copula: bašû (to exist/be)
//
// Verb conjugation conventions:
// person: "first" | "second" | "third"
// gender: "m" | "f"
// number: "singular" | "plural"
// tense: "present" | "perfect" | "stative"
//
// Noun declension conventions:
// gram_case: "nom" | "acc" | "gen"
// number: "singular" | "plural"
// gender: "m" | "f" (passed to akk_decline for gender-specific forms)
//
// Verbs covered (G-stem infinitive, transliterated):
// "bašû" to exist / be (copula)
// "alāku" to go
// "amāru" to see
// "qabû" to say
// "epēšu" to do / make
//
// Nouns covered with known mimation forms:
// "šarrum" king
// "awīlum" man / person
// "bītum" house
// "ilum" god
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with)
// String helpers
import "morphology.el"
fn akk_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn akk_str_len(s: String) -> Int {
return str_len(s)
}
fn akk_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.
// Akkadian verb agreement does not distinguish gender in 1st person,
// and the 2nd person often conflates masc/fem in some paradigms.
// We use a 6-cell paradigm matching the most common OB presentation:
//
// 0 = 1sg (I)
// 1 = 2sg (you sg)
// 2 = 3sg m (he)
// 3 = 3sg f (she)
// 4 = 1pl (we)
// 5 = 3pl (they)
//
// Note: 2pl is rare / vestigial in attested OB texts; omitted here.
fn akk_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 // default: 3sg masc; caller may override with gender check below
}
// akk_slot_g: gender-aware slot for third person singular.
// Returns 3 (3sg fem) when person=third, number=singular, gender=f.
fn akk_slot_g(person: String, gender: String, number: String) -> Int {
let base: Int = akk_slot(person, number)
if str_eq(person, "third") {
if str_eq(number, "singular") {
if str_eq(gender, "f") { return 3 }
}
}
return base
}
// Copula: bašû to exist / be
//
// bašû is suppletive and highly irregular.
// Present: ibašši (3sg m/f), abašši (1sg), tabašši (2sg)
// Stative: bašī (3sg m), bašiat (3sg f), bašāku (1sg)
// Perfect: not commonly attested in G-stem; use present forms as fallback.
fn akk_copula_present(slot: Int) -> String {
if slot == 0 { return "abašši" } // 1sg
if slot == 1 { return "tabašši" } // 2sg
if slot == 2 { return "ibašši" } // 3sg m
if slot == 3 { return "ibašši" } // 3sg f (same form in attested OB)
if slot == 4 { return "nibašši" } // 1pl
return "ibaššū" // 3pl
}
fn akk_copula_stative(slot: Int) -> String {
if slot == 0 { return "bašāku" } // 1sg (stative 1sg: -āku suffix)
if slot == 1 { return "bašāta" } // 2sg (-āta suffix)
if slot == 2 { return "bašī" } // 3sg m (unmarked base)
if slot == 3 { return "bašiat" } // 3sg f (-at suffix)
if slot == 4 { return "bašānu" } // 1pl (-ānu suffix)
return "bašū" // 3pl ( suffix)
}
fn akk_is_copula(verb: String) -> Bool {
if str_eq(verb, "bašû") { return true }
if str_eq(verb, "bashu") { return true }
if str_eq(verb, "be") { return true }
return false
}
fn akk_conjugate_copula(tense: String, slot: Int) -> String {
if str_eq(tense, "stative") { return akk_copula_stative(slot) }
// present and perfect both fall back to present forms for bašû
return akk_copula_present(slot)
}
// alāku to go
//
// Irregular: present stem is illak- (not the expected alakk-).
// Present: illak (3sg), allak (1sg), tallak (2sg), nillak (1pl), illaku (3pl)
// Perfect: ittalk- forms (less common, use illak- + perf marker)
// Stative: use present as proxy
fn akk_alaku_present(slot: Int) -> String {
if slot == 0 { return "allak" } // 1sg
if slot == 1 { return "tallak" } // 2sg
if slot == 2 { return "illak" } // 3sg m
if slot == 3 { return "tallak" } // 3sg f (same as 2sg OB pattern)
if slot == 4 { return "nillak" } // 1pl
return "illaku" // 3pl
}
fn akk_alaku_perfect(slot: Int) -> String {
if slot == 0 { return "ittalak" } // 1sg
if slot == 1 { return "tattalak" } // 2sg
if slot == 2 { return "ittalak" } // 3sg m
if slot == 3 { return "tattalak" } // 3sg f
if slot == 4 { return "nittalak" } // 1pl
return "ittalku" // 3pl
}
// amāru to see
//
// Present (immar-): immar (3sg), ammar (1sg), tammar (2sg)
// Perfect (imtamar-): imtamar (3sg), amtamar (1sg), tamtamar (2sg)
fn akk_amaru_present(slot: Int) -> String {
if slot == 0 { return "ammar" } // 1sg
if slot == 1 { return "tammar" } // 2sg
if slot == 2 { return "immar" } // 3sg m
if slot == 3 { return "tammar" } // 3sg f
if slot == 4 { return "nimmar" } // 1pl
return "immaru" // 3pl
}
fn akk_amaru_perfect(slot: Int) -> String {
if slot == 0 { return "amtamar" } // 1sg
if slot == 1 { return "tamtamar" } // 2sg
if slot == 2 { return "imtamar" } // 3sg m
if slot == 3 { return "tamtamar" } // 3sg f
if slot == 4 { return "nimtamar" } // 1pl
return "imtamaru" // 3pl
}
fn akk_amaru_stative(slot: Int) -> String {
// amāru stative: 3sg "amir" (the one who saw / he has seen)
if slot == 0 { return "amrāku" }
if slot == 1 { return "amrāta" }
if slot == 2 { return "amir" }
if slot == 3 { return "amrat" }
if slot == 4 { return "amrānu" }
return "amrū"
}
// qabû to say / speak
//
// Present: iqabbi (3sg), aqabbi (1sg), taqabbi (2sg)
// Perfect: iqtabi (3sg), aqtabi (1sg), taqtabi (2sg)
fn akk_qabu_present(slot: Int) -> String {
if slot == 0 { return "aqabbi" } // 1sg
if slot == 1 { return "taqabbi" } // 2sg
if slot == 2 { return "iqabbi" } // 3sg m
if slot == 3 { return "taqabbi" } // 3sg f
if slot == 4 { return "niqabbi" } // 1pl
return "iqabbû" // 3pl
}
fn akk_qabu_perfect(slot: Int) -> String {
if slot == 0 { return "aqtabi" } // 1sg
if slot == 1 { return "taqtabi" } // 2sg
if slot == 2 { return "iqtabi" } // 3sg m
if slot == 3 { return "taqtabi" } // 3sg f
if slot == 4 { return "niqtabi" } // 1pl
return "iqtabû" // 3pl
}
fn akk_qabu_stative(slot: Int) -> String {
if slot == 0 { return "qabāku" }
if slot == 1 { return "qabāta" }
if slot == 2 { return "qabi" }
if slot == 3 { return "qabiat" }
if slot == 4 { return "qabānu" }
return "qabû"
}
// epēšu to do / make
//
// Present (ieppuš / eppuš): ieppuš (3sg), eppuš (1sg), teppuš (2sg)
// Perfect: iptešu forms
fn akk_epesu_present(slot: Int) -> String {
if slot == 0 { return "eppuš" } // 1sg
if slot == 1 { return "teppuš" } // 2sg
if slot == 2 { return "ieppuš" } // 3sg m
if slot == 3 { return "teppuš" } // 3sg f
if slot == 4 { return "neppuš" } // 1pl
return "ieppušu" // 3pl
}
fn akk_epesu_perfect(slot: Int) -> String {
if slot == 0 { return "iptešu" } // 1sg (irregular: root ʿ-p-š)
if slot == 1 { return "taptešu" } // 2sg
if slot == 2 { return "iptešu" } // 3sg m
if slot == 3 { return "taptešu" } // 3sg f
if slot == 4 { return "niptešu" } // 1pl
return "iptešū" // 3pl
}
fn akk_epesu_stative(slot: Int) -> String {
if slot == 0 { return "epšāku" }
if slot == 1 { return "epšāta" }
if slot == 2 { return "epuš" }
if slot == 3 { return "epšat" }
if slot == 4 { return "epšānu" }
return "epšū"
}
// Regular G-stem paradigms (iparras model)
//
// For regular verbs not in the irregular table, we apply the standard
// OB G-stem paradigm using a caller-supplied present stem and perfect stem.
// The stems must be pre-computed by the caller (or vocabulary layer).
//
// iparras (present) endings by slot:
// 1sg: a- prefix
// 2sg: ta- prefix
// 3sg m: i- prefix
// 3sg f: ta- prefix (same prefix as 2sg)
// 1pl: ni- prefix
// 3pl: i- prefix + suffix
//
// For the generic fallback we use "iparras" as the model template.
fn akk_regular_present(stem: String, slot: Int) -> String {
// stem is the 3sg m form (i-prefix already present in conventional citation)
// We rebuild from the bare root portion by stripping/adding prefixes.
// Simplification: return prefixed forms using the provided present-3sg string.
if slot == 0 { return "a" + stem } // 1sg: a + stem (strip i-, add a-)
if slot == 1 { return "ta" + stem } // 2sg
if slot == 2 { return "i" + stem } // 3sg m
if slot == 3 { return "ta" + stem } // 3sg f
if slot == 4 { return "ni" + stem } // 1pl
return "i" + stem + "u" // 3pl: i + stem +
}
fn akk_regular_perfect(stem: String, slot: Int) -> String {
// Perfect (iptaras) uses infix -ta- after first root consonant.
// stem here is the 3sg perfect form; we apply person endings.
if slot == 0 { return "a" + stem } // 1sg
if slot == 1 { return "ta" + stem } // 2sg
if slot == 2 { return "i" + stem } // 3sg m
if slot == 3 { return "ta" + stem } // 3sg f
if slot == 4 { return "ni" + stem } // 1pl
return "i" + stem + "u" // 3pl
}
fn akk_regular_stative(stem: String, slot: Int) -> String {
// Stative (paris): 3sg m has zero ending; others take person suffixes.
if slot == 0 { return stem + "āku" } // 1sg
if slot == 1 { return stem + "āta" } // 2sg
if slot == 2 { return stem } // 3sg m: bare stem
if slot == 3 { return stem + "at" } // 3sg f
if slot == 4 { return stem + "ānu" } // 1pl
return stem + "ū" // 3pl
}
// Known-verb dispatcher
fn akk_known_verb(verb: String, tense: String, slot: Int) -> String {
// bašû to be / exist
if str_eq(verb, "bašû") {
return akk_conjugate_copula(tense, slot)
}
if str_eq(verb, "bashu") {
return akk_conjugate_copula(tense, slot)
}
// alāku to go
if str_eq(verb, "alāku") {
if str_eq(tense, "perfect") { return akk_alaku_perfect(slot) }
if str_eq(tense, "stative") { return akk_alaku_present(slot) }
return akk_alaku_present(slot)
}
if str_eq(verb, "alaku") {
if str_eq(tense, "perfect") { return akk_alaku_perfect(slot) }
return akk_alaku_present(slot)
}
// amāru to see
if str_eq(verb, "amāru") {
if str_eq(tense, "perfect") { return akk_amaru_perfect(slot) }
if str_eq(tense, "stative") { return akk_amaru_stative(slot) }
return akk_amaru_present(slot)
}
if str_eq(verb, "amaru") {
if str_eq(tense, "perfect") { return akk_amaru_perfect(slot) }
if str_eq(tense, "stative") { return akk_amaru_stative(slot) }
return akk_amaru_present(slot)
}
// qabû to say
if str_eq(verb, "qabû") {
if str_eq(tense, "perfect") { return akk_qabu_perfect(slot) }
if str_eq(tense, "stative") { return akk_qabu_stative(slot) }
return akk_qabu_present(slot)
}
if str_eq(verb, "qabu") {
if str_eq(tense, "perfect") { return akk_qabu_perfect(slot) }
if str_eq(tense, "stative") { return akk_qabu_stative(slot) }
return akk_qabu_present(slot)
}
// epēšu to do / make
if str_eq(verb, "epēšu") {
if str_eq(tense, "perfect") { return akk_epesu_perfect(slot) }
if str_eq(tense, "stative") { return akk_epesu_stative(slot) }
return akk_epesu_present(slot)
}
if str_eq(verb, "epesu") {
if str_eq(tense, "perfect") { return akk_epesu_perfect(slot) }
if str_eq(tense, "stative") { return akk_epesu_stative(slot) }
return akk_epesu_present(slot)
}
return ""
}
// Main conjugation entry point
//
// akk_conjugate: conjugate an Akkadian verb (G-stem).
//
// verb: G-stem infinitive (transliterated, e.g. "alāku", "amāru")
// tense: "present" | "perfect" | "stative"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns:
// - Inflected form for known verbs
// - verb unchanged as safe fallback for unknown verbs
fn akk_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let slot: Int = akk_slot(person, number)
// Copula shortcut
if akk_is_copula(verb) {
return akk_conjugate_copula(tense, slot)
}
// Known-verb table
let known: String = akk_known_verb(verb, tense, slot)
if !str_eq(known, "") {
return known
}
// Unknown verb: safe fallback
return verb
}
// Noun declension
//
// akk_decline: decline an Akkadian noun for gram_case and number.
//
// Mimation: OB nouns bear final -m in all case endings (mimation).
// The base noun (dictionary form) is the nominative singular with mimation.
// We strip the nominative -um ending (if present) to obtain the bare stem,
// then apply the requested ending.
//
// Masculine case endings (singular):
// Nominative: -um
// Accusative: -am
// Genitive: -im
//
// Masculine case endings (plural):
// Nominative: -ūtum (or in construct)
// Accusative/Genitive: -ātim (or in construct)
//
// Feminine nouns (identified by -tum nom sg ending):
// Sg nominative: -tum, accusative: -tam, genitive: -tim
// Pl nominative: -ātum, genitive/accusative: -ātim
//
// Known irregular stems (the vocabulary layer should pass dictionary forms):
// šarrum stem: šarr-
// awīlum stem: awīl-
// bītum stem: bīt-
// ilum stem: il-
fn akk_strip_nom(noun: String) -> String {
// Strip -um (masc nom sg mimation ending) to get bare stem
if akk_str_ends(noun, "um") {
return akk_str_drop_last(noun, 2)
}
// Strip -tum (fem nom sg)
if akk_str_ends(noun, "tum") {
return akk_str_drop_last(noun, 3)
}
// Already a bare stem or unusual form: return as-is
return noun
}
fn akk_is_fem(noun: String) -> Bool {
// Feminine nouns in OB typically end in -tum (nom sg)
if akk_str_ends(noun, "tum") { return true }
if akk_str_ends(noun, "tam") { return true }
if akk_str_ends(noun, "tim") { return true }
return false
}
fn akk_decline(noun: String, gram_case: String, number: String) -> String {
let fem: Bool = akk_is_fem(noun)
let stem: String = akk_strip_nom(noun)
if str_eq(number, "singular") {
if fem {
if str_eq(gram_case, "nom") { return stem + "tum" }
if str_eq(gram_case, "acc") { return stem + "tam" }
if str_eq(gram_case, "gen") { return stem + "tim" }
return stem + "tum"
}
// Masculine
if str_eq(gram_case, "nom") { return stem + "um" }
if str_eq(gram_case, "acc") { return stem + "am" }
if str_eq(gram_case, "gen") { return stem + "im" }
return stem + "um"
}
// Plural
if fem {
if str_eq(gram_case, "nom") { return stem + "ātum" }
// acc and gen merge in the oblique plural
return stem + "ātim"
}
// Masculine plural
if str_eq(gram_case, "nom") { return stem + "ūtum" }
return stem + "ātim"
}
// Noun phrase
//
// akk_noun_phrase: produce the surface noun phrase.
//
// Akkadian has no definite or indefinite article. Determination is conveyed
// by context, word order, and the genitive construct chain (status constructus).
// The definite parameter is accepted but has no surface effect: the declined
// noun is returned in either case.
//
// noun: dictionary form (nominative singular with mimation, e.g. "šarrum")
// gram_case: "nom" | "acc" | "gen"
// number: "singular" | "plural"
// definite: "true" | "false" (no surface effect in Akkadian)
fn akk_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
return akk_decline(noun, gram_case, number)
}
// Canonical verb mapping
//
// akk_map_canonical: map cross-lingual English canonical verb labels to
// their Akkadian G-stem infinitive equivalents.
fn akk_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "bašû" }
if str_eq(verb, "go") { return "alāku" }
if str_eq(verb, "see") { return "amāru" }
if str_eq(verb, "say") { return "qabû" }
if str_eq(verb, "speak") { return "qabû" }
if str_eq(verb, "do") { return "epēšu" }
if str_eq(verb, "make") { return "epēšu" }
return verb
}