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
711 lines
30 KiB
EmacsLisp
711 lines
30 KiB
EmacsLisp
// 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)
|
|
}
|