c2cd5e01e1
El SDK CI - dev / build-and-test (pull_request) Successful in 3m34s
elb.el: - Auto-detect Homebrew OpenSSL (-L$(brew --prefix openssl)/lib) so -lssl resolves on macOS without manual flags; no-op on Linux - Add -include elp-c-decls.h when present in out_dir: resolves undeclared cross-module calls in packages like ELP that lack explicit imports ELP source: - Add import "morphology.el" to all 29 language morphology modules - Add language module imports to morphology.el (all langs it dispatches to) These were missing since ELP was originally built as a monolithic unit
669 lines
24 KiB
EmacsLisp
669 lines
24 KiB
EmacsLisp
// 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
|
||
}
|