// morphology-fr.el - French morphology for the NLG engine. // // Implements fusional French verb conjugation, noun pluralization, gender // inference, article agreement, and interrogative inversion. Designed as a // companion to morphology.el and called by the engine when the language // profile code is "fr". // // Verb tenses covered: present, future, imparfait, passé composé. // Persons: first/second/third × singular/plural. // Verb groups: -er (regular), -ir (regular finir-type), -re (regular vendre-type) // + a core set of common irregular verbs. // // Liaison / elision notes: // - le/la → l' before vowel-initial nouns (handled in fr_agree_article) // - est-ce que can precede any statement for yes/no questions (fr_question_inversion) // - Inversion inserts -t- between vowel-final verb form and 3s pronoun: parle-t-il // // Depends on: morphology.el (str_ends_with, str_slice, str_len helpers) // ── String helpers (local, matching morphology.el conventions) ──────────────── import "morphology.el" fn fr_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn fr_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 fr_str_last_char(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "" } return str_slice(s, n - 1, n) } fn fr_str_last2(s: String) -> String { let n: Int = str_len(s) if n < 2 { return s } return str_slice(s, n - 2, n) } fn fr_is_vowel_start(s: String) -> Bool { let n: Int = str_len(s) if n == 0 { return false } let c: String = str_slice(s, 0, 1) if str_eq(c, "a") { return true } if str_eq(c, "e") { return true } if str_eq(c, "é") { return true } if str_eq(c, "è") { return true } if str_eq(c, "ê") { return true } if str_eq(c, "i") { return true } if str_eq(c, "î") { return true } if str_eq(c, "o") { return true } if str_eq(c, "ô") { return true } if str_eq(c, "u") { return true } if str_eq(c, "û") { return true } if str_eq(c, "h") { return true } return false } // ── Verb group detection ────────────────────────────────────────────────────── // // Returns "er" | "ir" | "re" | "irregular". // Irregular detection is done by checking against a known list first; all other // verbs are classified by ending. fn fr_is_known_irregular(verb: String) -> Bool { if str_eq(verb, "être") { return true } if str_eq(verb, "avoir") { return true } if str_eq(verb, "aller") { return true } if str_eq(verb, "faire") { return true } if str_eq(verb, "pouvoir") { return true } if str_eq(verb, "vouloir") { return true } if str_eq(verb, "venir") { return true } if str_eq(verb, "dire") { return true } if str_eq(verb, "voir") { return true } if str_eq(verb, "prendre") { return true } if str_eq(verb, "mettre") { return true } if str_eq(verb, "savoir") { return true } return false } fn fr_verb_group(base: String) -> String { if fr_is_known_irregular(base) { return "irregular" } if fr_str_ends(base, "er") { return "er" } if fr_str_ends(base, "ir") { return "ir" } if fr_str_ends(base, "re") { return "re" } return "er" } fn fr_stem(base: String) -> String { return fr_str_drop_last(base, 2) } // ── Person/number slot index ────────────────────────────────────────────────── // // 0 = 1s (je), 1 = 2s (tu), 2 = 3s (il/elle), 3 = 1p (nous), 4 = 2p (vous), 5 = 3p (ils/elles) fn fr_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 } if str_eq(number, "singular") { return 2 } return 5 } // ── Irregular present tense ─────────────────────────────────────────────────── // // être: suis, es, est, sommes, êtes, sont // avoir: ai, as, a, avons, avez, ont // aller: vais, vas, va, allons, allez, vont // faire: fais, fais, fait, faisons, faites, font // pouvoir: peux, peux, peut, pouvons, pouvez, peuvent // vouloir: veux, veux, veut, voulons, voulez, veulent // venir: viens, viens, vient, venons, venez, viennent // dire: dis, dis, dit, disons, dites, disent // voir: vois, vois, voit, voyons, voyez, voient // prendre: prends, prends, prend, prenons, prenez, prennent // mettre: mets, mets, met, mettons, mettez, mettent // savoir: sais, sais, sait, savons, savez, savent fn fr_irregular_present(verb: String, person: String, number: String) -> String { let slot: Int = fr_slot(person, number) if str_eq(verb, "être") { if slot == 0 { return "suis" } if slot == 1 { return "es" } if slot == 2 { return "est" } if slot == 3 { return "sommes" } if slot == 4 { return "etes" } return "sont" } // ASCII alias used by morph_map_canonical — identical conjugation. if str_eq(verb, "etre") { if slot == 0 { return "suis" } if slot == 1 { return "es" } if slot == 2 { return "est" } if slot == 3 { return "sommes" } if slot == 4 { return "etes" } return "sont" } if str_eq(verb, "avoir") { 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" } if str_eq(verb, "aller") { if slot == 0 { return "vais" } if slot == 1 { return "vas" } if slot == 2 { return "va" } if slot == 3 { return "allons" } if slot == 4 { return "allez" } return "vont" } if str_eq(verb, "faire") { if slot == 0 { return "fais" } if slot == 1 { return "fais" } if slot == 2 { return "fait" } if slot == 3 { return "faisons" } if slot == 4 { return "faites" } return "font" } if str_eq(verb, "pouvoir") { if slot == 0 { return "peux" } if slot == 1 { return "peux" } if slot == 2 { return "peut" } if slot == 3 { return "pouvons" } if slot == 4 { return "pouvez" } return "peuvent" } if str_eq(verb, "vouloir") { if slot == 0 { return "veux" } if slot == 1 { return "veux" } if slot == 2 { return "veut" } if slot == 3 { return "voulons" } if slot == 4 { return "voulez" } return "veulent" } if str_eq(verb, "venir") { if slot == 0 { return "viens" } if slot == 1 { return "viens" } if slot == 2 { return "vient" } if slot == 3 { return "venons" } if slot == 4 { return "venez" } return "viennent" } if str_eq(verb, "dire") { if slot == 0 { return "dis" } if slot == 1 { return "dis" } if slot == 2 { return "dit" } if slot == 3 { return "disons" } if slot == 4 { return "dites" } return "disent" } if str_eq(verb, "voir") { if slot == 0 { return "vois" } if slot == 1 { return "vois" } if slot == 2 { return "voit" } if slot == 3 { return "voyons" } if slot == 4 { return "voyez" } return "voient" } if str_eq(verb, "prendre") { if slot == 0 { return "prends" } if slot == 1 { return "prends" } if slot == 2 { return "prend" } if slot == 3 { return "prenons" } if slot == 4 { return "prenez" } return "prennent" } if str_eq(verb, "mettre") { if slot == 0 { return "mets" } if slot == 1 { return "mets" } if slot == 2 { return "met" } if slot == 3 { return "mettons" } if slot == 4 { return "mettez" } return "mettent" } if str_eq(verb, "savoir") { if slot == 0 { return "sais" } if slot == 1 { return "sais" } if slot == 2 { return "sait" } if slot == 3 { return "savons" } if slot == 4 { return "savez" } return "savent" } return "" } // ── Regular present tense ───────────────────────────────────────────────────── // // -er: -e, -es, -e, -ons, -ez, -ent // -ir: -is, -is, -it, -issons, -issez, -issent (finir-type; stem gets -iss- in plural) // -re: -s, -s, -(nothing), -ons, -ez, -ent fn fr_regular_present(stem: String, vgroup: String, slot: Int) -> String { if str_eq(vgroup, "er") { 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" } if str_eq(vgroup, "ir") { // finir-type: singular uses bare stem, plural uses stem + -iss- 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 + "issez" } return stem + "issent" } // -re (vendre-type) if slot == 0 { return stem + "s" } if slot == 1 { return stem + "s" } if slot == 2 { return stem } if slot == 3 { return stem + "ons" } if slot == 4 { return stem + "ez" } return stem + "ent" } // ── Regular future tense ────────────────────────────────────────────────────── // // Future is formed from the infinitive (minus silent -e for -re verbs) + endings: // -ai, -as, -a, -ons, -ez, -ont fn fr_future_stem(base: String, vgroup: String) -> String { // -re verbs drop the final -e before adding future endings if str_eq(vgroup, "re") { return fr_str_drop_last(base, 1) } return base } fn fr_regular_future(fstem: String, slot: Int) -> String { if slot == 0 { return fstem + "ai" } if slot == 1 { return fstem + "as" } if slot == 2 { return fstem + "a" } if slot == 3 { return fstem + "ons" } if slot == 4 { return fstem + "ez" } return fstem + "ont" } // ── Irregular future stems ──────────────────────────────────────────────────── // // Returns the irregular future stem, or "" if regular. fn fr_irregular_future_stem(verb: String) -> String { if str_eq(verb, "être") { return "ser" } if str_eq(verb, "avoir") { return "aur" } if str_eq(verb, "aller") { return "ir" } if str_eq(verb, "faire") { return "fer" } if str_eq(verb, "pouvoir") { return "pourr" } if str_eq(verb, "vouloir") { return "voudr" } if str_eq(verb, "venir") { return "viendr" } if str_eq(verb, "voir") { return "verr" } if str_eq(verb, "savoir") { return "saur" } return "" } // ── Regular imparfait ───────────────────────────────────────────────────────── // // Imparfait is formed from the nous-present stem (infinitive minus -er/-ir/-re, // then add -iss for -ir verbs in nous-form) + endings: // -ais, -ais, -ait, -ions, -iez, -aient // // For -er verbs: stem = infinitive minus -er // For -ir verbs: stem = infinitive minus -ir (bare stem, not -iss- — imparfait // uses the basic stem, unlike present plural which uses -iss-) // For -re verbs: stem = infinitive minus -re // Exception: être uses ét- as the imparfait stem. fn fr_imperfect_stem(base: String, vgroup: String) -> String { if str_eq(base, "être") { return "ét" } return fr_stem(base) } fn fr_regular_imperfect(istem: String, slot: Int) -> String { if slot == 0 { return istem + "ais" } if slot == 1 { return istem + "ais" } if slot == 2 { return istem + "ait" } if slot == 3 { return istem + "ions" } if slot == 4 { return istem + "iez" } return istem + "aient" } // ── Passé composé (past compound) ──────────────────────────────────────────── // // Passé composé = auxiliary (avoir or être) + past participle. // Most verbs use avoir; a core set of motion/state verbs use être. // // This function returns a two-word string "auxiliary participle". // The caller is responsible for agreement of the past participle when être is // used (feminine adds -e, plural adds -s); this function returns the masculine // singular participle unconditionally. fn fr_uses_etre(verb: String) -> Bool { if str_eq(verb, "aller") { return true } if str_eq(verb, "venir") { return true } if str_eq(verb, "partir") { return true } if str_eq(verb, "arriver") { return true } if str_eq(verb, "entrer") { return true } if str_eq(verb, "sortir") { return true } if str_eq(verb, "naître") { return true } if str_eq(verb, "mourir") { return true } if str_eq(verb, "rester") { return true } if str_eq(verb, "tomber") { return true } if str_eq(verb, "monter") { return true } if str_eq(verb, "descendre") { return true } if str_eq(verb, "rentrer") { return true } if str_eq(verb, "retourner") { return true } if str_eq(verb, "passer") { return true } return false } // Returns the past participle (masculine singular form). fn fr_past_participle(verb: String) -> String { // Irregular participles if str_eq(verb, "être") { return "été" } if str_eq(verb, "avoir") { return "eu" } if str_eq(verb, "aller") { return "allé" } if str_eq(verb, "faire") { return "fait" } if str_eq(verb, "pouvoir") { return "pu" } if str_eq(verb, "vouloir") { return "voulu" } if str_eq(verb, "venir") { return "venu" } if str_eq(verb, "dire") { return "dit" } if str_eq(verb, "voir") { return "vu" } if str_eq(verb, "prendre") { return "pris" } if str_eq(verb, "mettre") { return "mis" } if str_eq(verb, "savoir") { return "su" } if str_eq(verb, "naître") { return "né" } if str_eq(verb, "mourir") { return "mort" } // Regular participles by group let vgroup: String = fr_verb_group(verb) if str_eq(vgroup, "er") { return fr_str_drop_last(verb, 2) + "é" } if str_eq(vgroup, "ir") { return fr_str_drop_last(verb, 2) + "i" } // -re verbs: drop -re, add -u return fr_str_drop_last(verb, 2) + "u" } // Conjugates the avoir auxiliary in the present (for passé composé). fn fr_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" } // Conjugates the être auxiliary in the present (for passé composé). fn fr_etre_present(slot: Int) -> String { if slot == 0 { return "suis" } if slot == 1 { return "es" } if slot == 2 { return "est" } if slot == 3 { return "sommes" } if slot == 4 { return "êtes" } return "sont" } // ── Full conjugation entry point ────────────────────────────────────────────── // // fr_conjugate: conjugate a French verb. // // verb: French infinitive (e.g. "parler", "être", "venir") // tense: "present" | "future" | "imperfect" | "past" // (note: "past" returns the passé composé as "aux participle") // person: "first" | "second" | "third" // number: "singular" | "plural" fn fr_conjugate(verb: String, tense: String, person: String, number: String) -> String { let slot: Int = fr_slot(person, number) if str_eq(tense, "present") { let irreg: String = fr_irregular_present(verb, person, number) if !str_eq(irreg, "") { return irreg } let vgroup: String = fr_verb_group(verb) let stem: String = fr_stem(verb) return fr_regular_present(stem, vgroup, slot) } if str_eq(tense, "future") { let irreg_stem: String = fr_irregular_future_stem(verb) if !str_eq(irreg_stem, "") { return fr_regular_future(irreg_stem, slot) } let vgroup: String = fr_verb_group(verb) let fstem: String = fr_future_stem(verb, vgroup) return fr_regular_future(fstem, slot) } if str_eq(tense, "imperfect") { let vgroup: String = fr_verb_group(verb) let istem: String = fr_imperfect_stem(verb, vgroup) return fr_regular_imperfect(istem, slot) } if str_eq(tense, "past") { // Passé composé: auxiliary + past participle let pp: String = fr_past_participle(verb) if fr_uses_etre(verb) { let aux: String = fr_etre_present(slot) return aux + " " + pp } let aux: String = fr_avoir_present(slot) return aux + " " + pp } // Unknown tense: return infinitive unchanged return verb } // ── Noun gender inference ───────────────────────────────────────────────────── // // Returns "m" (masculine), "f" (feminine), or "unknown". // // Heuristics (common French patterns): // ends in -tion/-sion/-xion -> feminine (nation, passion, connexion) // ends in -ure -> feminine (voiture, culture) // ends in -ette -> feminine (omelette, cigarette) // ends in -eur (abstract) -> feminine (couleur, peur, chaleur) // ends in -eur (agent) -> masculine (acteur, serveur) — can't always distinguish // ends in -eur (no rule) -> try -teur → masculine (docteur, auteur) // ends in -ment -> masculine (sentiment, gouvernement) // ends in -age -> masculine (voyage, fromage) // ends in -isme -> masculine (socialisme) // ends in -eau -> masculine (tableau, gâteau) // ends in -er/-é -> masculine (boucher, café) // ends in -ée -> feminine (journée, idée) // ends in -ie -> feminine (philosophie, géographie) // ends in -ance/-ence -> feminine (chance, science) // ends in -té/-tié -> feminine (beauté, amitié) // ends in -ude -> feminine (attitude, solitude) // ends in -ade -> feminine (salade, promenade) // ends in -ette -> feminine (already covered) // ends in -e (generic) -> often feminine but not reliable; return "unknown" fn fr_gender(noun: String) -> String { // Feminine patterns (check more specific before general) if fr_str_ends(noun, "tion") { return "f" } if fr_str_ends(noun, "sion") { return "f" } if fr_str_ends(noun, "xion") { return "f" } if fr_str_ends(noun, "ure") { return "f" } if fr_str_ends(noun, "ette") { return "f" } if fr_str_ends(noun, "ance") { return "f" } if fr_str_ends(noun, "ence") { return "f" } if fr_str_ends(noun, "ité") { return "f" } if fr_str_ends(noun, "té") { return "f" } if fr_str_ends(noun, "tié") { return "f" } if fr_str_ends(noun, "ude") { return "f" } if fr_str_ends(noun, "ade") { return "f" } if fr_str_ends(noun, "ée") { return "f" } if fr_str_ends(noun, "ie") { return "f" } // Masculine patterns if fr_str_ends(noun, "ment") { return "m" } if fr_str_ends(noun, "age") { return "m" } if fr_str_ends(noun, "isme") { return "m" } if fr_str_ends(noun, "eau") { return "m" } if fr_str_ends(noun, "eur") { return "m" } if fr_str_ends(noun, "er") { return "m" } if fr_str_ends(noun, "é") { return "m" } return "unknown" } // ── Noun pluralization ──────────────────────────────────────────────────────── // // French plural rules: // already ends in -s, -x, or -z -> unchanged // ends in -eau -> add -x (bateau → bateaux) // ends in -eu -> add -x (jeu → jeux; bleu → bleus is exception) // ends in -al -> replace -al with -aux (animal → animaux) // ends in -ail (most) -> replace -ail with -aux (travail → travaux; bail) // otherwise -> add -s fn fr_invariant_plural(noun: String) -> String { // Words already ending in -s, -x, -z are unchanged in plural let last: String = fr_str_last_char(noun) if str_eq(last, "s") { return noun } if str_eq(last, "x") { return noun } if str_eq(last, "z") { return noun } return "" } fn fr_pluralize(noun: String) -> String { let inv: String = fr_invariant_plural(noun) if !str_eq(inv, "") { return inv } if fr_str_ends(noun, "eau") { return noun + "x" } if fr_str_ends(noun, "eu") { return noun + "x" } if fr_str_ends(noun, "al") { return fr_str_drop_last(noun, 2) + "aux" } if fr_str_ends(noun, "ail") { return fr_str_drop_last(noun, 3) + "aux" } return noun + "s" } // ── Article agreement ───────────────────────────────────────────────────────── // // fr_agree_article: return the correct French article for a noun. // // noun: the noun (used for gender inference and elision check) // definite: "true" for definite (le/la/l'/les), "false" for indefinite (un/une/des) // number: "singular" | "plural" // // Elision: le/la → l' before a vowel- or h-initial noun (handled here). fn fr_agree_article(noun: String, definite: String, number: String) -> String { let gender: String = fr_gender(noun) let is_plural: Bool = str_eq(number, "plural") let is_def: Bool = str_eq(definite, "true") let vowel_start: Bool = fr_is_vowel_start(noun) if is_def { if is_plural { return "les" } // singular if vowel_start { return "l'" } if str_eq(gender, "f") { return "la" } return "le" } // indefinite if is_plural { return "des" } if str_eq(gender, "f") { return "une" } return "un" } // ── Question inversion ──────────────────────────────────────────────────────── // // fr_question_inversion: form a yes/no question using subject-verb inversion. // // subject: pronoun string: "je" | "tu" | "il" | "elle" | "nous" | "vous" | "ils" | "elles" // verb_form: the conjugated verb form (e.g. "parle", "mange", "est") // // Rules: // - Verb and subject are joined with a hyphen: "parle-t-il ?" // - When the verb form ends in a vowel and the subject starts with a vowel // (il, elle, ils, elles), insert euphonic -t-: "parle-t-il ?" // - Je inversion is archaic; "est-ce que je ...?" is preferred — this function // generates "est-ce que je ?" for first-person singular. // - The result ends with " ?" fn fr_subject_starts_vowel(subject: String) -> Bool { if str_eq(subject, "il") { return true } if str_eq(subject, "elle") { return true } if str_eq(subject, "ils") { return true } if str_eq(subject, "elles") { return true } return false } fn fr_verb_ends_vowel(verb_form: String) -> Bool { let last: String = fr_str_last_char(verb_form) if str_eq(last, "a") { return true } if str_eq(last, "e") { return true } if str_eq(last, "é") { return true } if str_eq(last, "i") { return true } if str_eq(last, "o") { return true } if str_eq(last, "u") { return true } return false } fn fr_question_inversion(subject: String, verb_form: String) -> String { // First-person singular: use est-ce que construction if str_eq(subject, "je") { return "est-ce que je " + verb_form + " ?" } // Determine whether to insert -t- let need_t: Bool = false if fr_verb_ends_vowel(verb_form) { if fr_subject_starts_vowel(subject) { let need_t = true } } if need_t { return verb_form + "-t-" + subject + " ?" } return verb_form + "-" + subject + " ?" }