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
+636
View File
@@ -0,0 +1,636 @@
// morphology-goh.el - Old High German morphology for the NLG engine.
//
// Implements Old High German verb conjugation, noun declension, and the
// demonstrative determiner. Designed as a companion to morphology.el and
// called by the engine when the language profile code is "goh".
//
// Language profile: code=goh, name=Old High German, morph_type=fusional,
// word_order=V2, question_strategy=inversion, script=latin,
// family=germanic.
//
// Historical note: Old High German (ca. 7501050 CE) is the earliest
// substantially attested stage of High German, preserved in texts such as the
// Hildebrandslied, the Muspilli, and Notker's translations. It is the
// ancestor of Middle High German and thence Modern German. The "High" refers
// to the geographical highlands of southern Germany, Austria, and Switzerland
// distinct from the Low German / Old Saxon dialects spoken to the north.
//
// Defining feature the Second Germanic Consonant Shift (Hochdeutsche
// Lautverschiebung), which distinguishes OHG from Gothic and Old English:
// p ff/pf (Gothic "skip" OHG "skif"; Goth "apan" → OHG "affo")
// t ss/z (Goth "watan" OHG "wazzer"; OE "tid" → OHG "zit")
// k ch (Goth "mikan" OHG "mihil")
// This shift only applies to inherited consonants and not to recent loanwords.
//
// Three genders (masculine, feminine, neuter), four cases (nominative,
// accusative, genitive, dative), and two numbers (singular, plural).
//
// Verb conjugation covered:
// Tenses: present indicative, past indicative
// Persons: first/second/third × singular/plural (slots 0-5)
// Classes: weak verbs (dental -ta past suffix the most productive class)
// Irregulars: wesan/sīn (be), habēn (have), gān (go), sehan (see),
// quethan (say), tuon (do)
// Canonical map: "be" -> "wesan"
//
// Noun declension covered:
// Strong masc a-stem (tag day): 4 cases × sg/pl
// Strong fem ō-stem (geba gift): 4 cases × sg/pl
// Strong neut a-stem (wort word): 4 cases × sg/pl
// Weak masc n-stem (boto messenger): 4 cases × sg/pl
//
// Demonstrative / definite article:
// OHG uses the demonstrative dër/diu/daz (the) which doubles as a definite
// determiner. This implementation selects the correct nominative form by
// inferred gender and passes it as a separate word before the noun.
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with)
// String helpers
fn goh_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn goh_drop(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
//
// Maps person × number to a 0-based paradigm index.
// 0 = 1st singular (ih)
// 1 = 2nd singular ()
// 2 = 3rd singular (ër/siu/ez)
// 3 = 1st plural (wir)
// 4 = 2nd plural (ir)
// 5 = 3rd plural (sie)
fn goh_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 resolved to OHG dictionary infinitives
// before conjugation.
fn goh_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "wesan" }
if str_eq(verb, "have") { return "haben" }
if str_eq(verb, "go") { return "gan" }
if str_eq(verb, "see") { return "sehan" }
if str_eq(verb, "say") { return "quethan" }
if str_eq(verb, "do") { return "tuon" }
if str_eq(verb, "make") { return "tuon" }
if str_eq(verb, "come") { return "queman" }
if str_eq(verb, "give") { return "geban" }
if str_eq(verb, "know") { return "wizzan" }
if str_eq(verb, "want") { return "wellan" }
return verb
}
// Irregular verb: wesan/sīn (to be)
//
// wesan is one of the most irregular verbs in OHG. The present uses the
// bim/bin- forms (from Proto-Germanic *biju-); the past uses the was-/wāri-
// forms (from the strong past of wesan).
//
// Present indicative:
// 1sg bim/bin 2sg bist 3sg ist
// 1pl birum 2pl biruț 3pl sint
//
// Note: 2pl "biruț" the ț represents an old dental fricative; rendered here
// as "birut" (common orthographic variant in OHG manuscripts).
//
// Past indicative:
// 1sg was 2sg wāri 3sg was
// 1pl wārum 2pl wāruț 3pl wārun
fn goh_wesan_present(slot: Int) -> String {
if slot == 0 { return "bim" }
if slot == 1 { return "bist" }
if slot == 2 { return "ist" }
if slot == 3 { return "birum" }
if slot == 4 { return "birut" }
return "sint"
}
fn goh_wesan_past(slot: Int) -> String {
if slot == 0 { return "was" }
if slot == 1 { return "wari" }
if slot == 2 { return "was" }
if slot == 3 { return "warum" }
if slot == 4 { return "warut" }
return "warun"
}
// Irregular verb: habēn (to have)
//
// habēn is an athematic preterite-present verb.
//
// Present indicative:
// 1sg habem 2sg habest 3sg habet
// 1pl habemes 2pl habet 3pl habent
//
// Past (weak past -ta on stem hab-):
// 1sg habeta 2sg habetos 3sg habeta
// 1pl habetom 2pl habetot 3pl habeton
fn goh_haben_present(slot: Int) -> String {
if slot == 0 { return "habem" }
if slot == 1 { return "habest" }
if slot == 2 { return "habet" }
if slot == 3 { return "habemes" }
if slot == 4 { return "habet" }
return "habent"
}
fn goh_haben_past(slot: Int) -> String {
if slot == 0 { return "habeta" }
if slot == 1 { return "habetos" }
if slot == 2 { return "habeta" }
if slot == 3 { return "habetom" }
if slot == 4 { return "habetot" }
return "habeton"
}
// Irregular verb: gān (to go)
//
// gān is an anomalous contracted verb.
//
// Present indicative:
// 1sg gan 2sg gest 3sg get
// 1pl games 2pl gat 3pl gant
//
// Past (reduplicating/suppletive giang- forms):
// 1sg giang 2sg giangi 3sg giang
// 1pl giangum 2pl giangun 3pl giangun
fn goh_gan_present(slot: Int) -> String {
if slot == 0 { return "gan" }
if slot == 1 { return "gest" }
if slot == 2 { return "get" }
if slot == 3 { return "games" }
if slot == 4 { return "gat" }
return "gant"
}
fn goh_gan_past(slot: Int) -> String {
if slot == 0 { return "giang" }
if slot == 1 { return "giangi" }
if slot == 2 { return "giang" }
if slot == 3 { return "giangum" }
if slot == 4 { return "giangun" }
return "giangun"
}
// Irregular verb: sehan (to see)
//
// Strong class 5 (ablaut e/a). Present has i-mutation in sg forms.
//
// Present indicative:
// 1sg sihu 2sg sihist 3sg sihit
// 1pl sehemes 2pl sehet 3pl sehent
//
// Past (strong ablaut: e a):
// 1sg sah 2sg sahi 3sg sah
// 1pl sahum 2pl sahut 3pl sahun
fn goh_sehan_present(slot: Int) -> String {
if slot == 0 { return "sihu" }
if slot == 1 { return "sihist" }
if slot == 2 { return "sihit" }
if slot == 3 { return "sehemes" }
if slot == 4 { return "sehet" }
return "sehent"
}
fn goh_sehan_past(slot: Int) -> String {
if slot == 0 { return "sah" }
if slot == 1 { return "sahi" }
if slot == 2 { return "sah" }
if slot == 3 { return "sahum" }
if slot == 4 { return "sahut" }
return "sahun"
}
// Irregular verb: quethan (to say)
//
// Strong class 5 (ablaut e/a). Present sg has i-mutation (quid-).
//
// Present indicative:
// 1sg quidu 2sg quidist 3sg quidit
// 1pl quethumes 2pl quethet 3pl quethent
//
// Past (strong ablaut: e a):
// 1sg quad 2sg quadi 3sg quad
// 1pl quadum 2pl quadut 3pl quadun
fn goh_quethan_present(slot: Int) -> String {
if slot == 0 { return "quidu" }
if slot == 1 { return "quidist" }
if slot == 2 { return "quidit" }
if slot == 3 { return "quethumes" }
if slot == 4 { return "quethet" }
return "quethent"
}
fn goh_quethan_past(slot: Int) -> String {
if slot == 0 { return "quad" }
if slot == 1 { return "quadi" }
if slot == 2 { return "quad" }
if slot == 3 { return "quadum" }
if slot == 4 { return "quadut" }
return "quadun"
}
// Irregular verb: tuon (to do/make)
//
// tuon is a contracted athematic verb.
//
// Present indicative:
// 1sg tuom 2sg tuost 3sg tuot
// 1pl tuomes 2pl tuot 3pl tuont
//
// Past (weak past -ta on stem tā-):
// 1sg teta 2sg tetos 3sg teta
// 1pl tetom 2pl tetot 3pl teton
fn goh_tuon_present(slot: Int) -> String {
if slot == 0 { return "tuom" }
if slot == 1 { return "tuost" }
if slot == 2 { return "tuot" }
if slot == 3 { return "tuomes" }
if slot == 4 { return "tuot" }
return "tuont"
}
fn goh_tuon_past(slot: Int) -> String {
if slot == 0 { return "teta" }
if slot == 1 { return "tetos" }
if slot == 2 { return "teta" }
if slot == 3 { return "tetom" }
if slot == 4 { return "tetot" }
return "teton"
}
// Weak verb regular paradigm
//
// Weak verbs are the productive OHG class new verbs are regularly formed on
// this pattern. The infinitive ends in -en or -on; the weak past uses the
// dental suffix -ta/-to (a reflex of Proto-Germanic *-dō-).
//
// Class 1 weak (most common; infinitive -en, stem ends in consonant):
//
// Present indicative (stem + ending):
// 1sg -u 2sg -ist 3sg -it
// 1pl -emēs 2pl -et 3pl -ent
//
// Past indicative (stem + dental suffix):
// 1sg -ta 2sg -tōs 3sg -ta
// 1pl -tōm 2pl -tōt 3pl -tōn
//
// The stem for a regular weak verb is obtained by dropping -en (2 bytes).
fn goh_weak_present(stem: String, slot: Int) -> String {
if slot == 0 { return stem + "u" }
if slot == 1 { return stem + "ist" }
if slot == 2 { return stem + "it" }
if slot == 3 { return stem + "emes" }
if slot == 4 { return stem + "et" }
return stem + "ent"
}
fn goh_weak_past(stem: String, slot: Int) -> String {
if slot == 0 { return stem + "ta" }
if slot == 1 { return stem + "tos" }
if slot == 2 { return stem + "ta" }
if slot == 3 { return stem + "tom" }
if slot == 4 { return stem + "tot" }
return stem + "ton"
}
// goh_verb_stem: strip the infinitive ending to expose the productive stem.
// -en endings: drop 2 bytes (most weak verbs: sagēn sag-)
// -on endings: drop 2 bytes (class 2 weak: lobōn lob-)
// -an endings: drop 2 bytes (strong verbs handled as irregular; fallback)
fn goh_verb_stem(verb: String) -> String {
return goh_drop(verb, 2)
}
// goh_conjugate: main conjugation entry point
//
// verb: OHG infinitive (e.g. "sagēn", "lobōn") or English canonical label
// tense: "present" | "past"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns the inflected form. Unknown tenses fall back to the infinitive.
fn goh_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let v: String = goh_map_canonical(verb)
let slot: Int = goh_slot(person, number)
// Irregular: wesan (to be)
if str_eq(v, "wesan") {
if str_eq(tense, "present") { return goh_wesan_present(slot) }
if str_eq(tense, "past") { return goh_wesan_past(slot) }
return v
}
// Irregular: habēn / haben (to have)
if str_eq(v, "haben") {
if str_eq(tense, "present") { return goh_haben_present(slot) }
if str_eq(tense, "past") { return goh_haben_past(slot) }
return v
}
// Also match the macron-spelled infinitive if passed directly
if str_eq(v, "haben") {
if str_eq(tense, "present") { return goh_haben_present(slot) }
if str_eq(tense, "past") { return goh_haben_past(slot) }
return v
}
// Irregular: gān / gan (to go)
if str_eq(v, "gan") {
if str_eq(tense, "present") { return goh_gan_present(slot) }
if str_eq(tense, "past") { return goh_gan_past(slot) }
return v
}
// Irregular: sehan (to see)
if str_eq(v, "sehan") {
if str_eq(tense, "present") { return goh_sehan_present(slot) }
if str_eq(tense, "past") { return goh_sehan_past(slot) }
return v
}
// Irregular: quethan (to say)
if str_eq(v, "quethan") {
if str_eq(tense, "present") { return goh_quethan_present(slot) }
if str_eq(tense, "past") { return goh_quethan_past(slot) }
return v
}
// Irregular: tuon (to do/make)
if str_eq(v, "tuon") {
if str_eq(tense, "present") { return goh_tuon_present(slot) }
if str_eq(tense, "past") { return goh_tuon_past(slot) }
return v
}
// Regular weak conjugation
let stem: String = goh_verb_stem(v)
if str_eq(tense, "present") { return goh_weak_present(stem, slot) }
if str_eq(tense, "past") { return goh_weak_past(stem, slot) }
// Final fallback: return the infinitive
return v
}
// Noun stem type detection
//
// OHG nouns are grouped by historical stem class. This engine supports four:
//
// "masc_a" strong masculine a-stem (tag, fisc, arm)
// "fem_o" strong feminine ō-stem (geba, zala, burg)
// "neut_a" strong neuter a-stem (wort, kind, tier)
// "masc_n" weak masculine n-stem (boto, hazo, namo)
//
// Detection heuristic from the citation form (nominative singular):
// ends in -o masc_n (weak masc nouns: boto, hazo)
// ends in -a fem_o (strong fem: geba, zala)
// ends in -t, -d, -n (common neuter endings) neut_a
// otherwise masc_a (default strong masculine)
//
// Callers may override by passing the stem type directly when the heuristic
// would produce the wrong class (e.g. for monosyllables with ambiguous endings).
fn goh_stem_type(noun: String) -> String {
if goh_str_ends(noun, "o") { return "masc_n" }
if goh_str_ends(noun, "a") { return "fem_o" }
if goh_str_ends(noun, "t") { return "neut_a" }
if goh_str_ends(noun, "d") { return "neut_a" }
if goh_str_ends(noun, "nd") { return "neut_a" }
return "masc_a"
}
// goh_extract_stem: derive the bare stem used as the base for all case endings.
//
// masc_a: citation form is nom sg without ending (tag = tag, fisc = fisc)
// citation IS the stem; no stripping needed
// fem_o: strip final -a (geba geb-)
// neut_a: citation form is nom/acc sg without ending (wort = wort)
// citation IS the stem; no stripping needed
// masc_n: strip final -o (boto bot-)
fn goh_extract_stem(noun: String, stype: String) -> String {
if str_eq(stype, "fem_o") { return goh_drop(noun, 1) }
if str_eq(stype, "masc_n") { return goh_drop(noun, 1) }
// masc_a and neut_a: citation IS the stem
return noun
}
// Strong masculine a-stem declension (tag day)
//
// The strong masculine a-stem is the most common OHG masculine class.
// It corresponds to the Gothic a-stem and the Latin 2nd-declension masculine.
//
// Paradigm (stem = tag-):
// Singular: nom tag acc tag gen tages dat tage
// Plural: nom taga acc taga gen tago dat tagum
fn goh_decline_masc_a_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem }
if str_eq(gram_case, "accusative") { return stem }
if str_eq(gram_case, "genitive") { return stem + "es" }
if str_eq(gram_case, "dative") { return stem + "e" }
return stem
}
fn goh_decline_masc_a_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "a" }
if str_eq(gram_case, "accusative") { return stem + "a" }
if str_eq(gram_case, "genitive") { return stem + "o" }
if str_eq(gram_case, "dative") { return stem + "um" }
return stem + "a"
}
// Strong feminine ō-stem declension (geba gift)
//
// The ō-stem feminines are the standard OHG feminine class.
// They correspond to the Gothic o-stem and Latin 1st-declension nouns.
//
// Paradigm (stem = geb-):
// Singular: nom geba acc geba gen gebā dat gebu
// Plural: nom gebā acc gebā gen gebōno dat gebōm
fn goh_decline_fem_o_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "a" }
if str_eq(gram_case, "accusative") { return stem + "a" }
if str_eq(gram_case, "genitive") { return stem + "a" }
if str_eq(gram_case, "dative") { return stem + "u" }
return stem + "a"
}
fn goh_decline_fem_o_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "a" }
if str_eq(gram_case, "accusative") { return stem + "a" }
if str_eq(gram_case, "genitive") { return stem + "ono" }
if str_eq(gram_case, "dative") { return stem + "om" }
return stem + "a"
}
// Strong neuter a-stem declension (wort word)
//
// Strong neuter nouns share the a-stem pattern but have identical nom/acc
// throughout (a pan-Germanic neuter feature). The plural differs from the
// masculine in the nom/acc: neuters use stem alone rather than stem + -a.
//
// Paradigm (stem = wort):
// Singular: nom wort acc wort gen wortes dat worte
// Plural: nom wort acc wort gen worto dat wortum
fn goh_decline_neut_a_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem }
if str_eq(gram_case, "accusative") { return stem }
if str_eq(gram_case, "genitive") { return stem + "es" }
if str_eq(gram_case, "dative") { return stem + "e" }
return stem
}
fn goh_decline_neut_a_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem }
if str_eq(gram_case, "accusative") { return stem }
if str_eq(gram_case, "genitive") { return stem + "o" }
if str_eq(gram_case, "dative") { return stem + "um" }
return stem
}
// Weak masculine n-stem declension (boto messenger)
//
// Weak nouns (n-stems) are characterised by the nasal -n- appearing in all
// forms except the nominative singular. They correspond to the Gothic n-stem
// and the Old English weak noun class.
//
// Paradigm (stem = bot-):
// Singular: nom boto acc boton gen boton dat boton
// Plural: nom boton acc boton gen botōno dat botōm
fn goh_decline_masc_n_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "o" }
if str_eq(gram_case, "accusative") { return stem + "on" }
if str_eq(gram_case, "genitive") { return stem + "on" }
if str_eq(gram_case, "dative") { return stem + "on" }
return stem + "o"
}
fn goh_decline_masc_n_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "on" }
if str_eq(gram_case, "accusative") { return stem + "on" }
if str_eq(gram_case, "genitive") { return stem + "ono" }
if str_eq(gram_case, "dative") { return stem + "om" }
return stem + "on"
}
// goh_decline: main declension entry point
//
// noun: OHG nominative singular form (e.g. "tag", "geba", "wort", "boto")
// gram_case: "nominative" | "accusative" | "genitive" | "dative"
// number: "singular" | "plural"
//
// Returns the inflected form. Unknown stem types return the citation form
// unchanged as a safe fallback.
fn goh_decline(noun: String, gram_case: String, number: String) -> String {
let stype: String = goh_stem_type(noun)
let stem: String = goh_extract_stem(noun, stype)
if str_eq(stype, "masc_a") {
if str_eq(number, "singular") { return goh_decline_masc_a_sg(stem, gram_case) }
return goh_decline_masc_a_pl(stem, gram_case)
}
if str_eq(stype, "fem_o") {
if str_eq(number, "singular") { return goh_decline_fem_o_sg(stem, gram_case) }
return goh_decline_fem_o_pl(stem, gram_case)
}
if str_eq(stype, "neut_a") {
if str_eq(number, "singular") { return goh_decline_neut_a_sg(stem, gram_case) }
return goh_decline_neut_a_pl(stem, gram_case)
}
if str_eq(stype, "masc_n") {
if str_eq(number, "singular") { return goh_decline_masc_n_sg(stem, gram_case) }
return goh_decline_masc_n_pl(stem, gram_case)
}
// Unknown: return citation form unchanged
return noun
}
// Demonstrative article
//
// OHG uses the demonstrative pronoun dër/diu/daz as a definite determiner.
// Full declension of this pronoun is complex; this implementation provides the
// nominative forms used as determiners before nouns.
//
// Nominative forms (the most common slot for a determiner):
// Masculine sg: der Feminine sg: diu Neuter sg: daz
// Plural (all genders): die
//
// Gender is inferred from the stem type:
// masc_a masculine "der"
// fem_o feminine "diu"
// neut_a neuter "daz"
// masc_n masculine "der"
fn goh_demo_article(stype: String, number: String) -> String {
if str_eq(number, "plural") { return "die" }
if str_eq(stype, "fem_o") { return "diu" }
if str_eq(stype, "neut_a") { return "daz" }
return "der"
}
// goh_noun_phrase: noun phrase builder
//
// Assembles a declined noun with an optional OHG demonstrative article.
//
// noun: OHG nominative singular (e.g. "tag", "geba", "wort", "boto")
// gram_case: "nominative" | "accusative" | "genitive" | "dative"
// number: "singular" | "plural"
// definite: "true" to prepend the demonstrative determiner; any other value omits it
//
// Note: the demonstrative is given in its nominative singular form for
// simplicity. Full agreement would require a separate declined demonstrative
// paradigm; the NLG layer should implement that when case-agreement on the
// determiner is required.
fn goh_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
let stype: String = goh_stem_type(noun)
let declined: String = goh_decline(noun, gram_case, number)
if str_eq(definite, "true") {
let art: String = goh_demo_article(stype, number)
return art + " " + declined
}
return declined
}