752 lines
28 KiB
EmacsLisp
752 lines
28 KiB
EmacsLisp
// 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
|
||
}
|