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

680 lines
26 KiB
EmacsLisp
Raw Permalink 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-got.el - Gothic morphology for the NLG engine.
//
// Implements Gothic verb conjugation and noun declension using standard
// Gothic Latin romanisation. Designed as a companion to morphology.el and
// called by the engine when the language profile code is "got".
//
// Language profile: code=got, name=Gothic, morph_type=fusional,
// word_order=SOV, question_strategy=intonation, script=gothic-latin,
// family=germanic.
//
// Historical note: Gothic is attested primarily in the 4th-century Bible
// translation by Bishop Wulfila (Ulfilas). It is the earliest substantially
// attested Germanic language and preserves many archaic features lost in later
// branches (e.g. distinct dual, mediopassive voice, four-case system).
//
// Romanisation conventions used in this file:
// þ thorn (voiced/voiceless dental fricative; like "th" in "the/thin")
// ƕ hwair (Gothic hw-, like "wh" in older English "what")
// q Gothic q (labiovelar stop, like "qu")
// ei long /iː/ (Gothic digraph)
// ai short /ɛ/ (Gothic digraph, not a diphthong)
// au short /ɔ/ (Gothic digraph, not a diphthong)
// Standard vowels: a e i u (short)
//
// Verb conjugation covered:
// Tenses: present indicative active, past indicative active
// Persons: first/second/third × singular/plural
// Classes: weak class 1 (-jan verbs), weak class 2 (-on verbs) as
// the regular productive paths
// Irregulars: wisan (be), haban (have), gaggan (go), saihwan (see),
// qiþan (say), niman (take)
// Canonical map: "be" -> "wisan"
//
// Noun declension covered:
// Cases: nominative, accusative, genitive, dative (all 4 Gothic cases)
// Stem types: a-stem masc (paradigm: dags day),
// o-stem fem (paradigm: gibo gift),
// n-stem masc (paradigm: guma man)
// Numbers: singular, plural
//
// Demonstrative / article:
// Gothic has no definite article proper. The demonstrative pronoun
// sa (masc) / so (fem) / þata (neut) functions as a near-definite
// determiner. got_noun_phrase prepends "sa" (masc) or "þo" (fem) when
// definite=true. Gender must be inferred from stem class: a-stem masc,
// o-stem fem, n-stem masc.
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with)
// String helpers
fn got_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn got_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)
}
// Person/number slot
//
// Gothic has singular, dual, and plural. The dual is historically attested
// for first and second person only (e.g. 1du wit, 2du jut) but is quite rare
// even in the Gothic corpus. For NLG simplicity the dual is treated as plural.
//
// 0 = 1st singular
// 1 = 2nd singular
// 2 = 3rd singular
// 3 = 1st plural
// 4 = 2nd plural
// 5 = 3rd plural
fn got_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
}
// Canonical verb mapping
//
// English semantic-layer labels are mapped to Gothic dictionary forms before
// conjugation. Dictionary forms are the infinitive.
fn got_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "wisan" }
if str_eq(verb, "have") { return "haban" }
if str_eq(verb, "go") { return "gaggan" }
if str_eq(verb, "see") { return "saihwan" }
if str_eq(verb, "say") { return "qiþan" }
if str_eq(verb, "take") { return "niman" }
if str_eq(verb, "come") { return "qiman" }
if str_eq(verb, "give") { return "giban" }
if str_eq(verb, "know") { return "kunnan" }
if str_eq(verb, "want") { return "wiljan" }
return verb
}
// Irregular verb: wisan (to be)
//
// wisan is suppletive the present is formed from im / is / ist / sijum
// and the past from was / wast / was / wesum
//
// Present indicative active:
// 1sg im 2sg is 3sg ist
// 1pl sijum 2pl sijuþ 3pl sind
//
// Past indicative active:
// 1sg was 2sg wast 3sg was
// 1pl wesum 2pl wesuþ 3pl wesun
fn got_wisan_present(slot: Int) -> String {
if slot == 0 { return "im" }
if slot == 1 { return "is" }
if slot == 2 { return "ist" }
if slot == 3 { return "sijum" }
if slot == 4 { return "sijuþ" }
return "sind"
}
fn got_wisan_past(slot: Int) -> String {
if slot == 0 { return "was" }
if slot == 1 { return "wast" }
if slot == 2 { return "was" }
if slot == 3 { return "wesum" }
if slot == 4 { return "wesuþ" }
return "wesun"
}
// Irregular verb: haban (to have)
//
// Weak class 3 (-an preterite-present type).
//
// Present:
// 1sg haba 2sg habais 3sg habaiþ
// 1pl habam 2pl habaiþ 3pl haband
//
// Past (habida series weak past in -ida):
// 1sg habida 2sg habides 3sg habida
// 1pl habidum 2pl habideþ 3pl habidedun
fn got_haban_present(slot: Int) -> String {
if slot == 0 { return "haba" }
if slot == 1 { return "habais" }
if slot == 2 { return "habaiþ" }
if slot == 3 { return "habam" }
if slot == 4 { return "habaiþ" }
return "haband"
}
fn got_haban_past(slot: Int) -> String {
if slot == 0 { return "habida" }
if slot == 1 { return "habides" }
if slot == 2 { return "habida" }
if slot == 3 { return "habidum" }
if slot == 4 { return "habideþ" }
return "habidedun"
}
// Irregular verb: gaggan (to go)
//
// Strong class 7 (reduplicating); suppletive in past with iddja- forms.
//
// Present:
// 1sg gagga 2sg gaggis 3sg gaggiþ
// 1pl gagam 2pl gagiþ 3pl gaggand
//
// Past (iddja series suppletive):
// 1sg iddja 2sg iddjēs 3sg iddja
// 1pl iddjēdum 2pl iddjēduþ 3pl iddjēdun
fn got_gaggan_present(slot: Int) -> String {
if slot == 0 { return "gagga" }
if slot == 1 { return "gaggis" }
if slot == 2 { return "gaggiþ" }
if slot == 3 { return "gagam" }
if slot == 4 { return "gagiþ" }
return "gaggand"
}
fn got_gaggan_past(slot: Int) -> String {
if slot == 0 { return "iddja" }
if slot == 1 { return "iddjēs" }
if slot == 2 { return "iddja" }
if slot == 3 { return "iddjēdum" }
if slot == 4 { return "iddjēduþ" }
return "iddjēdun"
}
// Irregular verb: saihwan (to see)
//
// Strong class 5 (saiƕan / saihwan). Present stem saihw-; past stem sahw-.
//
// Present:
// 1sg saihwa 2sg saihwis 3sg saihwiþ
// 1pl saihwam 2pl saihwiþ 3pl saihwand
//
// Past (ablaut: aia, strong past):
// 1sg sahw 2sg sahwt 3sg sahw
// 1pl sehwum 2pl sehwuþ 3pl sehwun
fn got_saihwan_present(slot: Int) -> String {
if slot == 0 { return "saihwa" }
if slot == 1 { return "saihwis" }
if slot == 2 { return "saihwiþ" }
if slot == 3 { return "saihwam" }
if slot == 4 { return "saihwiþ" }
return "saihwand"
}
fn got_saihwan_past(slot: Int) -> String {
if slot == 0 { return "sahw" }
if slot == 1 { return "sahwt" }
if slot == 2 { return "sahw" }
if slot == 3 { return "sehwum" }
if slot == 4 { return "sehwuþ" }
return "sehwun"
}
// Irregular verb: qiþan (to say)
//
// Strong class 5 (i-ablaut). Present stem qiþ-; past stem qaþ-.
//
// Present:
// 1sg qiþa 2sg qiþis 3sg qiþiþ
// 1pl qiþam 2pl qiþiþ 3pl qiþand
//
// Past (ablaut: ia):
// 1sg qaþ 2sg qast 3sg qaþ
// 1pl qēþum 2pl qēþuþ 3pl qēþun
fn got_qithan_present(slot: Int) -> String {
if slot == 0 { return "qiþa" }
if slot == 1 { return "qiþis" }
if slot == 2 { return "qiþiþ" }
if slot == 3 { return "qiþam" }
if slot == 4 { return "qiþiþ" }
return "qiþand"
}
fn got_qithan_past(slot: Int) -> String {
if slot == 0 { return "qaþ" }
if slot == 1 { return "qast" }
if slot == 2 { return "qaþ" }
if slot == 3 { return "qēþum" }
if slot == 4 { return "qēþuþ" }
return "qēþun"
}
// Irregular verb: niman (to take)
//
// Strong class 4 (sonorant stem; i-ablaut in present, a-ablaut in past).
//
// Present:
// 1sg nima 2sg nimis 3sg nimiþ
// 1pl nimam 2pl nimiþ 3pl nimand
//
// Past (ablaut: ia):
// 1sg nam 2sg namt 3sg nam
// 1pl nēmum 2pl nēmuþ 3pl nēmun
fn got_niman_present(slot: Int) -> String {
if slot == 0 { return "nima" }
if slot == 1 { return "nimis" }
if slot == 2 { return "nimiþ" }
if slot == 3 { return "nimam" }
if slot == 4 { return "nimiþ" }
return "nimand"
}
fn got_niman_past(slot: Int) -> String {
if slot == 0 { return "nam" }
if slot == 1 { return "namt" }
if slot == 2 { return "nam" }
if slot == 3 { return "nēmum" }
if slot == 4 { return "nēmuþ" }
return "nēmun"
}
// Weak class 1 (-jan verbs): regular conjugation
//
// Class 1 weak verbs are the most productive Gothic verb class. The
// infinitive ends in -jan; the stem is obtained by stripping -jan.
//
// Present indicative active:
// 1sg stem + -a (nasjan -> nasja)
// 2sg stem + -is (nasjis)
// 3sg stem + -iþ (nasjiþ)
// 1pl stem + -jam (nasjam)
// 2pl stem + -jiþ (nasjiþ)
// 3pl stem + -jand (nasjand)
//
// Past indicative active (weak past -ida):
// 1sg stem + -ida (nasjida)
// 2sg stem + -ides (nasjides)
// 3sg stem + -ida (nasjida)
// 1pl stem + -idum (nasjidum)
// 2pl stem + -ideþ (nasjideþ)
// 3pl stem + -idedun (nasjidedun)
fn got_wk1_present_ending(slot: Int) -> String {
if slot == 0 { return "a" }
if slot == 1 { return "is" }
if slot == 2 { return "" }
if slot == 3 { return "jam" }
if slot == 4 { return "jiþ" }
return "jand"
}
fn got_wk1_past_ending(slot: Int) -> String {
if slot == 0 { return "ida" }
if slot == 1 { return "ides" }
if slot == 2 { return "ida" }
if slot == 3 { return "idum" }
if slot == 4 { return "ideþ" }
return "idedun"
}
fn got_wk1_conjugate(stem: String, tense: String, slot: Int) -> String {
if str_eq(tense, "present") {
return stem + got_wk1_present_ending(slot)
}
if str_eq(tense, "past") {
return stem + got_wk1_past_ending(slot)
}
return stem
}
// Weak class 2 (-on verbs): regular conjugation
//
// Class 2 weak verbs. Infinitive ends in -on; stem = drop -on.
// This class corresponds roughly to OE -ian / OHG -on denominatives.
//
// Present indicative active:
// 1sg stem + -o (salbon -> salbo)
// 2sg stem + -os (salbos)
// 3sg stem + -oþ (salboþ)
// 1pl stem + -om (salbom)
// 2pl stem + -oþ (salboþ)
// 3pl stem + -ond (salbond)
//
// Past indicative active (weak past -oda):
// 1sg stem + -oda (salboda)
// 2sg stem + -odes (salbodes)
// 3sg stem + -oda (salboda)
// 1pl stem + -odum (salbodum)
// 2pl stem + -odeþ (salbodeþ)
// 3pl stem + -odedun (salbodedun)
fn got_wk2_present_ending(slot: Int) -> String {
if slot == 0 { return "o" }
if slot == 1 { return "os" }
if slot == 2 { return "" }
if slot == 3 { return "om" }
if slot == 4 { return "" }
return "ond"
}
fn got_wk2_past_ending(slot: Int) -> String {
if slot == 0 { return "oda" }
if slot == 1 { return "odes" }
if slot == 2 { return "oda" }
if slot == 3 { return "odum" }
if slot == 4 { return "odeþ" }
return "odedun"
}
fn got_wk2_conjugate(stem: String, tense: String, slot: Int) -> String {
if str_eq(tense, "present") {
return stem + got_wk2_present_ending(slot)
}
if str_eq(tense, "past") {
return stem + got_wk2_past_ending(slot)
}
return stem
}
// Infinitive class detection
//
// Identifies the verb class from the infinitive ending so that the correct
// regular paradigm can be applied for verbs not in the irregular table.
//
// ends in -jan -> weak class 1 ("wk1")
// ends in -on -> weak class 2 ("wk2")
// otherwise -> default to weak class 1 (most productive class)
fn got_verb_class(verb: String) -> String {
if got_str_ends(verb, "jan") { return "wk1" }
if got_str_ends(verb, "on") { return "wk2" }
return "wk1"
}
// got_verb_stem: strip the infinitive suffix to expose the productive stem.
//
// wk1: strip -jan (3 bytes all ASCII)
// wk2: strip -on (2 bytes)
fn got_verb_stem(verb: String, vclass: String) -> String {
if str_eq(vclass, "wk1") { return got_str_drop_last(verb, 3) }
if str_eq(vclass, "wk2") { return got_str_drop_last(verb, 2) }
return got_str_drop_last(verb, 2)
}
// got_conjugate: main conjugation entry point
//
// verb: Gothic infinitive in Latin romanisation (e.g. "wisan", "nasjan")
// or English canonical label ("be", "go", "have")
// tense: "present" | "past"
// person: "first" | "second" | "third"
// number: "singular" | "plural" (or "dual" treated as plural)
//
// Returns the inflected form. Falls back to the infinitive for unknown
// tenses rather than crashing.
fn got_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let v: String = got_map_canonical(verb)
let slot: Int = got_slot(person, number)
// Irregular: wisan (to be)
if str_eq(v, "wisan") {
if str_eq(tense, "present") { return got_wisan_present(slot) }
if str_eq(tense, "past") { return got_wisan_past(slot) }
return v
}
// Irregular: haban (to have)
if str_eq(v, "haban") {
if str_eq(tense, "present") { return got_haban_present(slot) }
if str_eq(tense, "past") { return got_haban_past(slot) }
return v
}
// Irregular: gaggan (to go)
if str_eq(v, "gaggan") {
if str_eq(tense, "present") { return got_gaggan_present(slot) }
if str_eq(tense, "past") { return got_gaggan_past(slot) }
return v
}
// Irregular: saihwan (to see)
if str_eq(v, "saihwan") {
if str_eq(tense, "present") { return got_saihwan_present(slot) }
if str_eq(tense, "past") { return got_saihwan_past(slot) }
return v
}
// Irregular: qiþan (to say)
if str_eq(v, "qiþan") {
if str_eq(tense, "present") { return got_qithan_present(slot) }
if str_eq(tense, "past") { return got_qithan_past(slot) }
return v
}
// Irregular: niman (to take)
if str_eq(v, "niman") {
if str_eq(tense, "present") { return got_niman_present(slot) }
if str_eq(tense, "past") { return got_niman_past(slot) }
return v
}
// Regular weak conjugation
let vclass: String = got_verb_class(v)
let stem: String = got_verb_stem(v, vclass)
if str_eq(vclass, "wk1") {
return got_wk1_conjugate(stem, tense, slot)
}
if str_eq(vclass, "wk2") {
return got_wk2_conjugate(stem, tense, slot)
}
// Final fallback: return the infinitive
return v
}
// a-stem masculine paradigm (dags day)
//
// The a-stem masculine is the largest Gothic noun class, corresponding to the
// Proto-Germanic *-a- stems (OE -as, German -e plurals).
//
// Nominative singular: dags
// Stem: dag- (strip final -s from nom sg)
//
// Singular: nom dags acc dag gen dagis dat daga
// Plural: nom dagos acc dagans gen dage dat dagam
//
// Note: some sources list acc sg as "dag" (without -n), consistent with
// Gothic nomacc distinction in masculine a-stems.
fn got_decline_a_stem_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "s" }
if str_eq(gram_case, "accusative") { return stem }
if str_eq(gram_case, "genitive") { return stem + "is" }
if str_eq(gram_case, "dative") { return stem + "a" }
return stem + "s"
}
fn got_decline_a_stem_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "os" }
if str_eq(gram_case, "accusative") { return stem + "ans" }
if str_eq(gram_case, "genitive") { return stem + "e" }
if str_eq(gram_case, "dative") { return stem + "am" }
return stem + "os"
}
// o-stem feminine paradigm (gibo gift)
//
// The o-stem feminines correspond to Proto-Germanic *-ō- stems. The
// nominative singular ends in -o; nom and acc plural are identical.
//
// Singular: nom gibo acc giba gen gibos dat gibai
// Plural: nom gibos acc gibos gen gibo dat gibom
fn got_decline_o_stem_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "o" }
if str_eq(gram_case, "accusative") { return stem + "a" }
if str_eq(gram_case, "genitive") { return stem + "os" }
if str_eq(gram_case, "dative") { return stem + "ai" }
return stem + "o"
}
fn got_decline_o_stem_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "os" }
if str_eq(gram_case, "accusative") { return stem + "os" }
if str_eq(gram_case, "genitive") { return stem + "o" }
if str_eq(gram_case, "dative") { return stem + "om" }
return stem + "os"
}
// n-stem masculine paradigm (guma man)
//
// The n-stems (weak nouns) are characterised by -n- appearing in all cases
// except the nominative singular. They are sometimes called "weak nouns"
// by analogy with Old English grammar.
//
// Singular: nom guma acc guman gen gumins dat gumin
// Plural: nom gumans acc gumans gen gumane dat gumam
fn got_decline_n_stem_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "a" }
if str_eq(gram_case, "accusative") { return stem + "an" }
if str_eq(gram_case, "genitive") { return stem + "ins" }
if str_eq(gram_case, "dative") { return stem + "in" }
return stem + "a"
}
fn got_decline_n_stem_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "ans" }
if str_eq(gram_case, "accusative") { return stem + "ans" }
if str_eq(gram_case, "genitive") { return stem + "ane" }
if str_eq(gram_case, "dative") { return stem + "am" }
return stem + "ans"
}
// Stem type detection
//
// Infers the stem class from the nominative singular form.
//
// Strategy:
// ends in -s and previous char is not a vowel -> a-stem masc (dags type)
// ends in -o -> o-stem fem (gibo type)
// ends in -a -> n-stem masc (guma type)
// ends in -s and previous char is a vowel -> could be various; default a-stem
// otherwise -> default to a-stem
//
// For the common case where the caller passes the paradigm lemma directly:
// "dags" -> "a", "gibo" -> "o", "guma" -> "n"
fn got_stem_type(noun: String) -> String {
if got_str_ends(noun, "o") { return "o" }
if got_str_ends(noun, "a") { return "n" }
if got_str_ends(noun, "s") { return "a" }
// Fallback for other forms
return "a"
}
// got_extract_stem: strip the nom-sg suffix to obtain the bare stem.
//
// a-stem: strip final -s (dags -> dag)
// o-stem: strip final -o (gibo -> gib)
// n-stem: strip final -a (guma -> gum)
//
// All suffix bytes are single-byte ASCII, so str_len gives byte=char counts.
fn got_extract_stem(noun: String, stype: String) -> String {
let n: Int = str_len(noun)
// All three suffixes are 1 byte each
return str_slice(noun, 0, n - 1)
}
// Gender inference for article selection
//
// The demonstrative-article used in got_noun_phrase depends on gender.
// Gender correlates with stem class in Gothic (imperfect but useful heuristic):
// a-stem -> masculine -> sa
// o-stem -> feminine -> þo (nom sg of feminine demonstrative)
// n-stem -> masculine -> sa
//
// Neuter a-stems (like "waurd" word) exist but are not distinguished here;
// the NLG layer is expected to pass explicit gender when the distinction matters.
fn got_demo_article(stype: String) -> String {
if str_eq(stype, "o") { return "þo" }
// a-stem and n-stem both masculine
return "sa"
}
// got_decline: main declension entry point
//
// noun: nominative singular in Gothic Latin romanisation
// (e.g. "dags", "gibo", "guma")
// gram_case: "nominative" | "accusative" | "genitive" | "dative"
// number: "singular" | "plural"
//
// Returns the inflected form. Unknown stem types return the nominative
// singular unchanged rather than producing garbage output.
fn got_decline(noun: String, gram_case: String, number: String) -> String {
let stype: String = got_stem_type(noun)
let stem: String = got_extract_stem(noun, stype)
if str_eq(stype, "a") {
if str_eq(number, "singular") { return got_decline_a_stem_sg(stem, gram_case) }
return got_decline_a_stem_pl(stem, gram_case)
}
if str_eq(stype, "o") {
if str_eq(number, "singular") { return got_decline_o_stem_sg(stem, gram_case) }
return got_decline_o_stem_pl(stem, gram_case)
}
if str_eq(stype, "n") {
if str_eq(number, "singular") { return got_decline_n_stem_sg(stem, gram_case) }
return got_decline_n_stem_pl(stem, gram_case)
}
// Unknown: return the nominative singular unchanged
return noun
}
// got_noun_phrase: noun phrase builder
//
// Gothic has no definite article. The demonstrative pronouns sa (masc) /
// so (fem) / þata (neut) are used in contexts where a definite-article-like
// meaning is required (closely following Greek ὁ/ἡ/τό in Wulfila's translation).
//
// When definite=true this function prepends the gender-appropriate demonstrative
// in its nominative singular form. For non-nominative cases the demonstrative
// should ideally be declined to match; this implementation prepends the nom-sg
// form as a simplification suitable for NLG output. Callers requiring full
// demonstrative agreement should call got_decline_demonstrative separately.
//
// noun: nominative singular Gothic noun
// gram_case: "nominative" | "accusative" | "genitive" | "dative"
// number: "singular" | "plural"
// definite: "true" prepends the demonstrative; any other value omits it
fn got_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
let declined: String = got_decline(noun, gram_case, number)
if str_eq(definite, "true") {
let stype: String = got_stem_type(noun)
let article: String = got_demo_article(stype)
return article + " " + declined
}
return declined
}