Rename: nlg → elp (Engram Language Protocol)
This commit is contained in:
+477
-344
@@ -1,9 +1,37 @@
|
||||
// morphology.el - English morphology: pluralization, verb conjugation, agreement.
|
||||
// morphology.el - Morphology engine: inflection driven by language profile.
|
||||
//
|
||||
// Handles regular rules and the ~40 most common irregular forms.
|
||||
// Standalone: no dependencies on other NLG modules.
|
||||
// Strategy dispatch is based on the morph_type field of the language profile:
|
||||
//
|
||||
// "isolating" (zh, vi) - no inflection; base form is final form.
|
||||
// "agglutinative" (ja, fi, sw) - suffix chains; each feature adds a suffix.
|
||||
// "fusional" (en, de, ru) - endings encode multiple features at once;
|
||||
// table-driven per language.
|
||||
// "polysynthetic" (various) - complex verb complexes; returns base form,
|
||||
// full support deferred.
|
||||
//
|
||||
// Language-specific dispatch (added in 0.3.0):
|
||||
// morph_conjugate and morph_pluralize first check the profile language code
|
||||
// and delegate to the corresponding language module when one is available:
|
||||
// es -> morphology-es.el (Spanish)
|
||||
// fr -> morphology-fr.el (French)
|
||||
// de -> morphology-de.el (German)
|
||||
// ru -> morphology-ru.el (Russian)
|
||||
// ja -> morphology-ja.el (Japanese)
|
||||
// fi -> morphology-fi.el (Finnish)
|
||||
// ar -> morphology-ar.el (Arabic)
|
||||
// hi -> morphology-hi.el (Hindi)
|
||||
// sw -> morphology-sw.el (Swahili)
|
||||
// Unknown codes fall through to the morph_type strategy dispatch below.
|
||||
//
|
||||
// For English (fusional, code="en") the existing rules are preserved and
|
||||
// routed through the engine interface rather than exposed as the sole path.
|
||||
//
|
||||
// Depends on: language-profile
|
||||
// Language modules (loaded after this file, depend on this file):
|
||||
// morphology-es, morphology-fr, morphology-de, morphology-ru, morphology-ja,
|
||||
// morphology-fi, morphology-ar, morphology-hi, morphology-sw
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
// ── String helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
fn str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
@@ -50,41 +78,91 @@ fn is_vowel(c: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ── Irregular noun plurals ────────────────────────────────────────────────────
|
||||
// ── Suffix application ────────────────────────────────────────────────────────
|
||||
//
|
||||
// Apply a suffix to a base form with basic phonological adjustments.
|
||||
// Used by the agglutinative path and the fusional helper functions.
|
||||
//
|
||||
// Rules applied (in order):
|
||||
// base ends in "-e" and suffix starts with a vowel -> drop the trailing "e"
|
||||
// base ends in CVC and suffix starts with a vowel -> double final consonant
|
||||
// default: concatenate
|
||||
|
||||
fn irregular_plural(word: String) -> String {
|
||||
// Truly irregular
|
||||
if str_eq(word, "child") { return "children" }
|
||||
if str_eq(word, "man") { return "men" }
|
||||
if str_eq(word, "woman") { return "women" }
|
||||
if str_eq(word, "tooth") { return "teeth" }
|
||||
if str_eq(word, "foot") { return "feet" }
|
||||
if str_eq(word, "goose") { return "geese" }
|
||||
if str_eq(word, "mouse") { return "mice" }
|
||||
if str_eq(word, "louse") { return "lice" }
|
||||
if str_eq(word, "ox") { return "oxen" }
|
||||
if str_eq(word, "person") { return "people" }
|
||||
if str_eq(word, "leaf") { return "leaves" }
|
||||
if str_eq(word, "loaf") { return "loaves" }
|
||||
if str_eq(word, "wolf") { return "wolves" }
|
||||
if str_eq(word, "life") { return "lives" }
|
||||
if str_eq(word, "knife") { return "knives" }
|
||||
if str_eq(word, "wife") { return "wives" }
|
||||
if str_eq(word, "half") { return "halves" }
|
||||
if str_eq(word, "self") { return "selves" }
|
||||
if str_eq(word, "elf") { return "elves" }
|
||||
if str_eq(word, "shelf") { return "shelves" }
|
||||
// Zero-plural (same singular and plural)
|
||||
if str_eq(word, "fish") { return "fish" }
|
||||
if str_eq(word, "sheep") { return "sheep" }
|
||||
if str_eq(word, "deer") { return "deer" }
|
||||
if str_eq(word, "moose") { return "moose" }
|
||||
if str_eq(word, "series") { return "series" }
|
||||
if str_eq(word, "species") { return "species" }
|
||||
fn morph_apply_suffix(base: String, suffix: String) -> String {
|
||||
if str_eq(suffix, "") {
|
||||
return base
|
||||
}
|
||||
let suf_start: String = str_slice(suffix, 0, 1)
|
||||
let suf_starts_vowel: Bool = is_vowel(suf_start)
|
||||
|
||||
// Drop trailing silent -e before a vowel-initial suffix
|
||||
if suf_starts_vowel {
|
||||
if str_ends(base, "e") {
|
||||
if !str_ends(base, "ee") {
|
||||
return str_drop_last(base, 1) + suffix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CVC doubling before a vowel-initial suffix
|
||||
if suf_starts_vowel {
|
||||
let n: Int = str_len(base)
|
||||
if n >= 3 {
|
||||
let c3: String = str_slice(base, n - 3, n - 2)
|
||||
let c2: String = str_slice(base, n - 2, n - 1)
|
||||
let c1: String = str_slice(base, n - 1, n)
|
||||
if !is_vowel(c3) {
|
||||
if is_vowel(c2) {
|
||||
if !is_vowel(c1) {
|
||||
if !str_eq(c1, "w") {
|
||||
if !str_eq(c1, "x") {
|
||||
if !str_eq(c1, "y") {
|
||||
return base + c1 + suffix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base + suffix
|
||||
}
|
||||
|
||||
// ── English irregular tables ──────────────────────────────────────────────────
|
||||
|
||||
fn en_irregular_plural(word: String) -> String {
|
||||
if str_eq(word, "child") { return "children" }
|
||||
if str_eq(word, "man") { return "men" }
|
||||
if str_eq(word, "woman") { return "women" }
|
||||
if str_eq(word, "tooth") { return "teeth" }
|
||||
if str_eq(word, "foot") { return "feet" }
|
||||
if str_eq(word, "goose") { return "geese" }
|
||||
if str_eq(word, "mouse") { return "mice" }
|
||||
if str_eq(word, "louse") { return "lice" }
|
||||
if str_eq(word, "ox") { return "oxen" }
|
||||
if str_eq(word, "person") { return "people" }
|
||||
if str_eq(word, "leaf") { return "leaves" }
|
||||
if str_eq(word, "loaf") { return "loaves" }
|
||||
if str_eq(word, "wolf") { return "wolves" }
|
||||
if str_eq(word, "life") { return "lives" }
|
||||
if str_eq(word, "knife") { return "knives" }
|
||||
if str_eq(word, "wife") { return "wives" }
|
||||
if str_eq(word, "half") { return "halves" }
|
||||
if str_eq(word, "self") { return "selves" }
|
||||
if str_eq(word, "elf") { return "elves" }
|
||||
if str_eq(word, "shelf") { return "shelves" }
|
||||
if str_eq(word, "fish") { return "fish" }
|
||||
if str_eq(word, "sheep") { return "sheep" }
|
||||
if str_eq(word, "deer") { return "deer" }
|
||||
if str_eq(word, "moose") { return "moose" }
|
||||
if str_eq(word, "series") { return "series" }
|
||||
if str_eq(word, "species") { return "species" }
|
||||
return ""
|
||||
}
|
||||
|
||||
fn irregular_singular(word: String) -> String {
|
||||
fn en_irregular_singular(word: String) -> String {
|
||||
if str_eq(word, "children") { return "child" }
|
||||
if str_eq(word, "men") { return "man" }
|
||||
if str_eq(word, "women") { return "woman" }
|
||||
@@ -113,278 +191,65 @@ fn irregular_singular(word: String) -> String {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── Noun pluralization ────────────────────────────────────────────────────────
|
||||
|
||||
fn pluralize(singular: String) -> String {
|
||||
// Check irregulars first
|
||||
let irreg: String = irregular_plural(singular)
|
||||
if !str_eq(irreg, "") {
|
||||
return irreg
|
||||
}
|
||||
|
||||
// -s, -x, -z, -ch, -sh -> +es
|
||||
if str_ends(singular, "s") { return singular + "es" }
|
||||
if str_ends(singular, "x") { return singular + "es" }
|
||||
if str_ends(singular, "z") { return singular + "es" }
|
||||
if str_ends(singular, "ch") { return singular + "es" }
|
||||
if str_ends(singular, "sh") { return singular + "es" }
|
||||
|
||||
// consonant + y -> -y +ies
|
||||
let last: String = str_last_char(singular)
|
||||
if str_eq(last, "y") {
|
||||
let prev: String = str_drop_last(singular, 1)
|
||||
let prev_last: String = str_last_char(prev)
|
||||
if !is_vowel(prev_last) {
|
||||
return prev + "ies"
|
||||
}
|
||||
}
|
||||
|
||||
// -fe -> -ves
|
||||
if str_ends(singular, "fe") {
|
||||
return str_drop_last(singular, 2) + "ves"
|
||||
}
|
||||
|
||||
// -f -> -ves (for common words; not universal)
|
||||
// skip - too many exceptions (roof, belief, cliff)
|
||||
|
||||
// Default: +s
|
||||
return singular + "s"
|
||||
}
|
||||
|
||||
fn singularize(plural: String) -> String {
|
||||
// Check irregulars first
|
||||
let irreg: String = irregular_singular(plural)
|
||||
if !str_eq(irreg, "") {
|
||||
return irreg
|
||||
}
|
||||
|
||||
// -ies -> -y
|
||||
if str_ends(plural, "ies") {
|
||||
return str_drop_last(plural, 3) + "y"
|
||||
}
|
||||
|
||||
// -ves -> -f or -fe (best effort: try -fe first, else -f)
|
||||
if str_ends(plural, "ves") {
|
||||
// A few specific ones we know; otherwise default to -f
|
||||
let stem: String = str_drop_last(plural, 3)
|
||||
let last_stem: String = str_last_char(stem)
|
||||
if str_eq(last_stem, "i") {
|
||||
// lives -> life, knives -> knife, wives -> wife
|
||||
return stem + "fe"
|
||||
}
|
||||
return stem + "f"
|
||||
}
|
||||
|
||||
// -es (for -s, -x, -z, -ch, -sh endings)
|
||||
if str_ends(plural, "ches") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "shes") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "xes") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "zes") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "ses") { return str_drop_last(plural, 2) }
|
||||
|
||||
// Default: -s
|
||||
if str_ends(plural, "s") {
|
||||
return str_drop_last(plural, 1)
|
||||
}
|
||||
|
||||
return plural
|
||||
}
|
||||
|
||||
// ── Irregular verb forms ──────────────────────────────────────────────────────
|
||||
// Returns a list: [base, 3sg-present, past, past-participle, gerund]
|
||||
|
||||
fn irregular_verb(base: String) -> [String] {
|
||||
// Returns [base, 3sg-present, past, past-participle, gerund] for English
|
||||
// irregular verbs, or an empty list if the verb is regular.
|
||||
fn en_irregular_verb(base: String) -> [String] {
|
||||
let empty: [String] = []
|
||||
if str_eq(base, "be") {
|
||||
let r: [String] = ["be", "is", "was", "been", "being"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "have") {
|
||||
let r: [String] = ["have", "has", "had", "had", "having"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "do") {
|
||||
let r: [String] = ["do", "does", "did", "done", "doing"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "go") {
|
||||
let r: [String] = ["go", "goes", "went", "gone", "going"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "say") {
|
||||
let r: [String] = ["say", "says", "said", "said", "saying"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "make") {
|
||||
let r: [String] = ["make", "makes", "made", "made", "making"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "know") {
|
||||
let r: [String] = ["know", "knows", "knew", "known", "knowing"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "take") {
|
||||
let r: [String] = ["take", "takes", "took", "taken", "taking"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "see") {
|
||||
let r: [String] = ["see", "sees", "saw", "seen", "seeing"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "come") {
|
||||
let r: [String] = ["come", "comes", "came", "come", "coming"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "think") {
|
||||
let r: [String] = ["think", "thinks", "thought", "thought", "thinking"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "get") {
|
||||
let r: [String] = ["get", "gets", "got", "gotten", "getting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "give") {
|
||||
let r: [String] = ["give", "gives", "gave", "given", "giving"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "find") {
|
||||
let r: [String] = ["find", "finds", "found", "found", "finding"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "tell") {
|
||||
let r: [String] = ["tell", "tells", "told", "told", "telling"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "become") {
|
||||
let r: [String] = ["become", "becomes", "became", "become", "becoming"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "leave") {
|
||||
let r: [String] = ["leave", "leaves", "left", "left", "leaving"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "feel") {
|
||||
let r: [String] = ["feel", "feels", "felt", "felt", "feeling"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "put") {
|
||||
let r: [String] = ["put", "puts", "put", "put", "putting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "bring") {
|
||||
let r: [String] = ["bring", "brings", "brought", "brought", "bringing"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "begin") {
|
||||
let r: [String] = ["begin", "begins", "began", "begun", "beginning"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "keep") {
|
||||
let r: [String] = ["keep", "keeps", "kept", "kept", "keeping"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "hold") {
|
||||
let r: [String] = ["hold", "holds", "held", "held", "holding"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "write") {
|
||||
let r: [String] = ["write", "writes", "wrote", "written", "writing"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "stand") {
|
||||
let r: [String] = ["stand", "stands", "stood", "stood", "standing"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "hear") {
|
||||
let r: [String] = ["hear", "hears", "heard", "heard", "hearing"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "let") {
|
||||
let r: [String] = ["let", "lets", "let", "let", "letting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "run") {
|
||||
let r: [String] = ["run", "runs", "ran", "run", "running"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "meet") {
|
||||
let r: [String] = ["meet", "meets", "met", "met", "meeting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "sit") {
|
||||
let r: [String] = ["sit", "sits", "sat", "sat", "sitting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "send") {
|
||||
let r: [String] = ["send", "sends", "sent", "sent", "sending"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "speak") {
|
||||
let r: [String] = ["speak", "speaks", "spoke", "spoken", "speaking"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "buy") {
|
||||
let r: [String] = ["buy", "buys", "bought", "bought", "buying"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "pay") {
|
||||
let r: [String] = ["pay", "pays", "paid", "paid", "paying"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "read") {
|
||||
let r: [String] = ["read", "reads", "read", "read", "reading"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "win") {
|
||||
let r: [String] = ["win", "wins", "won", "won", "winning"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "eat") {
|
||||
let r: [String] = ["eat", "eats", "ate", "eaten", "eating"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "fall") {
|
||||
let r: [String] = ["fall", "falls", "fell", "fallen", "falling"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "sleep") {
|
||||
let r: [String] = ["sleep", "sleeps", "slept", "slept", "sleeping"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "drive") {
|
||||
let r: [String] = ["drive", "drives", "drove", "driven", "driving"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "build") {
|
||||
let r: [String] = ["build", "builds", "built", "built", "building"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "cut") {
|
||||
let r: [String] = ["cut", "cuts", "cut", "cut", "cutting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "set") {
|
||||
let r: [String] = ["set", "sets", "set", "set", "setting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "hit") {
|
||||
let r: [String] = ["hit", "hits", "hit", "hit", "hitting"]
|
||||
return r
|
||||
}
|
||||
if str_eq(base, "be") { let r: [String] = ["be", "is", "was", "been", "being"]; return r }
|
||||
if str_eq(base, "have") { let r: [String] = ["have", "has", "had", "had", "having"]; return r }
|
||||
if str_eq(base, "do") { let r: [String] = ["do", "does", "did", "done", "doing"]; return r }
|
||||
if str_eq(base, "go") { let r: [String] = ["go", "goes", "went", "gone", "going"]; return r }
|
||||
if str_eq(base, "say") { let r: [String] = ["say", "says", "said", "said", "saying"]; return r }
|
||||
if str_eq(base, "make") { let r: [String] = ["make", "makes", "made", "made", "making"]; return r }
|
||||
if str_eq(base, "know") { let r: [String] = ["know", "knows", "knew", "known", "knowing"]; return r }
|
||||
if str_eq(base, "take") { let r: [String] = ["take", "takes", "took", "taken", "taking"]; return r }
|
||||
if str_eq(base, "see") { let r: [String] = ["see", "sees", "saw", "seen", "seeing"]; return r }
|
||||
if str_eq(base, "come") { let r: [String] = ["come", "comes", "came", "come", "coming"]; return r }
|
||||
if str_eq(base, "think") { let r: [String] = ["think", "thinks","thought","thought", "thinking"]; return r }
|
||||
if str_eq(base, "get") { let r: [String] = ["get", "gets", "got", "gotten", "getting"]; return r }
|
||||
if str_eq(base, "give") { let r: [String] = ["give", "gives", "gave", "given", "giving"]; return r }
|
||||
if str_eq(base, "find") { let r: [String] = ["find", "finds", "found", "found", "finding"]; return r }
|
||||
if str_eq(base, "tell") { let r: [String] = ["tell", "tells", "told", "told", "telling"]; return r }
|
||||
if str_eq(base, "become") { let r: [String] = ["become", "becomes","became","become", "becoming"]; return r }
|
||||
if str_eq(base, "leave") { let r: [String] = ["leave", "leaves","left", "left", "leaving"]; return r }
|
||||
if str_eq(base, "feel") { let r: [String] = ["feel", "feels", "felt", "felt", "feeling"]; return r }
|
||||
if str_eq(base, "put") { let r: [String] = ["put", "puts", "put", "put", "putting"]; return r }
|
||||
if str_eq(base, "bring") { let r: [String] = ["bring", "brings","brought","brought", "bringing"]; return r }
|
||||
if str_eq(base, "begin") { let r: [String] = ["begin", "begins","began", "begun", "beginning"];return r }
|
||||
if str_eq(base, "keep") { let r: [String] = ["keep", "keeps", "kept", "kept", "keeping"]; return r }
|
||||
if str_eq(base, "hold") { let r: [String] = ["hold", "holds", "held", "held", "holding"]; return r }
|
||||
if str_eq(base, "write") { let r: [String] = ["write", "writes","wrote", "written", "writing"]; return r }
|
||||
if str_eq(base, "stand") { let r: [String] = ["stand", "stands","stood", "stood", "standing"]; return r }
|
||||
if str_eq(base, "hear") { let r: [String] = ["hear", "hears", "heard", "heard", "hearing"]; return r }
|
||||
if str_eq(base, "let") { let r: [String] = ["let", "lets", "let", "let", "letting"]; return r }
|
||||
if str_eq(base, "run") { let r: [String] = ["run", "runs", "ran", "run", "running"]; return r }
|
||||
if str_eq(base, "meet") { let r: [String] = ["meet", "meets", "met", "met", "meeting"]; return r }
|
||||
if str_eq(base, "sit") { let r: [String] = ["sit", "sits", "sat", "sat", "sitting"]; return r }
|
||||
if str_eq(base, "send") { let r: [String] = ["send", "sends", "sent", "sent", "sending"]; return r }
|
||||
if str_eq(base, "speak") { let r: [String] = ["speak", "speaks","spoke", "spoken", "speaking"]; return r }
|
||||
if str_eq(base, "buy") { let r: [String] = ["buy", "buys", "bought", "bought", "buying"]; return r }
|
||||
if str_eq(base, "pay") { let r: [String] = ["pay", "pays", "paid", "paid", "paying"]; return r }
|
||||
if str_eq(base, "read") { let r: [String] = ["read", "reads", "read", "read", "reading"]; return r }
|
||||
if str_eq(base, "win") { let r: [String] = ["win", "wins", "won", "won", "winning"]; return r }
|
||||
if str_eq(base, "eat") { let r: [String] = ["eat", "eats", "ate", "eaten", "eating"]; return r }
|
||||
if str_eq(base, "fall") { let r: [String] = ["fall", "falls", "fell", "fallen", "falling"]; return r }
|
||||
if str_eq(base, "sleep") { let r: [String] = ["sleep", "sleeps","slept", "slept", "sleeping"]; return r }
|
||||
if str_eq(base, "drive") { let r: [String] = ["drive", "drives","drove", "driven", "driving"]; return r }
|
||||
if str_eq(base, "build") { let r: [String] = ["build", "builds","built", "built", "building"]; return r }
|
||||
if str_eq(base, "cut") { let r: [String] = ["cut", "cuts", "cut", "cut", "cutting"]; return r }
|
||||
if str_eq(base, "set") { let r: [String] = ["set", "sets", "set", "set", "setting"]; return r }
|
||||
if str_eq(base, "hit") { let r: [String] = ["hit", "hits", "hit", "hit", "hitting"]; return r }
|
||||
return empty
|
||||
}
|
||||
|
||||
// ── Verb 3sg present form ─────────────────────────────────────────────────────
|
||||
// ── English regular inflection helpers ────────────────────────────────────────
|
||||
|
||||
fn verb_3sg(base: String) -> String {
|
||||
// -s, -x, -z, -ch, -sh -> +es
|
||||
fn en_verb_3sg(base: String) -> String {
|
||||
if str_ends(base, "s") { return base + "es" }
|
||||
if str_ends(base, "x") { return base + "es" }
|
||||
if str_ends(base, "z") { return base + "es" }
|
||||
if str_ends(base, "ch") { return base + "es" }
|
||||
if str_ends(base, "sh") { return base + "es" }
|
||||
|
||||
// consonant + y -> -y +ies
|
||||
let last: String = str_last_char(base)
|
||||
if str_eq(last, "y") {
|
||||
let prev: String = str_drop_last(base, 1)
|
||||
@@ -393,15 +258,10 @@ fn verb_3sg(base: String) -> String {
|
||||
return prev + "ies"
|
||||
}
|
||||
}
|
||||
|
||||
return base + "s"
|
||||
}
|
||||
|
||||
// ── Verb past tense form ──────────────────────────────────────────────────────
|
||||
|
||||
fn should_double_final(base: String) -> Bool {
|
||||
// CVC pattern: single syllable ending in consonant-vowel-consonant
|
||||
// and the final consonant is not w, x, y
|
||||
fn en_should_double_final(base: String) -> Bool {
|
||||
let n: Int = str_len(base)
|
||||
if n < 3 {
|
||||
return false
|
||||
@@ -425,13 +285,10 @@ fn should_double_final(base: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
fn verb_past(base: String) -> String {
|
||||
// Ends in -e: just add -d
|
||||
fn en_verb_past(base: String) -> String {
|
||||
if str_ends(base, "e") {
|
||||
return base + "d"
|
||||
}
|
||||
|
||||
// consonant + y -> -y +ied
|
||||
let last: String = str_last_char(base)
|
||||
if str_eq(last, "y") {
|
||||
let prev: String = str_drop_last(base, 1)
|
||||
@@ -440,53 +297,63 @@ fn verb_past(base: String) -> String {
|
||||
return prev + "ied"
|
||||
}
|
||||
}
|
||||
|
||||
// Double final consonant
|
||||
if should_double_final(base) {
|
||||
if en_should_double_final(base) {
|
||||
return base + last + "ed"
|
||||
}
|
||||
|
||||
return base + "ed"
|
||||
}
|
||||
|
||||
// ── Verb gerund form ──────────────────────────────────────────────────────────
|
||||
|
||||
fn verb_gerund(base: String) -> String {
|
||||
// Ends in -ie: drop -ie, add -ying
|
||||
fn en_verb_gerund(base: String) -> String {
|
||||
if str_ends(base, "ie") {
|
||||
return str_drop_last(base, 2) + "ying"
|
||||
}
|
||||
|
||||
// Ends in -e (not -ee): drop -e, add -ing
|
||||
if str_ends(base, "e") {
|
||||
if !str_ends(base, "ee") {
|
||||
return str_drop_last(base, 1) + "ing"
|
||||
}
|
||||
}
|
||||
|
||||
// Double final consonant (same CVC rule as past)
|
||||
let last: String = str_last_char(base)
|
||||
if should_double_final(base) {
|
||||
if en_should_double_final(base) {
|
||||
return base + last + "ing"
|
||||
}
|
||||
|
||||
return base + "ing"
|
||||
}
|
||||
|
||||
// ── Main verb conjugation ─────────────────────────────────────────────────────
|
||||
// English noun pluralization (regular).
|
||||
fn en_pluralize_regular(singular: String) -> String {
|
||||
if str_ends(singular, "s") { return singular + "es" }
|
||||
if str_ends(singular, "x") { return singular + "es" }
|
||||
if str_ends(singular, "z") { return singular + "es" }
|
||||
if str_ends(singular, "ch") { return singular + "es" }
|
||||
if str_ends(singular, "sh") { return singular + "es" }
|
||||
let last: String = str_last_char(singular)
|
||||
if str_eq(last, "y") {
|
||||
let prev: String = str_drop_last(singular, 1)
|
||||
let prev_last: String = str_last_char(prev)
|
||||
if !is_vowel(prev_last) {
|
||||
return prev + "ies"
|
||||
}
|
||||
}
|
||||
if str_ends(singular, "fe") {
|
||||
return str_drop_last(singular, 2) + "ves"
|
||||
}
|
||||
return singular + "s"
|
||||
}
|
||||
|
||||
// ── English verb conjugation ──────────────────────────────────────────────────
|
||||
//
|
||||
// tense: "present" | "past" | "future" | "perfect" | "progressive"
|
||||
// person: "first" | "second" | "third"
|
||||
// number: "singular" | "plural"
|
||||
|
||||
fn verb_form(base: String, tense: String, person: String, number: String) -> String {
|
||||
let irreg: [String] = irregular_verb(base)
|
||||
fn en_verb_form(base: String, tense: String, person: String, number: String) -> String {
|
||||
let irreg: [String] = en_irregular_verb(base)
|
||||
let is_irreg: Bool = false
|
||||
if native_list_len(irreg) > 0 {
|
||||
let is_irreg = true
|
||||
}
|
||||
|
||||
// ── "be" special-cased for all persons ───────────────────────────────────
|
||||
// "be" is fully irregular across all persons and tenses
|
||||
if str_eq(base, "be") {
|
||||
if str_eq(tense, "present") {
|
||||
if str_eq(number, "plural") { return "are" }
|
||||
@@ -505,53 +372,42 @@ fn verb_form(base: String, tense: String, person: String, number: String) -> Str
|
||||
return "be"
|
||||
}
|
||||
|
||||
// ── present ──────────────────────────────────────────────────────────────
|
||||
if str_eq(tense, "present") {
|
||||
if str_eq(person, "third") {
|
||||
if str_eq(number, "singular") {
|
||||
if is_irreg {
|
||||
return native_list_get(irreg, 1)
|
||||
}
|
||||
return verb_3sg(base)
|
||||
return en_verb_3sg(base)
|
||||
}
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
// ── past ─────────────────────────────────────────────────────────────────
|
||||
if str_eq(tense, "past") {
|
||||
if is_irreg {
|
||||
return native_list_get(irreg, 2)
|
||||
}
|
||||
return verb_past(base)
|
||||
if is_irreg { return native_list_get(irreg, 2) }
|
||||
return en_verb_past(base)
|
||||
}
|
||||
|
||||
// ── future ───────────────────────────────────────────────────────────────
|
||||
if str_eq(tense, "future") {
|
||||
return "will " + base
|
||||
}
|
||||
|
||||
// ── perfect (past participle) ─────────────────────────────────────────────
|
||||
if str_eq(tense, "perfect") {
|
||||
if is_irreg {
|
||||
return native_list_get(irreg, 3)
|
||||
}
|
||||
return verb_past(base)
|
||||
if is_irreg { return native_list_get(irreg, 3) }
|
||||
return en_verb_past(base)
|
||||
}
|
||||
|
||||
// ── progressive (gerund/present participle) ───────────────────────────────
|
||||
if str_eq(tense, "progressive") {
|
||||
if is_irreg {
|
||||
return native_list_get(irreg, 4)
|
||||
}
|
||||
return verb_gerund(base)
|
||||
if is_irreg { return native_list_get(irreg, 4) }
|
||||
return en_verb_gerund(base)
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
// ── Determiner agreement ──────────────────────────────────────────────────────
|
||||
// "a" -> "an" before vowel sounds.
|
||||
// Language-independent interface; only English has a/an distinction today.
|
||||
|
||||
fn agree_determiner(det: String, noun: String) -> String {
|
||||
if str_eq(det, "a") {
|
||||
@@ -564,3 +420,280 @@ fn agree_determiner(det: String, noun: String) -> String {
|
||||
}
|
||||
return det
|
||||
}
|
||||
|
||||
// ── Morphology engine: public interface ───────────────────────────────────────
|
||||
|
||||
// morph_pluralize: pluralize a noun given a language profile.
|
||||
//
|
||||
// For isolating languages: return base form (no inflection).
|
||||
// For agglutinative languages: look up plural suffix from Engram; fall back to
|
||||
// base form when not found (Engram data drives agglutinative languages).
|
||||
// For fusional English: apply English rule + irregular table.
|
||||
// For other fusional languages: return base form (Engram data required).
|
||||
// For polysynthetic: return base form.
|
||||
|
||||
fn morph_pluralize(noun: String, profile: [String]) -> String {
|
||||
let mtype: String = lang_get(profile, "morph_type")
|
||||
let code: String = lang_get(profile, "code")
|
||||
|
||||
// ── Language-specific dispatch (0.3.0) ────────────────────────────────────
|
||||
// Delegate to the language module when one is available. Each module
|
||||
// exposes a well-known public function; unknown codes fall through to the
|
||||
// morph_type strategy below.
|
||||
if str_eq(code, "es") { return es_pluralize(noun) }
|
||||
if str_eq(code, "fr") { return fr_pluralize(noun) }
|
||||
if str_eq(code, "de") { return de_noun_plural(noun, "unknown") }
|
||||
if str_eq(code, "ru") { return ru_noun_case(noun, "m", "nom", "pl") }
|
||||
if str_eq(code, "ja") { return noun } // Japanese nouns do not pluralize
|
||||
if str_eq(code, "fi") { return fi_apply_case(noun, "nom", "pl") }
|
||||
if str_eq(code, "ar") { return ar_sound_plural(noun, "m") }
|
||||
if str_eq(code, "hi") { return hi_noun_direct(noun, hi_gender(noun), "pl") }
|
||||
if str_eq(code, "sw") { return sw_noun_plural(noun) }
|
||||
|
||||
// ── morph_type fallback (isolating / agglutinative / fusional) ────────────
|
||||
|
||||
if str_eq(mtype, "isolating") {
|
||||
// Isolating languages (zh, vi, etc.) do not inflect nouns.
|
||||
// Number is expressed through context, classifiers, or separate words.
|
||||
return noun
|
||||
}
|
||||
|
||||
if str_eq(mtype, "agglutinative") {
|
||||
// Agglutinative languages attach suffixes that come from vocabulary data
|
||||
// in the Engram. The engine provides the mechanism; the data (suffixes)
|
||||
// come from language-specific vocabulary nodes. Without loaded Engram
|
||||
// data for the target language, we return the base form and let the
|
||||
// caller supply the inflected form directly.
|
||||
return noun
|
||||
}
|
||||
|
||||
if str_eq(mtype, "fusional") {
|
||||
if str_eq(code, "en") {
|
||||
let irreg: String = en_irregular_plural(noun)
|
||||
if !str_eq(irreg, "") {
|
||||
return irreg
|
||||
}
|
||||
return en_pluralize_regular(noun)
|
||||
}
|
||||
// Other fusional languages: inflection tables are extensive and
|
||||
// language-specific. Return base; Engram vocabulary nodes supply the
|
||||
// correct plural form for the target language.
|
||||
return noun
|
||||
}
|
||||
|
||||
// polysynthetic and unknown: return base form
|
||||
return noun
|
||||
}
|
||||
|
||||
// morph_map_canonical: map cross-lingual canonical verbs to language-specific forms.
|
||||
//
|
||||
// Semantic layers pass English canonical labels ("be", "have", "do") as predicates.
|
||||
// Language modules expect their native infinitives ("ser", "sein", "olla" ...).
|
||||
// This function normalises before dispatch so each module sees its native form.
|
||||
//
|
||||
// Zero-copula languages (ar, ja, hi) return "" for "be" — callers that receive ""
|
||||
// should omit the verb from the surface form.
|
||||
|
||||
fn morph_map_canonical(verb: String, code: String) -> String {
|
||||
if str_eq(verb, "be") {
|
||||
if str_eq(code, "es") { return "ser" }
|
||||
if str_eq(code, "fr") { return "etre" } // ASCII alias; fr handles "etre" -> être
|
||||
if str_eq(code, "de") { return "sein" }
|
||||
if str_eq(code, "fi") { return "olla" }
|
||||
if str_eq(code, "ru") { return "byt" } // Latin transliteration for now
|
||||
if str_eq(code, "sw") { return "kuwa" }
|
||||
}
|
||||
return verb
|
||||
}
|
||||
|
||||
// morph_conjugate: conjugate a verb given tense, person, number, and profile.
|
||||
//
|
||||
// tense: "present" | "past" | "future" | "perfect" | "progressive"
|
||||
// person: "first" | "second" | "third"
|
||||
// number: "singular" | "plural"
|
||||
|
||||
fn morph_conjugate(verb: String, tense: String, person: String, number: String, profile: [String]) -> String {
|
||||
let mtype: String = lang_get(profile, "morph_type")
|
||||
let code: String = lang_get(profile, "code")
|
||||
|
||||
// Map canonical English verb labels to language-specific infinitives before dispatch.
|
||||
let verb = morph_map_canonical(verb, code)
|
||||
|
||||
// ── Language-specific dispatch (0.3.0) ────────────────────────────────────
|
||||
// Delegate to the language module when one is available. Each module
|
||||
// exposes a well-known public function; unknown codes fall through to the
|
||||
// morph_type strategy below.
|
||||
if str_eq(code, "es") { return es_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "fr") { return fr_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "de") { return de_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "ru") { return ru_conjugate(verb, tense, person, number, "unknown") }
|
||||
if str_eq(code, "ja") { return ja_conjugate(verb, "present") }
|
||||
if str_eq(code, "fi") { return fi_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "ar") { return ar_conjugate(verb, tense, person, "m", number) }
|
||||
if str_eq(code, "hi") { return hi_conjugate(verb, tense, person, "m", number) }
|
||||
if str_eq(code, "sw") { return sw_conjugate(verb, person, number, "1", tense) }
|
||||
if str_eq(code, "la") { return la_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "he") { return he_conjugate(verb, tense, person, "m", number) }
|
||||
if str_eq(code, "grc") { return grc_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "ang") { return ang_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "sa") { return sa_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "got") { return got_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "non") { return non_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "enm") { return enm_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "pi") { return pi_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "fro") { return fro_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "goh") { return goh_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "sga") { return sga_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "txb") { return txb_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "peo") { return peo_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "akk") { return akk_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "uga") { return uga_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "egy") { return egy_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "sux") { return sux_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "gez") { return gez_conjugate(verb, tense, person, number) }
|
||||
if str_eq(code, "cop") { return cop_conjugate(verb, tense, person, number) }
|
||||
|
||||
// ── morph_type fallback (isolating / agglutinative / fusional) ────────────
|
||||
|
||||
if str_eq(mtype, "isolating") {
|
||||
// Isolating languages do not inflect verbs. Tense and aspect are
|
||||
// expressed through particles and separate words (e.g. 了 in Mandarin).
|
||||
return verb
|
||||
}
|
||||
|
||||
if str_eq(mtype, "agglutinative") {
|
||||
// Agglutinative suffix chains. The engine builds the suffix sequence;
|
||||
// the actual suffix strings come from Engram vocabulary nodes tagged with
|
||||
// the language code and the grammatical feature.
|
||||
// Without Engram-loaded suffix tables, return base form.
|
||||
return verb
|
||||
}
|
||||
|
||||
if str_eq(mtype, "fusional") {
|
||||
if str_eq(code, "en") {
|
||||
return en_verb_form(verb, tense, person, number)
|
||||
}
|
||||
// Other fusional languages: return base form.
|
||||
// Engram vocabulary nodes carry the inflected forms for de, ru, ar, etc.
|
||||
return verb
|
||||
}
|
||||
|
||||
// polysynthetic and unknown
|
||||
return verb
|
||||
}
|
||||
|
||||
// morph_inflect: general inflection entry point.
|
||||
//
|
||||
// features: a semicolon-separated feature string, e.g. "plural" | "past;third;singular"
|
||||
// Dispatches to the appropriate engine path based on the profile morph_type.
|
||||
|
||||
fn morph_inflect(word: String, features: String, profile: [String]) -> String {
|
||||
// Parse the first feature token to decide what kind of inflection to apply.
|
||||
let n: Int = str_len(features)
|
||||
if n == 0 {
|
||||
return word
|
||||
}
|
||||
|
||||
// Scan to the first ';' to extract the leading feature token.
|
||||
let i: Int = 0
|
||||
let running: Bool = true
|
||||
while running {
|
||||
if i >= n {
|
||||
let running = false
|
||||
} else {
|
||||
let c: String = str_slice(features, i, i + 1)
|
||||
if str_eq(c, ";") {
|
||||
let running = false
|
||||
} else {
|
||||
let i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
let first_feat: String = str_slice(features, 0, i)
|
||||
|
||||
if str_eq(first_feat, "plural") {
|
||||
return morph_pluralize(word, profile)
|
||||
}
|
||||
|
||||
// For verb features: expect "tense;person;number"
|
||||
// Parse remaining tokens after the first ';'
|
||||
if i < n {
|
||||
let rest: String = str_slice(features, i + 1, n)
|
||||
// Find second ';'
|
||||
let j: Int = 0
|
||||
let rn: Int = str_len(rest)
|
||||
let running2: Bool = true
|
||||
while running2 {
|
||||
if j >= rn {
|
||||
let running2 = false
|
||||
} else {
|
||||
let c: String = str_slice(rest, j, j + 1)
|
||||
if str_eq(c, ";") {
|
||||
let running2 = false
|
||||
} else {
|
||||
let j = j + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
let person: String = str_slice(rest, 0, j)
|
||||
let number: String = ""
|
||||
if j < rn {
|
||||
let number = str_slice(rest, j + 1, rn)
|
||||
}
|
||||
return morph_conjugate(word, first_feat, person, number, profile)
|
||||
}
|
||||
|
||||
// Single token that is a tense (e.g. "past") with no person/number
|
||||
return morph_conjugate(word, first_feat, "third", "singular", profile)
|
||||
}
|
||||
|
||||
// ── Backward-compatible English-only entry points ─────────────────────────────
|
||||
//
|
||||
// These preserve the original signatures for callers that were written before
|
||||
// the language-profile system was introduced.
|
||||
|
||||
fn pluralize(singular: String) -> String {
|
||||
return morph_pluralize(singular, lang_default())
|
||||
}
|
||||
|
||||
fn singularize(plural: String) -> String {
|
||||
let irreg: String = en_irregular_singular(plural)
|
||||
if !str_eq(irreg, "") {
|
||||
return irreg
|
||||
}
|
||||
if str_ends(plural, "ies") {
|
||||
return str_drop_last(plural, 3) + "y"
|
||||
}
|
||||
if str_ends(plural, "ves") {
|
||||
let stem: String = str_drop_last(plural, 3)
|
||||
let last_stem: String = str_last_char(stem)
|
||||
if str_eq(last_stem, "i") {
|
||||
return stem + "fe"
|
||||
}
|
||||
return stem + "f"
|
||||
}
|
||||
if str_ends(plural, "ches") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "shes") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "xes") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "zes") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "ses") { return str_drop_last(plural, 2) }
|
||||
if str_ends(plural, "s") {
|
||||
return str_drop_last(plural, 1)
|
||||
}
|
||||
return plural
|
||||
}
|
||||
|
||||
// verb_form: English verb conjugation (original signature).
|
||||
fn verb_form(base: String, tense: String, person: String, number: String) -> String {
|
||||
return morph_conjugate(base, tense, person, number, lang_default())
|
||||
}
|
||||
|
||||
// irregular_plural: English irregular plural lookup (backward compat).
|
||||
fn irregular_plural(word: String) -> String {
|
||||
return en_irregular_plural(word)
|
||||
}
|
||||
|
||||
// irregular_singular: English irregular singular lookup (backward compat).
|
||||
fn irregular_singular(word: String) -> String {
|
||||
return en_irregular_singular(word)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user