// morphology.el - Morphology engine: inflection driven by language profile. // // 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 // ── String helpers ──────────────────────────────────────────────────────────── import "language-profile.el" import "morphology-es.el" import "morphology-fr.el" import "morphology-de.el" import "morphology-ru.el" import "morphology-fi.el" import "morphology-ar.el" import "morphology-hi.el" import "morphology-sw.el" import "morphology-la.el" import "morphology-ja.el" fn str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn str_last_char(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "" } return str_slice(s, n - 1, n) } fn str_last2(s: String) -> String { let n: Int = str_len(s) if n < 2 { return s } return str_slice(s, n - 2, n) } fn str_last3(s: String) -> String { let n: Int = str_len(s) if n < 3 { return s } return str_slice(s, n - 3, n) } fn 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 is_vowel(c: String) -> Bool { if str_eq(c, "a") { return true } if str_eq(c, "e") { return true } if str_eq(c, "i") { return true } if str_eq(c, "o") { return true } if str_eq(c, "u") { return true } return false } // ── 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 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 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" } if str_eq(word, "teeth") { return "tooth" } if str_eq(word, "feet") { return "foot" } if str_eq(word, "geese") { return "goose" } if str_eq(word, "mice") { return "mouse" } if str_eq(word, "lice") { return "louse" } if str_eq(word, "oxen") { return "ox" } if str_eq(word, "people") { return "person" } if str_eq(word, "leaves") { return "leaf" } if str_eq(word, "wolves") { return "wolf" } if str_eq(word, "lives") { return "life" } if str_eq(word, "knives") { return "knife" } if str_eq(word, "wives") { return "wife" } if str_eq(word, "halves") { return "half" } if str_eq(word, "selves") { return "self" } if str_eq(word, "elves") { return "elf" } if str_eq(word, "shelves") { return "shelf" } 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 "" } // 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 } return empty } // ── English regular inflection helpers ──────────────────────────────────────── 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" } let last: String = str_last_char(base) if str_eq(last, "y") { let prev: String = str_drop_last(base, 1) let prev_last: String = str_last_char(prev) if !is_vowel(prev_last) { return prev + "ies" } } return base + "s" } fn en_should_double_final(base: String) -> Bool { let n: Int = str_len(base) if n < 3 { return false } 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 true } } } } } } return false } fn en_verb_past(base: String) -> String { if str_ends(base, "e") { return base + "d" } let last: String = str_last_char(base) if str_eq(last, "y") { let prev: String = str_drop_last(base, 1) let prev_last: String = str_last_char(prev) if !is_vowel(prev_last) { return prev + "ied" } } if en_should_double_final(base) { return base + last + "ed" } return base + "ed" } fn en_verb_gerund(base: String) -> String { if str_ends(base, "ie") { return str_drop_last(base, 2) + "ying" } if str_ends(base, "e") { if !str_ends(base, "ee") { return str_drop_last(base, 1) + "ing" } } let last: String = str_last_char(base) if en_should_double_final(base) { return base + last + "ing" } return base + "ing" } // 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 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" 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" } if str_eq(person, "first") { return "am" } if str_eq(person, "second") { return "are" } return "is" } if str_eq(tense, "past") { if str_eq(number, "plural") { return "were" } if str_eq(person, "second") { return "were" } return "was" } if str_eq(tense, "future") { return "will be" } if str_eq(tense, "perfect") { return "been" } if str_eq(tense, "progressive") { return "being" } return "be" } 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 en_verb_3sg(base) } } return base } if str_eq(tense, "past") { if is_irreg { return native_list_get(irreg, 2) } return en_verb_past(base) } if str_eq(tense, "future") { return "will " + base } if str_eq(tense, "perfect") { if is_irreg { return native_list_get(irreg, 3) } return en_verb_past(base) } if str_eq(tense, "progressive") { if is_irreg { return native_list_get(irreg, 4) } return en_verb_gerund(base) } return base } // ── Determiner agreement ────────────────────────────────────────────────────── // Language-independent interface; only English has a/an distinction today. fn agree_determiner(det: String, noun: String) -> String { if str_eq(det, "a") { let first: String = str_slice(noun, 0, 1) let fl: String = str_to_lower(first) if is_vowel(fl) { return "an" } return "a" } 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) }