451 lines
16 KiB
EmacsLisp
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
|
|
}
|