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

451 lines
16 KiB
EmacsLisp

// morphology-enm.el - Middle English morphology for the NLG engine.
//
// Implements Middle English verb conjugation and noun declension for the
// ca. 1100-1500 CE period (Chaucerian English). Designed as a companion to
// morphology.el and called by the engine when the language profile code is "enm".
//
// Language profile: code=enm, name=Middle English, morph_type=analytic,
// word_order=SVO, question_strategy=inversion, script=latin, family=germanic.
//
// Verb conjugation covered:
// Tenses: present, past
// Persons: first/second/third x singular/plural (slots 0-5)
// Classes: weak (productive: -est 2sg, -eth 3sg, -en pl; past: -ede/-de/-te)
// Irregulars: been/ben (be), han/haven (have), goon (go), seen (see),
// seyn/seyen (say), comen (come), maken (make)
// Canonical map: "be" -> "been"
//
// Noun declension covered:
// Middle English has largely lost case endings. This module handles:
// - nominative (base form)
// - genitive singular (+es)
// - plural (+es default; irregular forms for common words)
// Common irregulars: man->men, child->children, ox->oxen, foot->feet,
// tooth->teeth
//
// Article formation:
// Definite: "the" prepended
// Indefinite: "a" or "an" based on the first letter of the noun phrase
//
// Depends on: morphology.el (str_ends_with, str_len, str_slice, str_eq)
// String helpers
fn enm_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn enm_drop(s: String, n: Int) -> String {
let len: Int = str_len(s)
if n >= len { return "" }
return str_slice(s, 0, len - n)
}
fn enm_first_char(s: String) -> String {
if str_len(s) == 0 { return "" }
return str_slice(s, 0, 1)
}
// Person/number slot
//
// Maps person x number to a 0-based paradigm slot.
// 0 = 1st singular (I / ich)
// 1 = 2nd singular (thou)
// 2 = 3rd singular (he / she / it)
// 3 = 1st plural (we)
// 4 = 2nd plural (ye)
// 5 = 3rd plural (they)
fn enm_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
if str_eq(number, "singular") { return 2 }
return 5
}
// Irregular verb tables
//
// Each irregular verb has a present and past paradigm of six forms (slots 0-5).
// Forms are in Middle English spelling, close to Chaucerian usage.
fn enm_been_present(slot: Int) -> String {
if slot == 0 { return "am" }
if slot == 1 { return "art" }
if slot == 2 { return "is" }
if slot == 3 { return "aren" }
if slot == 4 { return "been" }
return "been"
}
fn enm_been_past(slot: Int) -> String {
if slot == 0 { return "was" }
if slot == 1 { return "were" }
if slot == 2 { return "was" }
if slot == 3 { return "were" }
if slot == 4 { return "were" }
return "were"
}
fn enm_haven_present(slot: Int) -> String {
if slot == 0 { return "have" }
if slot == 1 { return "hast" }
if slot == 2 { return "hath" }
if slot == 3 { return "have" }
if slot == 4 { return "have" }
return "have"
}
fn enm_haven_past(slot: Int) -> String {
if slot == 0 { return "hadde" }
if slot == 1 { return "haddest" }
if slot == 2 { return "hadde" }
if slot == 3 { return "hadden" }
if slot == 4 { return "hadden" }
return "hadden"
}
fn enm_goon_present(slot: Int) -> String {
if slot == 0 { return "go" }
if slot == 1 { return "goost" }
if slot == 2 { return "gooth" }
if slot == 3 { return "goon" }
if slot == 4 { return "goon" }
return "goon"
}
fn enm_goon_past(slot: Int) -> String {
if slot == 0 { return "wente" }
if slot == 1 { return "wentest" }
if slot == 2 { return "wente" }
if slot == 3 { return "wenten" }
if slot == 4 { return "wenten" }
return "wenten"
}
fn enm_seen_present(slot: Int) -> String {
if slot == 0 { return "see" }
if slot == 1 { return "seest" }
if slot == 2 { return "seeth" }
if slot == 3 { return "seen" }
if slot == 4 { return "seen" }
return "seen"
}
fn enm_seen_past(slot: Int) -> String {
if slot == 0 { return "saugh" }
if slot == 1 { return "sawest" }
if slot == 2 { return "saugh" }
if slot == 3 { return "sawen" }
if slot == 4 { return "sawen" }
return "sawen"
}
fn enm_seyen_present(slot: Int) -> String {
if slot == 0 { return "seye" }
if slot == 1 { return "seyst" }
if slot == 2 { return "seith" }
if slot == 3 { return "seyen" }
if slot == 4 { return "seyen" }
return "seyen"
}
fn enm_seyen_past(slot: Int) -> String {
if slot == 0 { return "seide" }
if slot == 1 { return "seidest" }
if slot == 2 { return "seide" }
if slot == 3 { return "seiden" }
if slot == 4 { return "seiden" }
return "seiden"
}
fn enm_comen_present(slot: Int) -> String {
if slot == 0 { return "come" }
if slot == 1 { return "comest" }
if slot == 2 { return "cometh" }
if slot == 3 { return "comen" }
if slot == 4 { return "comen" }
return "comen"
}
fn enm_comen_past(slot: Int) -> String {
if slot == 0 { return "cam" }
if slot == 1 { return "come" }
if slot == 2 { return "cam" }
if slot == 3 { return "comen" }
if slot == 4 { return "comen" }
return "comen"
}
fn enm_maken_present(slot: Int) -> String {
if slot == 0 { return "make" }
if slot == 1 { return "makest" }
if slot == 2 { return "maketh" }
if slot == 3 { return "maken" }
if slot == 4 { return "maken" }
return "maken"
}
fn enm_maken_past(slot: Int) -> String {
if slot == 0 { return "made" }
if slot == 1 { return "madest" }
if slot == 2 { return "made" }
if slot == 3 { return "maden" }
if slot == 4 { return "maden" }
return "maden"
}
// Canonical verb mapping
//
// Maps English semantic labels to Middle English infinitives so the semantic
// layer can request forms without knowing the target-language lexeme.
fn enm_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "been" }
if str_eq(verb, "have") { return "haven" }
if str_eq(verb, "go") { return "goon" }
if str_eq(verb, "see") { return "seen" }
if str_eq(verb, "say") { return "seyen" }
if str_eq(verb, "come") { return "comen" }
if str_eq(verb, "make") { return "maken" }
return verb
}
// Weak verb stem derivation
//
// For weak verbs the present stem is the infinitive minus any trailing -en or -e.
// Past tense suffix: most common is -ede (after unvoiced consonants often -te,
// after voiced -de). This module uses -ede as the default productive past suffix.
//
// Present:
// slot 0: stem (I love)
// slot 1: stem + est (thou lovest)
// slot 2: stem + eth (he loveth)
// slot 3: stem + en (we loven)
// slot 4: stem + en (ye loven)
// slot 5: stem + en (they loven)
//
// Past:
// slot 0: stem + ede (I lovede)
// slot 1: stem + edest (thou lovedest)
// slot 2: stem + ede (he lovede)
// slot 3..5: stem + eden (we loveden)
fn enm_weak_stem(verb: String) -> String {
if enm_str_ends(verb, "en") { return enm_drop(verb, 2) }
if enm_str_ends(verb, "e") { return enm_drop(verb, 1) }
return verb
}
fn enm_weak_present(stem: String, slot: Int) -> String {
if slot == 0 { return stem + "e" }
if slot == 1 { return stem + "est" }
if slot == 2 { return stem + "eth" }
if slot == 3 { return stem + "en" }
if slot == 4 { return stem + "en" }
return stem + "en"
}
fn enm_weak_past(stem: String, slot: Int) -> String {
if slot == 0 { return stem + "ede" }
if slot == 1 { return stem + "edest" }
if slot == 2 { return stem + "ede" }
if slot == 3 { return stem + "eden" }
if slot == 4 { return stem + "eden" }
return stem + "eden"
}
// enm_conjugate: main conjugation entry point
//
// verb: Middle English infinitive (e.g. "loven", "been") or English canonical
// tense: "present" | "past"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns the inflected form. Unknown tenses fall back to the infinitive rather
// than crashing.
fn enm_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let v: String = enm_map_canonical(verb)
let slot: Int = enm_slot(person, number)
// Irregulars
if str_eq(v, "been") {
if str_eq(tense, "present") { return enm_been_present(slot) }
if str_eq(tense, "past") { return enm_been_past(slot) }
return v
}
if str_eq(v, "haven") {
if str_eq(tense, "present") { return enm_haven_present(slot) }
if str_eq(tense, "past") { return enm_haven_past(slot) }
return v
}
if str_eq(v, "goon") {
if str_eq(tense, "present") { return enm_goon_present(slot) }
if str_eq(tense, "past") { return enm_goon_past(slot) }
return v
}
if str_eq(v, "seen") {
if str_eq(tense, "present") { return enm_seen_present(slot) }
if str_eq(tense, "past") { return enm_seen_past(slot) }
return v
}
if str_eq(v, "seyen") {
if str_eq(tense, "present") { return enm_seyen_present(slot) }
if str_eq(tense, "past") { return enm_seyen_past(slot) }
return v
}
if str_eq(v, "comen") {
if str_eq(tense, "present") { return enm_comen_present(slot) }
if str_eq(tense, "past") { return enm_comen_past(slot) }
return v
}
if str_eq(v, "maken") {
if str_eq(tense, "present") { return enm_maken_present(slot) }
if str_eq(tense, "past") { return enm_maken_past(slot) }
return v
}
// Regular weak verb
let stem: String = enm_weak_stem(v)
if str_eq(tense, "present") { return enm_weak_present(stem, slot) }
if str_eq(tense, "past") { return enm_weak_past(stem, slot) }
// Unknown tense: return infinitive unchanged
return v
}
// Noun plural irregulars
//
// Returns the suppletive plural form for nouns with non-productive plurals, or ""
// if the noun takes the regular -es plural.
//
// Covered: man, woman, child, ox, foot, tooth, goose, mouse, louse
// These mirror patterns still visible in Modern English, present in ME too.
fn enm_irregular_plural(noun: String) -> String {
if str_eq(noun, "man") { return "men" }
if str_eq(noun, "woman") { return "wommen" }
if str_eq(noun, "child") { return "children" }
if str_eq(noun, "ox") { return "oxen" }
if str_eq(noun, "foot") { return "feet" }
if str_eq(noun, "tooth") { return "teeth" }
if str_eq(noun, "goose") { return "gees" }
if str_eq(noun, "mouse") { return "mees" }
if str_eq(noun, "louse") { return "lees" }
return ""
}
// Regular plural formation
//
// Default: append -es. For nouns already ending in -e, append just -s.
// For nouns ending in -s, -x, -sh, -ch: the -es is still appropriate but
// in ME spelling the forms vary; we use the simple +es rule uniformly.
fn enm_make_plural(noun: String) -> String {
// Check suppletive irregular first
let irreg: String = enm_irregular_plural(noun)
if !str_eq(irreg, "") { return irreg }
// Noun ends in -e: just add -s to avoid double vowel
if enm_str_ends(noun, "e") { return noun + "s" }
// Default: +es
return noun + "es"
}
// enm_decline: main declension entry point
//
// Middle English has largely lost case morphology. This function handles the
// three practically relevant categories:
// nominative base form (used also for accusative and dative)
// genitive base form + es (possessive)
// plural irregular or base + es
//
// noun: base nominative form (e.g. "knyght", "man", "lond")
// gram_case: "nominative" | "accusative" | "dative" | "genitive" | "plural"
// ("accusative" and "dative" return the nominative form)
// number: "singular" | "plural"
//
// Returns the inflected form.
fn enm_decline(noun: String, gram_case: String, number: String) -> String {
// Plural number overrides gram_case for the plural form
if str_eq(number, "plural") {
return enm_make_plural(noun)
}
// Singular
if str_eq(gram_case, "genitive") {
// Genitive singular: +es (even after -e: "the kinges court")
return noun + "es"
}
// Nominative, accusative, dative all the same in ME
return noun
}
// Article selection
//
// Middle English uses "the" (definite) and "a" / "an" (indefinite).
// The indefinite article is "an" before a vowel-initial word, "a" otherwise.
// Vowel check is on the first character of the noun phrase word.
fn enm_is_vowel_initial(s: String) -> Bool {
let c: String = enm_first_char(s)
if str_eq(c, "a") { return true }
if str_eq(c, "e") { return true }
if str_eq(c, "i") { return true }
if str_eq(c, "o") { return true }
if str_eq(c, "u") { return true }
// ME also treated initial h as effectively silent in some dialects;
// we conservatively treat h-initial as consonant-initial.
return false
}
fn enm_indef_article(noun_phrase: String) -> String {
if enm_is_vowel_initial(noun_phrase) { return "an" }
return "a"
}
// enm_noun_phrase: noun phrase builder
//
// Constructs a full noun phrase with the appropriate article.
//
// noun: base nominative singular form (e.g. "knyght", "man", "lond")
// gram_case: "nominative" | "accusative" | "dative" | "genitive" | "plural"
// number: "singular" | "plural"
// definite: "true" | "false" (string comparison)
//
// Returns the complete noun phrase string (article + declined noun).
fn enm_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
let form: String = enm_decline(noun, gram_case, number)
if str_eq(definite, "true") {
return "the " + form
}
// Indefinite article only makes sense for singular; plural takes no article
if str_eq(number, "plural") {
return form
}
let art: String = enm_indef_article(form)
return art + " " + form
}