// morphology-fro.el - Old French morphology for the NLG engine. // // Implements Old French verb conjugation, noun declension, and the definite // article. Designed as a companion to morphology.el and called by the engine // when the language profile code is "fro". // // Language profile: code=fro, name=Old French, morph_type=fusional, // word_order=V2, question_strategy=inversion, script=latin, // family=romance. // // Historical note: Old French (ca. 900–1400 CE) is the ancestor of Modern // French. It diverged from Vulgar Latin and retained a two-case system — // nominative (cas sujet) and oblique (cas régime) — inherited ultimately from // Latin. By around 1300 CE the case distinction had largely collapsed in // spoken usage, surviving mainly in formal written registers until it // disappeared altogether. This file targets the core Old French period // (ca. 1000–1300). // // Two-case system (masculine nouns): // Singular: nominative stem + -s (li murs = the wall [subject]) // oblique stem (le mur = the wall [object]) // Plural: nominative stem (li mur = the walls [subject]) // oblique stem + -s (les murs = the walls [object]) // Feminine nouns show no case distinction throughout. // // Verb conjugation covered: // Tenses: present indicative, passé simple (past), future // Persons: first/second/third × singular/plural (slots 0-5) // Conjugations: // 1st (-er): present -e/-es/-e/-ons/-ez/-ent // passé simple -ai/-as/-a/-ames/-astes/-erent // future stem+rai/ras/ra/rons/rez/ront // 2nd (-ir): present -is/-is/-it/-issons/-issiez/-issent // passé simple -is/-is/-it/-imes/-istes/-irent // future stem+rai series // 3rd (-re): present stem/s/t/-ons/-ez/-ent // passé simple -is series (like 2nd) // future stem+rai series // Irregulars: estre (be), avoir (have), aler (go), venir (come), // faire (do/make) // Canonical map: "be" -> "estre" // // Noun declension covered: // Masculine: two-case (nom/obl) × sg/pl as above // Feminine: case-neutral, sg base / pl base + -s // Gender detection: -e ending -> feminine (heuristic), else masculine // // Article: // Definite masculine nom sg: li; obl sg: le; nom pl: li; obl pl: les // Definite feminine sg: la; pl: les // // Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with) // ── String helpers ───────────────────────────────────────────────────────────── import "morphology.el" fn fro_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn fro_drop(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } return str_slice(s, 0, len - n) } // ── Person/number slot ───────────────────────────────────────────────────────── // // Maps person × number to a 0-based paradigm index. // 0 = 1st singular (je) // 1 = 2nd singular (tu) // 2 = 3rd singular (il/ele) // 3 = 1st plural (nos) // 4 = 2nd plural (vos) // 5 = 3rd plural (il/eles) fn fro_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 person if str_eq(number, "singular") { return 2 } return 5 } // ── Canonical verb mapping ───────────────────────────────────────────────────── // // English semantic-layer labels are resolved to Old French dictionary infinitives // before conjugation. fn fro_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "estre" } if str_eq(verb, "have") { return "avoir" } if str_eq(verb, "go") { return "aler" } if str_eq(verb, "come") { return "venir" } if str_eq(verb, "do") { return "faire" } if str_eq(verb, "make") { return "faire" } if str_eq(verb, "say") { return "dire" } if str_eq(verb, "see") { return "veoir" } if str_eq(verb, "want") { return "vouloir" } if str_eq(verb, "can") { return "pooir" } return verb } // ── Irregular verb: estre (to be) ───────────────────────────────────────────── // // Suppletive paradigm — one of the most irregular verbs in Old French. // // Present indicative: // 1sg sui 2sg es 3sg est // 1pl somes 2pl estes 3pl sont // // Passé simple (past): // 1sg fui 2sg fus 3sg fu // 1pl fumes 2pl fustes 3pl furent // // Future (periphrastic, based on ester- stem): // 1sg esterai 2sg esteras 3sg estera // 1pl esterons 2pl esterez 3pl esteront fn fro_estre_present(slot: Int) -> String { if slot == 0 { return "sui" } if slot == 1 { return "es" } if slot == 2 { return "est" } if slot == 3 { return "somes" } if slot == 4 { return "estes" } return "sont" } fn fro_estre_past(slot: Int) -> String { if slot == 0 { return "fui" } if slot == 1 { return "fus" } if slot == 2 { return "fu" } if slot == 3 { return "fumes" } if slot == 4 { return "fustes" } return "furent" } fn fro_estre_future(slot: Int) -> String { if slot == 0 { return "esterai" } if slot == 1 { return "esteras" } if slot == 2 { return "estera" } if slot == 3 { return "esterons" } if slot == 4 { return "esterez" } return "esteront" } // ── Irregular verb: avoir (to have) ─────────────────────────────────────────── // // Present indicative: // 1sg ai 2sg as 3sg a // 1pl avons 2pl avez 3pl ont // // Passé simple: // 1sg oi 2sg os 3sg ot // 1pl eumes 2pl eustes 3pl orent // // Future: // 1sg avrai 2sg avras 3sg avra // 1pl avrons 2pl avrez 3pl avront fn fro_avoir_present(slot: Int) -> String { if slot == 0 { return "ai" } if slot == 1 { return "as" } if slot == 2 { return "a" } if slot == 3 { return "avons" } if slot == 4 { return "avez" } return "ont" } fn fro_avoir_past(slot: Int) -> String { if slot == 0 { return "oi" } if slot == 1 { return "os" } if slot == 2 { return "ot" } if slot == 3 { return "eumes" } if slot == 4 { return "eustes" } return "orent" } fn fro_avoir_future(slot: Int) -> String { if slot == 0 { return "avrai" } if slot == 1 { return "avras" } if slot == 2 { return "avra" } if slot == 3 { return "avrons" } if slot == 4 { return "avrez" } return "avront" } // ── Irregular verb: aler (to go) ────────────────────────────────────────────── // // Highly suppletive — present draws on Latin *vadere (vois- stem). // // Present indicative: // 1sg vois 2sg vas 3sg va // 1pl alons 2pl alez 3pl vont // // Passé simple (regular -er pattern on al-): // 1sg alai 2sg alas 3sg ala // 1pl alames 2pl alastes 3pl alerent // // Future (ir- stem, archaic): // 1sg irai 2sg iras 3sg ira // 1pl irons 2pl irez 3pl iront fn fro_aler_present(slot: Int) -> String { if slot == 0 { return "vois" } if slot == 1 { return "vas" } if slot == 2 { return "va" } if slot == 3 { return "alons" } if slot == 4 { return "alez" } return "vont" } fn fro_aler_past(slot: Int) -> String { if slot == 0 { return "alai" } if slot == 1 { return "alas" } if slot == 2 { return "ala" } if slot == 3 { return "alames" } if slot == 4 { return "alastes" } return "alerent" } fn fro_aler_future(slot: Int) -> String { if slot == 0 { return "irai" } if slot == 1 { return "iras" } if slot == 2 { return "ira" } if slot == 3 { return "irons" } if slot == 4 { return "irez" } return "iront" } // ── Irregular verb: venir (to come) ─────────────────────────────────────────── // // Present indicative (vien-/ven- alternation): // 1sg vieng 2sg viens 3sg vient // 1pl venons 2pl venez 3pl vienent // // Passé simple: // 1sg ving 2sg vins 3sg vint // 1pl vinsmes 2pl vinstes 3pl vindrent // // Future (venr- stem): // 1sg venrai 2sg venras 3sg venra // 1pl venrons 2pl venrez 3pl venront fn fro_venir_present(slot: Int) -> String { if slot == 0 { return "vieng" } if slot == 1 { return "viens" } if slot == 2 { return "vient" } if slot == 3 { return "venons" } if slot == 4 { return "venez" } return "vienent" } fn fro_venir_past(slot: Int) -> String { if slot == 0 { return "ving" } if slot == 1 { return "vins" } if slot == 2 { return "vint" } if slot == 3 { return "vinsmes" } if slot == 4 { return "vinstes" } return "vindrent" } fn fro_venir_future(slot: Int) -> String { if slot == 0 { return "venrai" } if slot == 1 { return "venras" } if slot == 2 { return "venra" } if slot == 3 { return "venrons" } if slot == 4 { return "venrez" } return "venront" } // ── Irregular verb: faire (to do/make) ──────────────────────────────────────── // // Present indicative (faz/fais- alternation): // 1sg faz 2sg fais 3sg fait // 1pl faisons 2pl faites 3pl font // // Passé simple: // 1sg fis 2sg fis 3sg fist // 1pl fimes 2pl fistes 3pl firent // // Future (fer- stem): // 1sg ferai 2sg feras 3sg fera // 1pl ferons 2pl ferez 3pl feront fn fro_faire_present(slot: Int) -> String { if slot == 0 { return "faz" } if slot == 1 { return "fais" } if slot == 2 { return "fait" } if slot == 3 { return "faisons" } if slot == 4 { return "faites" } return "font" } fn fro_faire_past(slot: Int) -> String { if slot == 0 { return "fis" } if slot == 1 { return "fis" } if slot == 2 { return "fist" } if slot == 3 { return "fimes" } if slot == 4 { return "fistes" } return "firent" } fn fro_faire_future(slot: Int) -> String { if slot == 0 { return "ferai" } if slot == 1 { return "feras" } if slot == 2 { return "fera" } if slot == 3 { return "ferons" } if slot == 4 { return "ferez" } return "feront" } // ── Conjugation class detection ──────────────────────────────────────────────── // // Old French verbs fall into three broad conjugation classes: // 1st conjugation: infinitive ends in -er (chanter, donner) // 2nd conjugation: infinitive ends in -ir (finir, choisir) // 3rd conjugation: infinitive ends in -re (vendre, rendre) // // Returns "1", "2", or "3". fn fro_verb_class(verb: String) -> String { if fro_str_ends(verb, "er") { return "1" } if fro_str_ends(verb, "ir") { return "2" } if fro_str_ends(verb, "re") { return "3" } return "1" } // fro_verb_stem: strip the infinitive suffix to expose the productive stem. // 1st (-er): drop 2 bytes // 2nd (-ir): drop 2 bytes // 3rd (-re): drop 2 bytes fn fro_verb_stem(verb: String, vclass: String) -> String { return fro_drop(verb, 2) } // ── 1st conjugation (-er): regular endings ──────────────────────────────────── // // Present indicative (stem + ending): // 1sg -e 2sg -es 3sg -e // 1pl -ons 2pl -ez 3pl -ent // // Passé simple: // 1sg -ai 2sg -as 3sg -a // 1pl -ames 2pl -astes 3pl -erent // // Future (infinitive is the base — drop -r then add endings): // Actually the future stem = infinitive minus final -r (chanter- -> chanterai) // 1sg -ai 2sg -as 3sg -a // 1pl -ons 2pl -ez 3pl -ont // Combined with chanterr-: chant+er+ai = chanterai; stem for future = infinitive + "a"... // Simpler: future base = fro_drop(verb, 1) i.e. drop final -r to keep the -e: // chanterai, chanteras, chantera, chanterons, chanterez, chanteront fn fro_conj1_present(stem: String, slot: Int) -> String { if slot == 0 { return stem + "e" } if slot == 1 { return stem + "es" } if slot == 2 { return stem + "e" } if slot == 3 { return stem + "ons" } if slot == 4 { return stem + "ez" } return stem + "ent" } fn fro_conj1_past(stem: String, slot: Int) -> String { if slot == 0 { return stem + "ai" } if slot == 1 { return stem + "as" } if slot == 2 { return stem + "a" } if slot == 3 { return stem + "ames" } if slot == 4 { return stem + "astes" } return stem + "erent" } fn fro_conj1_future(verb: String, slot: Int) -> String { // Future base = infinitive minus final -r (retains the -e): chanter -> chante- let base: String = fro_drop(verb, 1) if slot == 0 { return base + "rai" } if slot == 1 { return base + "ras" } if slot == 2 { return base + "ra" } if slot == 3 { return base + "rons" } if slot == 4 { return base + "rez" } return base + "ront" } // ── 2nd conjugation (-ir): regular endings ──────────────────────────────────── // // Present indicative uses an infix -iss- in pl forms (inchoative): // 1sg stem + -is 2sg stem + -is 3sg stem + -it // 1pl stem + -issons 2pl stem + -issiez 3pl stem + -issent // // Passé simple: // 1sg stem + -is 2sg stem + -is 3sg stem + -it // 1pl stem + -imes 2pl stem + -istes 3pl stem + -irent // // Future (infinitive minus final -r): // finir -> fini- -> finirai ... fn fro_conj2_present(stem: String, slot: Int) -> String { if slot == 0 { return stem + "is" } if slot == 1 { return stem + "is" } if slot == 2 { return stem + "it" } if slot == 3 { return stem + "issons" } if slot == 4 { return stem + "issiez" } return stem + "issent" } fn fro_conj2_past(stem: String, slot: Int) -> String { if slot == 0 { return stem + "is" } if slot == 1 { return stem + "is" } if slot == 2 { return stem + "it" } if slot == 3 { return stem + "imes" } if slot == 4 { return stem + "istes" } return stem + "irent" } fn fro_conj2_future(verb: String, slot: Int) -> String { let base: String = fro_drop(verb, 1) if slot == 0 { return base + "rai" } if slot == 1 { return base + "ras" } if slot == 2 { return base + "ra" } if slot == 3 { return base + "rons" } if slot == 4 { return base + "rez" } return base + "ront" } // ── 3rd conjugation (-re): regular endings ──────────────────────────────────── // // Present indicative: // 1sg stem (no ending) 2sg stem + -s 3sg stem + -t // 1pl stem + -ons 2pl stem + -ez 3pl stem + -ent // // Passé simple (same endings as 2nd conj): // 1sg stem + -is 2sg stem + -is 3sg stem + -it // 1pl stem + -imes 2pl stem + -istes 3pl stem + -irent // // Future (-re verbs drop -e before adding endings): // vendre -> vendr- -> vendrai ... fn fro_conj3_present(stem: String, slot: Int) -> String { if slot == 0 { return stem } if slot == 1 { return stem + "s" } if slot == 2 { return stem + "t" } if slot == 3 { return stem + "ons" } if slot == 4 { return stem + "ez" } return stem + "ent" } fn fro_conj3_past(stem: String, slot: Int) -> String { if slot == 0 { return stem + "is" } if slot == 1 { return stem + "is" } if slot == 2 { return stem + "it" } if slot == 3 { return stem + "imes" } if slot == 4 { return stem + "istes" } return stem + "irent" } fn fro_conj3_future(verb: String, slot: Int) -> String { // Drop -re (2 bytes) to get consonant-final stem, then add -rai etc. let base: String = fro_drop(verb, 2) if slot == 0 { return base + "rai" } if slot == 1 { return base + "ras" } if slot == 2 { return base + "ra" } if slot == 3 { return base + "rons" } if slot == 4 { return base + "rez" } return base + "ront" } // ── fro_conjugate: main conjugation entry point ─────────────────────────────── // // verb: Old French infinitive (e.g. "chanter", "finir", "vendre") // or English canonical label ("be", "go", "have", ...) // tense: "present" | "past" | "future" // person: "first" | "second" | "third" // number: "singular" | "plural" // // Returns the inflected form. Unknown tenses fall back to the infinitive. fn fro_conjugate(verb: String, tense: String, person: String, number: String) -> String { let v: String = fro_map_canonical(verb) let slot: Int = fro_slot(person, number) // ── Irregular: estre (to be) ────────────────────────────────────────────── if str_eq(v, "estre") { if str_eq(tense, "present") { return fro_estre_present(slot) } if str_eq(tense, "past") { return fro_estre_past(slot) } if str_eq(tense, "future") { return fro_estre_future(slot) } return v } // ── Irregular: avoir (to have) ──────────────────────────────────────────── if str_eq(v, "avoir") { if str_eq(tense, "present") { return fro_avoir_present(slot) } if str_eq(tense, "past") { return fro_avoir_past(slot) } if str_eq(tense, "future") { return fro_avoir_future(slot) } return v } // ── Irregular: aler (to go) ─────────────────────────────────────────────── if str_eq(v, "aler") { if str_eq(tense, "present") { return fro_aler_present(slot) } if str_eq(tense, "past") { return fro_aler_past(slot) } if str_eq(tense, "future") { return fro_aler_future(slot) } return v } // ── Irregular: venir (to come) ──────────────────────────────────────────── if str_eq(v, "venir") { if str_eq(tense, "present") { return fro_venir_present(slot) } if str_eq(tense, "past") { return fro_venir_past(slot) } if str_eq(tense, "future") { return fro_venir_future(slot) } return v } // ── Irregular: faire (to do/make) ───────────────────────────────────────── if str_eq(v, "faire") { if str_eq(tense, "present") { return fro_faire_present(slot) } if str_eq(tense, "past") { return fro_faire_past(slot) } if str_eq(tense, "future") { return fro_faire_future(slot) } return v } // ── Regular conjugations ────────────────────────────────────────────────── let vclass: String = fro_verb_class(v) let stem: String = fro_verb_stem(v, vclass) if str_eq(vclass, "1") { if str_eq(tense, "present") { return fro_conj1_present(stem, slot) } if str_eq(tense, "past") { return fro_conj1_past(stem, slot) } if str_eq(tense, "future") { return fro_conj1_future(v, slot) } return v } if str_eq(vclass, "2") { if str_eq(tense, "present") { return fro_conj2_present(stem, slot) } if str_eq(tense, "past") { return fro_conj2_past(stem, slot) } if str_eq(tense, "future") { return fro_conj2_future(v, slot) } return v } if str_eq(vclass, "3") { if str_eq(tense, "present") { return fro_conj3_present(stem, slot) } if str_eq(tense, "past") { return fro_conj3_past(stem, slot) } if str_eq(tense, "future") { return fro_conj3_future(v, slot) } return v } // Final fallback: return the infinitive return v } // ── Gender detection ─────────────────────────────────────────────────────────── // // Heuristic gender detection from the citation form (nominative singular). // Old French gender was inherited from Latin with only two genders surviving: // masculine and feminine (neuter collapsed into masculine/feminine by Vulgar Latin). // // Heuristic: citation form ending in -e -> feminine; otherwise -> masculine. // This is imperfect but covers the majority of common nouns. fn fro_gender(noun: String) -> String { if fro_str_ends(noun, "e") { return "fem" } return "masc" } // ── Noun declension ──────────────────────────────────────────────────────────── // // Old French two-case system: // // Masculine (e.g. mur — wall, stem = mur): // Singular nominative (cas sujet): murs (stem + -s) // Singular oblique (cas régime): mur (stem) // Plural nominative: mur (stem) // Plural oblique: murs (stem + -s) // // This pattern means nominative markers invert relative to Latin: // the nominative takes -s in singular but loses it in plural, // while the oblique works the other way round. // // Feminine (e.g. dame — lady): // No case distinction throughout. // Singular: dame (citation form) // Plural: dames (citation + -s) // // gram_case: "nominative" | "oblique" // number: "singular" | "plural" fn fro_decline_masc(noun: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { if str_eq(gram_case, "nominative") { return noun + "s" } // oblique singular: bare stem return noun } // plural if str_eq(gram_case, "nominative") { return noun } return noun + "s" } fn fro_decline_fem(noun: String, number: String) -> String { if str_eq(number, "singular") { return noun } return noun + "s" } // fro_decline: main declension entry point. // // noun: citation form (nominative/oblique singular — typically the bare stem) // gram_case: "nominative" | "oblique" // number: "singular" | "plural" fn fro_decline(noun: String, gram_case: String, number: String) -> String { let gender: String = fro_gender(noun) if str_eq(gender, "masc") { return fro_decline_masc(noun, gram_case, number) } return fro_decline_fem(noun, number) } // ── Definite article ────────────────────────────────────────────────────────── // // Old French definite articles are case- and gender-sensitive: // // Masculine: // nom sg: li obl sg: le // nom pl: li obl pl: les // // Feminine: // sg: la pl: les // // gender: "masc" | "fem" // gram_case: "nominative" | "oblique" // number: "singular" | "plural" fn fro_article(gender: String, gram_case: String, number: String) -> String { if str_eq(gender, "masc") { if str_eq(number, "plural") { return "les" } if str_eq(gram_case, "nominative") { return "li" } return "le" } // feminine if str_eq(number, "plural") { return "les" } return "la" } // ── fro_noun_phrase: noun phrase builder ────────────────────────────────────── // // Assembles a declined noun with an optional definite article. // // noun: citation form (nominative/oblique singular stem) // gram_case: "nominative" | "oblique" // number: "singular" | "plural" // definite: "true" to prepend the definite article; any other value omits it fn fro_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String { let gender: String = fro_gender(noun) let declined: String = fro_decline(noun, gram_case, number) if str_eq(definite, "true") { let art: String = fro_article(gender, gram_case, number) return art + " " + declined } return declined }