// morphology-ang.el - Old English (Anglo-Saxon) morphology for the NLG engine. // // Implements Old English verb conjugation, noun declension, and the definite // article/demonstrative pronoun. Designed as a companion to morphology.el and // called by the engine when the language profile code is "ang". // // Language profile: code=ang, name=Old English, morph_type=fusional, // word_order=SOV, question_strategy=intonation, script=latin, family=germanic. // // Typology note: Old English is a synthetic Germanic language with four // grammatical cases (nominative, accusative, genitive, dative), three genders, // and strong/weak noun and verb classes. Strong verbs form their past tense by // internal vowel change (ablaut); weak verbs use a dental (-de/-ode) suffix. // Long vowels are marked with a macron (ā ē ī ō ū) and are preserved in all // string literals; ǣ, æ, þ, ð, and ƿ (wynn) are used where historically // appropriate. V2 (verb-second) word order applies in main clauses but is not // enforced by this module — the realizer handles constituent ordering. // // Verb conjugation covered: // Tenses: present, past // Persons: first/second/third × singular/plural (slots 0-5) // Classes: weak (regular -ian), strong irregular table // Irregulars: wesan/beon (be), habban (have), gān (go), cuman (come), // secgan (say), sēon (see), dōn (do), willan (want), magan (can) // Canonical map: "be" -> "wesan" (past) / "beon" (present) // // Noun declension covered: // Strong masc a-stem (cyning pattern): nom/acc -∅, gen -es, dat -e; pl -as/-a/-um // Strong neut a-stem (word pattern): sg same as masc; pl nom/acc -∅ // Weak n-stem (nama pattern): sg nom -a, obl -an; pl -an/-ena/-um // // Article: simplified demonstrative/article forms for masculine, feminine, // neuter (se/sēo/þæt), fully declined. // // Depends on: morphology.el (str_ends_with, str_len, str_slice, str_eq) // ── String helpers ───────────────────────────────────────────────────────────── fn ang_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn ang_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) } fn ang_str_last_char(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "" } return str_slice(s, n - 1, n) } fn ang_str_last2(s: String) -> String { let n: Int = str_len(s) if n < 2 { return s } return str_slice(s, n - 2, n) } // ── Person/number slot ───────────────────────────────────────────────────────── // // Maps person × number to a 0-based index for paradigm tables. // 0 = 1st singular (ic) // 1 = 2nd singular (þū) // 2 = 3rd singular (hē/hēo/hit) // 3 = 1st plural (wē) // 4 = 2nd plural (gē) // 5 = 3rd plural (hīe) // // Old English also has a dual (wit, git) — not handled; dual falls through // to plural. fn ang_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 } // ── Canonical verb mapping ───────────────────────────────────────────────────── // // The semantic layer may pass English canonical labels. Map to Old English // citation (infinitive) forms. "be" maps to "beon" for present and "wesan" // for past — the caller selects tense, so we map "be" to "beon" and handle // the past-tense wesan forms inside the conjugation function. fn ang_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "beon" } if str_eq(verb, "have") { return "habban" } if str_eq(verb, "go") { return "gān" } if str_eq(verb, "come") { return "cuman" } if str_eq(verb, "say") { return "secgan" } if str_eq(verb, "see") { return "sēon" } if str_eq(verb, "do") { return "dōn" } if str_eq(verb, "want") { return "willan" } if str_eq(verb, "will") { return "willan" } if str_eq(verb, "can") { return "magan" } if str_eq(verb, "know") { return "witan" } if str_eq(verb, "give") { return "giefan" } if str_eq(verb, "take") { return "niman" } if str_eq(verb, "find") { return "findan" } if str_eq(verb, "make") { return "macian" } return verb } // ── Irregular: wesan (to be — past tense forms) ─────────────────────────────── // // Past: wæs wǣre wæs wǣron wǣron wǣron fn ang_wesan_past(slot: Int) -> String { if slot == 0 { return "wæs" } if slot == 1 { return "wǣre" } if slot == 2 { return "wæs" } if slot == 3 { return "wǣron" } if slot == 4 { return "wǣron" } return "wǣron" } // ── Irregular: beon (to be — present / habitual / future) ──────────────────── // // Present: bēo bist biþ bēoþ bēoþ bēoþ // // The present indicative of "wesan" is eom/eart/is/sind — that paradigm is // also provided below for completeness and for callers who specifically request // wesan present. fn ang_beon_present(slot: Int) -> String { if slot == 0 { return "bēo" } if slot == 1 { return "bist" } if slot == 2 { return "biþ" } if slot == 3 { return "bēoþ" } if slot == 4 { return "bēoþ" } return "bēoþ" } // ── Irregular: wesan present (eom/eart/is/sind) ─────────────────────────────── // // Present: eom eart is sind/sindon sind sind fn ang_wesan_present(slot: Int) -> String { if slot == 0 { return "eom" } if slot == 1 { return "eart" } if slot == 2 { return "is" } if slot == 3 { return "sind" } if slot == 4 { return "sind" } return "sind" } // ── Irregular: habban (to have) ─────────────────────────────────────────────── // // Present: hæbbe hæfst hæfþ habbað habbað habbað // Past: hæfde hæfdest hæfde hæfdon hæfdon hæfdon fn ang_habban_present(slot: Int) -> String { if slot == 0 { return "hæbbe" } if slot == 1 { return "hæfst" } if slot == 2 { return "hæfþ" } if slot == 3 { return "habbað" } if slot == 4 { return "habbað" } return "habbað" } fn ang_habban_past(slot: Int) -> String { if slot == 0 { return "hæfde" } if slot == 1 { return "hæfdest" } if slot == 2 { return "hæfde" } if slot == 3 { return "hæfdon" } if slot == 4 { return "hæfdon" } return "hæfdon" } // ── Irregular: gān (to go) ──────────────────────────────────────────────────── // // Present: gā gǣst gǣþ gāð gāð gāð // Past: ēode ēodest ēode ēodon ēodon ēodon fn ang_gan_present(slot: Int) -> String { if slot == 0 { return "gā" } if slot == 1 { return "gǣst" } if slot == 2 { return "gǣþ" } if slot == 3 { return "gāð" } if slot == 4 { return "gāð" } return "gāð" } fn ang_gan_past(slot: Int) -> String { if slot == 0 { return "ēode" } if slot == 1 { return "ēodest" } if slot == 2 { return "ēode" } if slot == 3 { return "ēodon" } if slot == 4 { return "ēodon" } return "ēodon" } // ── Irregular: cuman (to come) ──────────────────────────────────────────────── // // Present: cume cymst cymþ cumað cumað cumað // Past: cōm cōme cōm cōmon cōmon cōmon fn ang_cuman_present(slot: Int) -> String { if slot == 0 { return "cume" } if slot == 1 { return "cymst" } if slot == 2 { return "cymþ" } if slot == 3 { return "cumað" } if slot == 4 { return "cumað" } return "cumað" } fn ang_cuman_past(slot: Int) -> String { if slot == 0 { return "cōm" } if slot == 1 { return "cōme" } if slot == 2 { return "cōm" } if slot == 3 { return "cōmon" } if slot == 4 { return "cōmon" } return "cōmon" } // ── Irregular: secgan (to say) ──────────────────────────────────────────────── // // Present: secge sagast sagað secgað secgað secgað // Past: sægde sægdest sægde sægdon sægdon sægdon fn ang_secgan_present(slot: Int) -> String { if slot == 0 { return "secge" } if slot == 1 { return "sagast" } if slot == 2 { return "sagað" } if slot == 3 { return "secgað" } if slot == 4 { return "secgað" } return "secgað" } fn ang_secgan_past(slot: Int) -> String { if slot == 0 { return "sægde" } if slot == 1 { return "sægdest" } if slot == 2 { return "sægde" } if slot == 3 { return "sægdon" } if slot == 4 { return "sægdon" } return "sægdon" } // ── Irregular: sēon (to see) ────────────────────────────────────────────────── // // Present: sēo siehst siehþ sēoð sēoð sēoð // Past: seah sāwe seah sāwon sāwon sāwon fn ang_seon_present(slot: Int) -> String { if slot == 0 { return "sēo" } if slot == 1 { return "siehst" } if slot == 2 { return "siehþ" } if slot == 3 { return "sēoð" } if slot == 4 { return "sēoð" } return "sēoð" } fn ang_seon_past(slot: Int) -> String { if slot == 0 { return "seah" } if slot == 1 { return "sāwe" } if slot == 2 { return "seah" } if slot == 3 { return "sāwon" } if slot == 4 { return "sāwon" } return "sāwon" } // ── Irregular: dōn (to do) ──────────────────────────────────────────────────── // // Present: dō dēst dēþ dōð dōð dōð // Past: dyde dydest dyde dydon dydon dydon fn ang_don_present(slot: Int) -> String { if slot == 0 { return "dō" } if slot == 1 { return "dēst" } if slot == 2 { return "dēþ" } if slot == 3 { return "dōð" } if slot == 4 { return "dōð" } return "dōð" } fn ang_don_past(slot: Int) -> String { if slot == 0 { return "dyde" } if slot == 1 { return "dydest" } if slot == 2 { return "dyde" } if slot == 3 { return "dydon" } if slot == 4 { return "dydon" } return "dydon" } // ── Irregular: willan (to want / will) ──────────────────────────────────────── // // Present: wille wilt wile willað willað willað // Past: wolde woldest wolde woldon woldon woldon fn ang_willan_present(slot: Int) -> String { if slot == 0 { return "wille" } if slot == 1 { return "wilt" } if slot == 2 { return "wile" } if slot == 3 { return "willað" } if slot == 4 { return "willað" } return "willað" } fn ang_willan_past(slot: Int) -> String { if slot == 0 { return "wolde" } if slot == 1 { return "woldest" } if slot == 2 { return "wolde" } if slot == 3 { return "woldon" } if slot == 4 { return "woldon" } return "woldon" } // ── Irregular: magan (to be able / can) ────────────────────────────────────── // // Present: mæg meaht mæg magon magon magon // Past: meahte meahtest meahte meahton meahton meahton fn ang_magan_present(slot: Int) -> String { if slot == 0 { return "mæg" } if slot == 1 { return "meaht" } if slot == 2 { return "mæg" } if slot == 3 { return "magon" } if slot == 4 { return "magon" } return "magon" } fn ang_magan_past(slot: Int) -> String { if slot == 0 { return "meahte" } if slot == 1 { return "meahtest" } if slot == 2 { return "meahte" } if slot == 3 { return "meahton" } if slot == 4 { return "meahton" } return "meahton" } // ── Irregular: witan (to know) ──────────────────────────────────────────────── // // Present: wāt wāst wāt witon witon witon // Past: wisse/wiste wissest wisse wisson wisson wisson fn ang_witan_present(slot: Int) -> String { if slot == 0 { return "wāt" } if slot == 1 { return "wāst" } if slot == 2 { return "wāt" } if slot == 3 { return "witon" } if slot == 4 { return "witon" } return "witon" } fn ang_witan_past(slot: Int) -> String { if slot == 0 { return "wisse" } if slot == 1 { return "wissest" } if slot == 2 { return "wisse" } if slot == 3 { return "wisson" } if slot == 4 { return "wisson" } return "wisson" } // ── Weak verb: present-tense endings ───────────────────────────────────────── // // Weak verbs with -ian infinitives form their present tense as: // stem + -e, -est, -eþ, -aþ, -aþ, -aþ // // The stem is the infinitive with -ian stripped (or -an for class-2 verbs). fn ang_weak_present_ending(slot: Int) -> String { if slot == 0 { return "e" } if slot == 1 { return "est" } if slot == 2 { return "eþ" } if slot == 3 { return "aþ" } if slot == 4 { return "aþ" } return "aþ" } // ── Weak verb: past-tense ending selection ──────────────────────────────────── // // Class 1 (-ian with short stem): past -ede (e.g. nerian -> nerede) // Class 2 (-ian with long/heavy stem): past -ode (e.g. macian -> macode) // Class 3 (-ian, small group): past -de (e.g. habban -> hæfde — irregular) // // Heuristic: if the stem length is 1 char, use -ede; otherwise use -ode. // This is a simplification; correct assignment requires lexical class marking. // // For the past, all persons in the plural share -on, and all singulars share // the same dental-suffixed stem. fn ang_weak_past_stem(stem: String) -> String { let slen: Int = str_len(stem) if slen <= 2 { return stem + "ede" } return stem + "ode" } fn ang_weak_past(stem: String, slot: Int) -> String { let pstem: String = ang_weak_past_stem(stem) if slot == 0 { return pstem } if slot == 1 { return pstem + "st" } if slot == 2 { return pstem } if slot == 3 { return ang_str_drop_last(pstem, 1) + "on" } if slot == 4 { return ang_str_drop_last(pstem, 1) + "on" } return ang_str_drop_last(pstem, 1) + "on" } // ── Stem extraction for weak verbs ──────────────────────────────────────────── // // Strip the infinitive ending to recover the stem: // -ian -> strip 3 chars (nerian -> ner-, macian -> mac-) // -an -> strip 2 chars (habban -> habb-; fallback for non -ian) // otherwise: return as-is fn ang_weak_stem(verb: String) -> String { if ang_str_ends(verb, "ian") { return ang_str_drop_last(verb, 3) } if ang_str_ends(verb, "an") { return ang_str_drop_last(verb, 2) } return verb } // ── ang_conjugate: main conjugation entry point ─────────────────────────────── // // verb: Old English infinitive or English canonical label // tense: "present" | "past" // person: "first" | "second" | "third" // number: "singular" | "plural" // // Strategy: // 1. Map canonical English labels to OE verbs. // 2. Check the full irregular table. // 3. Fall back to weak conjugation for unknown -ian/-an verbs. // 4. Return the base form if nothing matches. fn ang_conjugate(verb: String, tense: String, person: String, number: String) -> String { let v: String = ang_map_canonical(verb) let slot: Int = ang_slot(person, number) // ── Irregulars ──────────────────────────────────────────────────────────── // beon: present-tense "be" (habitual/future/general) if str_eq(v, "beon") { if str_eq(tense, "present") { return ang_beon_present(slot) } // past: use wesan past forms return ang_wesan_past(slot) } // wesan: past "be" and present "be" (existential/stative) if str_eq(v, "wesan") { if str_eq(tense, "present") { return ang_wesan_present(slot) } return ang_wesan_past(slot) } if str_eq(v, "habban") { if str_eq(tense, "present") { return ang_habban_present(slot) } return ang_habban_past(slot) } if str_eq(v, "gān") { if str_eq(tense, "present") { return ang_gan_present(slot) } return ang_gan_past(slot) } if str_eq(v, "cuman") { if str_eq(tense, "present") { return ang_cuman_present(slot) } return ang_cuman_past(slot) } if str_eq(v, "secgan") { if str_eq(tense, "present") { return ang_secgan_present(slot) } return ang_secgan_past(slot) } if str_eq(v, "sēon") { if str_eq(tense, "present") { return ang_seon_present(slot) } return ang_seon_past(slot) } if str_eq(v, "dōn") { if str_eq(tense, "present") { return ang_don_present(slot) } return ang_don_past(slot) } if str_eq(v, "willan") { if str_eq(tense, "present") { return ang_willan_present(slot) } return ang_willan_past(slot) } if str_eq(v, "magan") { if str_eq(tense, "present") { return ang_magan_present(slot) } return ang_magan_past(slot) } if str_eq(v, "witan") { if str_eq(tense, "present") { return ang_witan_present(slot) } return ang_witan_past(slot) } // ── Regular weak conjugation ────────────────────────────────────────────── let stem: String = ang_weak_stem(v) if str_eq(tense, "present") { return stem + ang_weak_present_ending(slot) } if str_eq(tense, "past") { return ang_weak_past(stem, slot) } // Unknown tense: return infinitive return v } // ── Noun declension class detection ─────────────────────────────────────────── // // Infer the declension class from the nominative singular form and an optional // gender hint. Without a full lexicon, ending-based heuristics are used: // // ends in -a -> weak n-stem (nama pattern) // ends in -e (long) -> may be various; default to strong masc a-stem // any other ending -> strong a-stem; gender distinguishes masc vs neut // // The caller may pass gender as a hint: // "masculine" | "feminine" | "neuter" | "" (empty = infer) // // For simplicity this module handles three paradigms: // "strong_masc" — a-stem masculine (cyning, mann) // "strong_neut" — a-stem neuter (word, scip) // "weak" — n-stem (nama, ēage) fn ang_declension(noun: String, gender: String) -> String { if ang_str_ends(noun, "a") { return "weak" } if str_eq(gender, "neuter") { return "strong_neut" } return "strong_masc" } // ── Strong masculine a-stem (cyning pattern) ────────────────────────────────── // // Stem: the noun as given (nom sg lacks an inflectional ending in this class). // // Singular: nom -∅ acc -∅ gen -es dat -e // Plural: nom -as acc -as gen -a dat -um fn ang_decline_strong_masc(noun: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "accusative") { return noun } if str_eq(gram_case, "genitive") { return noun + "es" } if str_eq(gram_case, "dative") { return noun + "e" } return noun } // plural if str_eq(gram_case, "nominative") { return noun + "as" } if str_eq(gram_case, "accusative") { return noun + "as" } if str_eq(gram_case, "genitive") { return noun + "a" } if str_eq(gram_case, "dative") { return noun + "um" } return noun + "as" } // ── Strong neuter a-stem (word pattern) ─────────────────────────────────────── // // Singular: same as strong masc // Plural: nom/acc -∅ gen -a dat -um fn ang_decline_strong_neut(noun: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "accusative") { return noun } if str_eq(gram_case, "genitive") { return noun + "es" } if str_eq(gram_case, "dative") { return noun + "e" } return noun } // plural: neuters have zero ending in nom/acc if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "accusative") { return noun } if str_eq(gram_case, "genitive") { return noun + "a" } if str_eq(gram_case, "dative") { return noun + "um" } return noun } // ── Weak n-stem (nama pattern) ──────────────────────────────────────────────── // // The nom sg ends in -a; the oblique stem is formed by stripping -a and adding // -an. Plural genitive is -ena. // // Singular: nom -a acc -an gen -an dat -an // Plural: nom -an acc -an gen -ena dat -um fn ang_decline_weak(noun: String, gram_case: String, number: String) -> String { // Oblique stem: strip the final -a let stem: String = ang_str_drop_last(noun, 1) if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "accusative") { return stem + "an" } if str_eq(gram_case, "genitive") { return stem + "an" } if str_eq(gram_case, "dative") { return stem + "an" } return noun } // plural if str_eq(gram_case, "nominative") { return stem + "an" } if str_eq(gram_case, "accusative") { return stem + "an" } if str_eq(gram_case, "genitive") { return stem + "ena" } if str_eq(gram_case, "dative") { return stem + "um" } return stem + "an" } // ── ang_decline: main declension entry point ────────────────────────────────── // // noun: nominative singular Old English noun (e.g. "cyning", "word", "nama") // gram_case: "nominative" | "accusative" | "genitive" | "dative" // number: "singular" | "plural" // gender: "masculine" | "neuter" | "feminine" | "" (empty triggers inference) // // Returns the inflected form. Falls back to the nominative singular for any // unrecognised combination. fn ang_decline(noun: String, gram_case: String, number: String, gender: String) -> String { let decl: String = ang_declension(noun, gender) if str_eq(decl, "strong_masc") { return ang_decline_strong_masc(noun, gram_case, number) } if str_eq(decl, "strong_neut") { return ang_decline_strong_neut(noun, gram_case, number) } if str_eq(decl, "weak") { return ang_decline_weak(noun, gram_case, number) } // Unknown: return nominative unchanged return noun } // ── Definite article / demonstrative: se/sēo/þæt ───────────────────────────── // // Old English used the demonstrative pronoun se/sēo/þæt as a definite article. // The full paradigm (gender × case × number) is given below. // // Masculine: // sg: nom se acc þone gen þæs dat þǣm // pl: nom þā acc þā gen þāra dat þǣm // // Feminine: // sg: nom sēo acc þā gen þǣre dat þǣre // pl: nom þā acc þā gen þāra dat þǣm // // Neuter: // sg: nom þæt acc þæt gen þæs dat þǣm // pl: nom þā acc þā gen þāra dat þǣm fn ang_article_masculine(gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "se" } if str_eq(gram_case, "accusative") { return "þone" } if str_eq(gram_case, "genitive") { return "þæs" } if str_eq(gram_case, "dative") { return "þǣm" } return "se" } // plural if str_eq(gram_case, "nominative") { return "þā" } if str_eq(gram_case, "accusative") { return "þā" } if str_eq(gram_case, "genitive") { return "þāra" } if str_eq(gram_case, "dative") { return "þǣm" } return "þā" } fn ang_article_feminine(gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "sēo" } if str_eq(gram_case, "accusative") { return "þā" } if str_eq(gram_case, "genitive") { return "þǣre" } if str_eq(gram_case, "dative") { return "þǣre" } return "sēo" } // plural if str_eq(gram_case, "nominative") { return "þā" } if str_eq(gram_case, "accusative") { return "þā" } if str_eq(gram_case, "genitive") { return "þāra" } if str_eq(gram_case, "dative") { return "þǣm" } return "þā" } fn ang_article_neuter(gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "þæt" } if str_eq(gram_case, "accusative") { return "þæt" } if str_eq(gram_case, "genitive") { return "þæs" } if str_eq(gram_case, "dative") { return "þǣm" } return "þæt" } // plural if str_eq(gram_case, "nominative") { return "þā" } if str_eq(gram_case, "accusative") { return "þā" } if str_eq(gram_case, "genitive") { return "þāra" } if str_eq(gram_case, "dative") { return "þǣm" } return "þā" } fn ang_article(gender: String, gram_case: String, number: String) -> String { if str_eq(gender, "masculine") { return ang_article_masculine(gram_case, number) } if str_eq(gender, "feminine") { return ang_article_feminine(gram_case, number) } // neuter return ang_article_neuter(gram_case, number) } // ── Gender inference from noun form ─────────────────────────────────────────── // // A last-resort heuristic when the caller provides no gender hint. // -a ending strongly suggests weak masculine or neuter (but most -a nouns are // masculine weak). Without a full lexicon, masculine is the safe default. fn ang_infer_gender(noun: String) -> String { if ang_str_ends(noun, "u") { return "feminine" } if ang_str_ends(noun, "e") { return "feminine" } return "masculine" } // ── ang_noun_phrase: noun phrase builder ────────────────────────────────────── // // Produces a declined noun with optional definite article (demonstrative) // prepended. When gender is empty ("") it is inferred from the noun form. // // noun: nominative singular Old English noun // gram_case: "nominative" | "accusative" | "genitive" | "dative" // number: "singular" | "plural" // definite: "true" | "false" fn ang_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String { let gender: String = ang_infer_gender(noun) let declined: String = ang_decline(noun, gram_case, number, gender) if str_eq(definite, "true") { let art: String = ang_article(gender, gram_case, number) return art + " " + declined } return declined }