// morphology-non.el - Old Norse morphology for the NLG engine. // // Implements Old Norse verb conjugation and noun declension for the ca. 700-1100 CE // period. Designed as a companion to morphology.el and called by the engine when // the language profile code is "non". // // Language profile: code=non, name=Old Norse, morph_type=fusional, word_order=V2, // 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 (-a infinitive: -aði past), and a set of common strong/irregular verbs // Irregulars: vera (be), hafa (have), ganga (go), sjá (see), segja (say), // koma (come) // Canonical map: "be" -> "vera" // // Noun declension covered: // Strong masculine a-stem (like "armr") // Strong feminine ō-stem (like "gör") // Strong neuter a-stem (like "land") // Cases: nominative, accusative, genitive, dative // Numbers: singular, plural // Definite suffix: appended to the declined form (masc -inn/-ins/-inum, neut -it) // // Depends on: morphology.el (str_ends_with, str_len, str_slice, str_eq) // ── String helpers ───────────────────────────────────────────────────────────── import "morphology.el" fn non_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn non_drop(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } return str_slice(s, 0, len - n) } fn non_last(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "" } return str_slice(s, n - 1, n) } // ── Person/number slot ───────────────────────────────────────────────────────── // // Maps person x number to a 0-based paradigm slot. // 0 = 1st singular (ek) // 1 = 2nd singular (þú) // 2 = 3rd singular (hann/hon/þat) // 3 = 1st plural (vér) // 4 = 2nd plural (þér) // 5 = 3rd plural (þeir/þær/þau) fn non_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). fn non_vera_present(slot: Int) -> String { if slot == 0 { return "em" } if slot == 1 { return "ert" } if slot == 2 { return "er" } if slot == 3 { return "erum" } if slot == 4 { return "eruð" } return "eru" } fn non_vera_past(slot: Int) -> String { if slot == 0 { return "var" } if slot == 1 { return "vart" } if slot == 2 { return "var" } if slot == 3 { return "vórum" } if slot == 4 { return "vóruð" } return "vóru" } fn non_hafa_present(slot: Int) -> String { if slot == 0 { return "hefi" } if slot == 1 { return "hefr" } if slot == 2 { return "hefr" } if slot == 3 { return "höfum" } if slot == 4 { return "hafið" } return "hafa" } fn non_hafa_past(slot: Int) -> String { if slot == 0 { return "hafða" } if slot == 1 { return "hafðir" } if slot == 2 { return "hafði" } if slot == 3 { return "höfðum" } if slot == 4 { return "höfðuð" } return "höfðu" } fn non_ganga_present(slot: Int) -> String { if slot == 0 { return "geng" } if slot == 1 { return "gengr" } if slot == 2 { return "gengr" } if slot == 3 { return "göngum" } if slot == 4 { return "gangið" } return "ganga" } fn non_ganga_past(slot: Int) -> String { if slot == 0 { return "gekk" } if slot == 1 { return "gekkt" } if slot == 2 { return "gekk" } if slot == 3 { return "gengum" } if slot == 4 { return "genguð" } return "gengu" } fn non_sja_present(slot: Int) -> String { if slot == 0 { return "sé" } if slot == 1 { return "sér" } if slot == 2 { return "sér" } if slot == 3 { return "séum" } if slot == 4 { return "séið" } return "sjá" } fn non_sja_past(slot: Int) -> String { if slot == 0 { return "sá" } if slot == 1 { return "sást" } if slot == 2 { return "sá" } if slot == 3 { return "sám" } if slot == 4 { return "sáð" } return "sáu" } fn non_segja_present(slot: Int) -> String { if slot == 0 { return "segi" } if slot == 1 { return "segir" } if slot == 2 { return "segir" } if slot == 3 { return "segjum" } if slot == 4 { return "segið" } return "segja" } fn non_segja_past(slot: Int) -> String { if slot == 0 { return "sagði" } if slot == 1 { return "sagðir" } if slot == 2 { return "sagði" } if slot == 3 { return "sögðum" } if slot == 4 { return "sögðuð" } return "sögðu" } fn non_koma_present(slot: Int) -> String { if slot == 0 { return "kem" } if slot == 1 { return "kemr" } if slot == 2 { return "kemr" } if slot == 3 { return "komum" } if slot == 4 { return "komið" } return "koma" } fn non_koma_past(slot: Int) -> String { if slot == 0 { return "kom" } if slot == 1 { return "komt" } if slot == 2 { return "kom" } if slot == 3 { return "komum" } if slot == 4 { return "komuð" } return "komu" } // ── Canonical verb mapping ───────────────────────────────────────────────────── // // Maps English semantic labels to Old Norse infinitives so the semantic layer // can request forms without knowing the target language lexeme. fn non_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "vera" } if str_eq(verb, "have") { return "hafa" } if str_eq(verb, "go") { return "ganga" } if str_eq(verb, "see") { return "sjá" } if str_eq(verb, "say") { return "segja" } if str_eq(verb, "come") { return "koma" } return verb } // ── Weak verb conjugation ────────────────────────────────────────────────────── // // Weak verbs (infinitive ending in -a) form the productive conjugation class. // // Present tense (stem = drop final -a from infinitive): // Sg: 1st stem + -a 2nd stem + -ar 3rd stem + -ar // Pl: 1st stem + -um 2nd stem + -ið 3rd stem + -a // // Past tense (suffix -aði- inserted after stem): // Sg: 1st stem + -aði 2nd stem + -aðir 3rd stem + -aði // Pl: 1st stem + -uðum 2nd stem + -uðuð 3rd stem + -uðu fn non_weak_present(stem: String, slot: Int) -> String { if slot == 0 { return stem + "a" } if slot == 1 { return stem + "ar" } if slot == 2 { return stem + "ar" } if slot == 3 { return stem + "um" } if slot == 4 { return stem + "ið" } return stem + "a" } fn non_weak_past(stem: String, slot: Int) -> String { if slot == 0 { return stem + "aði" } if slot == 1 { return stem + "aðir" } if slot == 2 { return stem + "aði" } if slot == 3 { return stem + "uðum" } if slot == 4 { return stem + "uðuð" } return stem + "uðu" } // ── non_conjugate: main conjugation entry point ─────────────────────────────── // // verb: Old Norse infinitive (e.g. "kalla", "vera") 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 rather // than crashing. fn non_conjugate(verb: String, tense: String, person: String, number: String) -> String { let v: String = non_map_canonical(verb) let slot: Int = non_slot(person, number) // ── Irregulars ──────────────────────────────────────────────────────────── if str_eq(v, "vera") { if str_eq(tense, "present") { return non_vera_present(slot) } if str_eq(tense, "past") { return non_vera_past(slot) } return v } if str_eq(v, "hafa") { if str_eq(tense, "present") { return non_hafa_present(slot) } if str_eq(tense, "past") { return non_hafa_past(slot) } return v } if str_eq(v, "ganga") { if str_eq(tense, "present") { return non_ganga_present(slot) } if str_eq(tense, "past") { return non_ganga_past(slot) } return v } if str_eq(v, "sjá") { if str_eq(tense, "present") { return non_sja_present(slot) } if str_eq(tense, "past") { return non_sja_past(slot) } return v } if str_eq(v, "segja") { if str_eq(tense, "present") { return non_segja_present(slot) } if str_eq(tense, "past") { return non_segja_past(slot) } return v } if str_eq(v, "koma") { if str_eq(tense, "present") { return non_koma_present(slot) } if str_eq(tense, "past") { return non_koma_past(slot) } return v } // ── Regular weak verb (-a infinitive) ──────────────────────────────────── // // If the verb ends in -a, strip it to get the stem and apply weak endings. // Otherwise fall back to returning the base form unchanged. if non_str_ends(v, "a") { let stem: String = non_drop(v, 1) if str_eq(tense, "present") { return non_weak_present(stem, slot) } if str_eq(tense, "past") { return non_weak_past(stem, slot) } return v } // Unknown verb form: return the infinitive unchanged return v } // ── Strong masculine a-stem declension ("armr" — arm) ───────────────────────── // // The paradigm below uses "armr" as the exemplar. The caller passes the full // nominative singular (including the -r ending); the oblique stem is derived // by stripping -r (or -ur) from the nominative. // // Singular: nom armr acc arm gen arms dat armi // Plural: nom armar acc arma gen arma dat örmum // // The dative plural -örmum involves u-umlaut of the root vowel. Because umlaut // depends on the root vowel in complex ways, the module stores the dative plural // stem separately: for "armr" it is "örm". For unknown nouns we approximatehere // by returning the nominative plural (armar) rather than crashing. fn non_decline_masc(noun: String, gram_case: String, number: String) -> String { // Derive oblique stem by dropping the nominative -r ending if present. let stem: String = noun if non_str_ends(noun, "r") { let stem = non_drop(noun, 1) } // Hard-code the known exemplar "armr" fully, including the umlauted dat pl. if str_eq(noun, "armr") { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "armr" } if str_eq(gram_case, "accusative") { return "arm" } if str_eq(gram_case, "genitive") { return "arms" } if str_eq(gram_case, "dative") { return "armi" } return "armr" } if str_eq(gram_case, "nominative") { return "armar" } if str_eq(gram_case, "accusative") { return "arma" } if str_eq(gram_case, "genitive") { return "arma" } if str_eq(gram_case, "dative") { return "örmum" } return "armar" } // Generic strong masculine a-stem: use stripped stem + endings. // Dative plural umlaut is approximated as nominative plural (safe fallback). if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return stem + "r" } if str_eq(gram_case, "accusative") { return stem } if str_eq(gram_case, "genitive") { return stem + "s" } if str_eq(gram_case, "dative") { return stem + "i" } return stem + "r" } if str_eq(gram_case, "nominative") { return stem + "ar" } 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 + "um" } return stem + "ar" } // ── Strong feminine ō-stem declension ("gör" — gear) ───────────────────────── // // ō-stem feminines have a distinct set of endings. The stem is the nominative // singular form itself (no case suffix in nom sg for this class, though some // nouns add -ar in nom/acc pl). // // Singular: nom gör acc görvar gen görvar dat görvi // Plural: nom görvar acc görvar gen görva dat görvum fn non_decline_fem(noun: String, gram_case: String, number: String) -> String { // Use "gör" as the fully specified exemplar. if str_eq(noun, "gör") { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "gör" } if str_eq(gram_case, "accusative") { return "görvar" } if str_eq(gram_case, "genitive") { return "görvar" } if str_eq(gram_case, "dative") { return "görvi" } return "gör" } if str_eq(gram_case, "nominative") { return "görvar" } if str_eq(gram_case, "accusative") { return "görvar" } if str_eq(gram_case, "genitive") { return "görva" } if str_eq(gram_case, "dative") { return "görvum" } return "görvar" } // Generic ō-stem feminine: noun base + endings (approximate). if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun } if str_eq(gram_case, "accusative") { return noun + "var" } if str_eq(gram_case, "genitive") { return noun + "var" } if str_eq(gram_case, "dative") { return noun + "vi" } return noun } if str_eq(gram_case, "nominative") { return noun + "var" } if str_eq(gram_case, "accusative") { return noun + "var" } if str_eq(gram_case, "genitive") { return noun + "va" } if str_eq(gram_case, "dative") { return noun + "vum" } return noun + "var" } // ── Strong neuter a-stem declension ("land" — land) ────────────────────────── // // Neuter a-stems show no ending in nom/acc sg and u-umlaut in the dative plural. // // Singular: nom land acc land gen lands dat landi // Plural: nom lönd acc lönd gen landa dat löndum fn non_decline_neut(noun: String, gram_case: String, number: String) -> String { if str_eq(noun, "land") { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "land" } if str_eq(gram_case, "accusative") { return "land" } if str_eq(gram_case, "genitive") { return "lands" } if str_eq(gram_case, "dative") { return "landi" } return "land" } if str_eq(gram_case, "nominative") { return "lönd" } if str_eq(gram_case, "accusative") { return "lönd" } if str_eq(gram_case, "genitive") { return "landa" } if str_eq(gram_case, "dative") { return "löndum" } return "lönd" } // Generic strong neuter a-stem (no umlaut approximated — falls back to base). 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 + "s" } if str_eq(gram_case, "dative") { return noun + "i" } return noun } 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 } // ── Gender detection heuristic ───────────────────────────────────────────────── // // Old Norse gender must ideally be supplied by the lexicon. As a heuristic: // ends in -r (after removing) and root looks consonant-final -> masculine // ends in no case suffix (just the root vowel) -> try feminine // monosyllabic root without final r -> neuter // // The module uses a simplified approach: known exemplars are routed to their // specific paradigm; everything else defaults to strong masculine. fn non_detect_gender(noun: String) -> String { if str_eq(noun, "land") { return "neuter" } if str_eq(noun, "gör") { return "feminine" } // Default: masculine return "masculine" } // ── non_decline: main declension entry point ────────────────────────────────── // // noun: nominative singular Old Norse noun (e.g. "armr", "land", "gör") // gram_case: "nominative" | "accusative" | "genitive" | "dative" // number: "singular" | "plural" // // Returns the inflected form. Falls back to nominative singular on unknown input. fn non_decline(noun: String, gram_case: String, number: String) -> String { let gender: String = non_detect_gender(noun) if str_eq(gender, "masculine") { return non_decline_masc(noun, gram_case, number) } if str_eq(gender, "feminine") { return non_decline_fem(noun, gram_case, number) } if str_eq(gender, "neuter") { return non_decline_neut(noun, gram_case, number) } // Fallback return noun } // ── Definite suffix table ────────────────────────────────────────────────────── // // Old Norse expresses definiteness via a suffix enclitic attached to the end of // the inflected noun form (not a separate article). The suffix agrees with gender // and case. // // Masculine singular: nom -inn gen -ins dat -inum acc -inn // Neuter singular: nom -it gen -ins dat -inu acc -it // Feminine singular: nom -in gen -innar dat -inni acc -ina // Plural (all genders): nom/acc -inir (masc), -in (neut), -inar (fem) // — simplified to -in for plural in this module. fn non_def_suffix_masc(gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "inn" } if str_eq(gram_case, "genitive") { return "ins" } if str_eq(gram_case, "dative") { return "inum" } if str_eq(gram_case, "accusative") { return "inn" } return "inn" } // plural if str_eq(gram_case, "nominative") { return "inir" } if str_eq(gram_case, "accusative") { return "ina" } if str_eq(gram_case, "genitive") { return "anna" } if str_eq(gram_case, "dative") { return "unum" } return "inir" } fn non_def_suffix_neut(gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "it" } if str_eq(gram_case, "genitive") { return "ins" } if str_eq(gram_case, "dative") { return "inu" } if str_eq(gram_case, "accusative") { return "it" } return "it" } // plural if str_eq(gram_case, "nominative") { return "in" } if str_eq(gram_case, "accusative") { return "in" } if str_eq(gram_case, "genitive") { return "anna" } if str_eq(gram_case, "dative") { return "unum" } return "in" } fn non_def_suffix_fem(gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return "in" } if str_eq(gram_case, "genitive") { return "innar" } if str_eq(gram_case, "dative") { return "inni" } if str_eq(gram_case, "accusative") { return "ina" } return "in" } // plural if str_eq(gram_case, "nominative") { return "inar" } if str_eq(gram_case, "accusative") { return "inar" } if str_eq(gram_case, "genitive") { return "anna" } if str_eq(gram_case, "dative") { return "innar" } return "inar" } // ── non_noun_phrase: noun phrase builder ────────────────────────────────────── // // Old Norse definiteness is expressed through a suffix enclitic, not a separate // article. For indefinite nouns the declined form is returned as-is. // // noun: nominative singular Old Norse noun (e.g. "armr", "land") // gram_case: "nominative" | "accusative" | "genitive" | "dative" // number: "singular" | "plural" // definite: "true" | "false" (string comparison) // // Returns the complete noun phrase string. fn non_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String { let base: String = non_decline(noun, gram_case, number) if !str_eq(definite, "true") { return base } // Append the appropriate definite suffix. let gender: String = non_detect_gender(noun) if str_eq(gender, "masculine") { return base + non_def_suffix_masc(gram_case, number) } if str_eq(gender, "neuter") { return base + non_def_suffix_neut(gram_case, number) } if str_eq(gender, "feminine") { return base + non_def_suffix_fem(gram_case, number) } // Fallback: append generic -inn return base + "inn" }