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
922 lines
40 KiB
EmacsLisp
922 lines
40 KiB
EmacsLisp
// morphology-ru.el - Russian morphology: noun declension and verb conjugation.
|
||
//
|
||
// Russian is a fusional language with:
|
||
// - 6 grammatical cases: nominative, accusative, genitive, dative,
|
||
// instrumental, prepositional
|
||
// - 3 genders: masculine (m), feminine (f), neuter (n)
|
||
// - 2 numbers: singular (sg), plural (pl)
|
||
// - Aspect: perfective / imperfective (caller feature)
|
||
// - No articles
|
||
// - Cyrillic script (El strings are Unicode-transparent)
|
||
//
|
||
// Conventions:
|
||
// gender: "m" | "f" | "n"
|
||
// case: "nom" | "acc" | "gen" | "dat" | "ins" | "pre"
|
||
// number: "sg" | "pl"
|
||
// person: "1" | "2" | "3"
|
||
// tense: "present" | "past" | "future"
|
||
//
|
||
// Stem-type classification:
|
||
// "hard" - stem ends in a hard consonant
|
||
// "soft" - stem ends in a soft consonant (+ ь) or a hushing consonant
|
||
// "special" - lexically irregular
|
||
//
|
||
// Depends on: language-profile (str_eq, str_len, str_slice, str_drop_last,
|
||
// str_ends_with)
|
||
|
||
// ── Gender heuristic from ending ──────────────────────────────────────────────
|
||
//
|
||
// Russian gender is mostly predictable from the nominative singular ending:
|
||
// ends in consonant → masculine (стол, дом)
|
||
// ends in -ь → masculine or feminine (must be lexical)
|
||
// ends in -а / -я → feminine (женщина, земля)
|
||
// ends in -о / -е → neuter (слово, море)
|
||
//
|
||
// The heuristic returns the most probable gender. Caller should override
|
||
// for known exceptions (путь, рубль are masc despite -ь).
|
||
|
||
import "morphology.el"
|
||
fn ru_gender(noun: String) -> String {
|
||
let n: Int = str_len(noun)
|
||
if n == 0 { return "m" }
|
||
let last: String = str_slice(noun, n - 1, n)
|
||
|
||
// Neuter: -о or -е
|
||
if str_eq(last, "о") { return "n" }
|
||
if str_eq(last, "е") { return "n" }
|
||
if str_eq(last, "ё") { return "n" }
|
||
|
||
// Feminine: -а or -я
|
||
if str_eq(last, "а") { return "f" }
|
||
if str_eq(last, "я") { return "f" }
|
||
|
||
// Soft sign: ambiguous — default feminine (most common case)
|
||
if str_eq(last, "ь") { return "f" }
|
||
|
||
// Consonant or й: masculine
|
||
return "m"
|
||
}
|
||
|
||
// ── Stem-type classifier ──────────────────────────────────────────────────────
|
||
//
|
||
// "hard" — ends in a plain consonant (стол) or -а (женщина) or -о (слово)
|
||
// "soft" — ends in -ь, -й, -я, -е, -ие, -ия
|
||
// "sibilant" — ends in ж/ш/ч/щ (triggers spelling rules)
|
||
|
||
fn ru_stem_type(noun: String, gender: String) -> String {
|
||
let n: Int = str_len(noun)
|
||
if n == 0 { return "hard" }
|
||
let last: String = str_slice(noun, n - 1, n)
|
||
|
||
if str_eq(last, "ь") { return "soft" }
|
||
if str_eq(last, "й") { return "soft" }
|
||
if str_eq(last, "я") { return "soft" }
|
||
if str_eq(last, "е") { return "soft" }
|
||
|
||
// Sibilants (hushing consonants: ж ш ч щ) — use sibilant rules
|
||
if str_eq(last, "ж") { return "sibilant" }
|
||
if str_eq(last, "ш") { return "sibilant" }
|
||
if str_eq(last, "ч") { return "sibilant" }
|
||
if str_eq(last, "щ") { return "sibilant" }
|
||
|
||
return "hard"
|
||
}
|
||
|
||
// ── Noun declension ───────────────────────────────────────────────────────────
|
||
//
|
||
// Main entry point. Handles irregulars by lemma, then dispatches to the
|
||
// paradigm appropriate for (gender × stem_type).
|
||
|
||
fn ru_noun_case(noun: String, gender: String, gram_case: String, number: String) -> String {
|
||
// ── Lexical irregulars ──────────────────────────────────────────────────
|
||
|
||
// человек (person) — plural suppletive: люди
|
||
if str_eq(noun, "человек") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return "человек" }
|
||
if str_eq(gram_case, "acc") { return "человека" }
|
||
if str_eq(gram_case, "gen") { return "человека" }
|
||
if str_eq(gram_case, "dat") { return "человеку" }
|
||
if str_eq(gram_case, "ins") { return "человеком" }
|
||
if str_eq(gram_case, "pre") { return "человеке" }
|
||
}
|
||
// plural: люди paradigm
|
||
if str_eq(gram_case, "nom") { return "люди" }
|
||
if str_eq(gram_case, "acc") { return "людей" }
|
||
if str_eq(gram_case, "gen") { return "людей" }
|
||
if str_eq(gram_case, "dat") { return "людям" }
|
||
if str_eq(gram_case, "ins") { return "людьми" }
|
||
if str_eq(gram_case, "pre") { return "людях" }
|
||
return "люди"
|
||
}
|
||
|
||
// ребёнок (child) — plural suppletive: дети
|
||
if str_eq(noun, "ребёнок") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return "ребёнок" }
|
||
if str_eq(gram_case, "acc") { return "ребёнка" }
|
||
if str_eq(gram_case, "gen") { return "ребёнка" }
|
||
if str_eq(gram_case, "dat") { return "ребёнку" }
|
||
if str_eq(gram_case, "ins") { return "ребёнком" }
|
||
if str_eq(gram_case, "pre") { return "ребёнке" }
|
||
}
|
||
if str_eq(gram_case, "nom") { return "дети" }
|
||
if str_eq(gram_case, "acc") { return "детей" }
|
||
if str_eq(gram_case, "gen") { return "детей" }
|
||
if str_eq(gram_case, "dat") { return "детям" }
|
||
if str_eq(gram_case, "ins") { return "детьми" }
|
||
if str_eq(gram_case, "pre") { return "детях" }
|
||
return "дети"
|
||
}
|
||
|
||
// время (time) — neuter in -мя (heteroclitic declension)
|
||
if str_eq(noun, "время") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return "время" }
|
||
if str_eq(gram_case, "acc") { return "время" }
|
||
if str_eq(gram_case, "gen") { return "времени" }
|
||
if str_eq(gram_case, "dat") { return "времени" }
|
||
if str_eq(gram_case, "ins") { return "временем" }
|
||
if str_eq(gram_case, "pre") { return "времени" }
|
||
}
|
||
if str_eq(gram_case, "nom") { return "времена" }
|
||
if str_eq(gram_case, "acc") { return "времена" }
|
||
if str_eq(gram_case, "gen") { return "времён" }
|
||
if str_eq(gram_case, "dat") { return "временам" }
|
||
if str_eq(gram_case, "ins") { return "временами" }
|
||
if str_eq(gram_case, "pre") { return "временах" }
|
||
return "времена"
|
||
}
|
||
|
||
// имя (name) — neuter in -мя
|
||
if str_eq(noun, "имя") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return "имя" }
|
||
if str_eq(gram_case, "acc") { return "имя" }
|
||
if str_eq(gram_case, "gen") { return "имени" }
|
||
if str_eq(gram_case, "dat") { return "имени" }
|
||
if str_eq(gram_case, "ins") { return "именем" }
|
||
if str_eq(gram_case, "pre") { return "имени" }
|
||
}
|
||
if str_eq(gram_case, "nom") { return "имена" }
|
||
if str_eq(gram_case, "acc") { return "имена" }
|
||
if str_eq(gram_case, "gen") { return "имён" }
|
||
if str_eq(gram_case, "dat") { return "именам" }
|
||
if str_eq(gram_case, "ins") { return "именами" }
|
||
if str_eq(gram_case, "pre") { return "именах" }
|
||
return "имена"
|
||
}
|
||
|
||
// путь (way/path) — masculine soft with unique instrumental
|
||
if str_eq(noun, "путь") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return "путь" }
|
||
if str_eq(gram_case, "acc") { return "путь" }
|
||
if str_eq(gram_case, "gen") { return "пути" }
|
||
if str_eq(gram_case, "dat") { return "пути" }
|
||
if str_eq(gram_case, "ins") { return "путём" }
|
||
if str_eq(gram_case, "pre") { return "пути" }
|
||
}
|
||
if str_eq(gram_case, "nom") { return "пути" }
|
||
if str_eq(gram_case, "acc") { return "пути" }
|
||
if str_eq(gram_case, "gen") { return "путей" }
|
||
if str_eq(gram_case, "dat") { return "путям" }
|
||
if str_eq(gram_case, "ins") { return "путями" }
|
||
if str_eq(gram_case, "pre") { return "путях" }
|
||
return "пути"
|
||
}
|
||
|
||
// мать (mother) — feminine with -ер- insert
|
||
if str_eq(noun, "мать") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return "мать" }
|
||
if str_eq(gram_case, "acc") { return "мать" }
|
||
if str_eq(gram_case, "gen") { return "матери" }
|
||
if str_eq(gram_case, "dat") { return "матери" }
|
||
if str_eq(gram_case, "ins") { return "матерью" }
|
||
if str_eq(gram_case, "pre") { return "матери" }
|
||
}
|
||
if str_eq(gram_case, "nom") { return "матери" }
|
||
if str_eq(gram_case, "acc") { return "матерей" }
|
||
if str_eq(gram_case, "gen") { return "матерей" }
|
||
if str_eq(gram_case, "dat") { return "матерям" }
|
||
if str_eq(gram_case, "ins") { return "матерями" }
|
||
if str_eq(gram_case, "pre") { return "матерях" }
|
||
return "матери"
|
||
}
|
||
|
||
// дочь (daughter) — same insert pattern
|
||
if str_eq(noun, "дочь") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return "дочь" }
|
||
if str_eq(gram_case, "acc") { return "дочь" }
|
||
if str_eq(gram_case, "gen") { return "дочери" }
|
||
if str_eq(gram_case, "dat") { return "дочери" }
|
||
if str_eq(gram_case, "ins") { return "дочерью" }
|
||
if str_eq(gram_case, "pre") { return "дочери" }
|
||
}
|
||
if str_eq(gram_case, "nom") { return "дочери" }
|
||
if str_eq(gram_case, "acc") { return "дочерей" }
|
||
if str_eq(gram_case, "gen") { return "дочерей" }
|
||
if str_eq(gram_case, "dat") { return "дочерям" }
|
||
if str_eq(gram_case, "ins") { return "дочерями" }
|
||
if str_eq(gram_case, "pre") { return "дочерях" }
|
||
return "дочери"
|
||
}
|
||
|
||
// ── Regular paradigm dispatch ────────────────────────────────────────────
|
||
|
||
let stype: String = ru_stem_type(noun, gender)
|
||
return ru_decline_regular(noun, gender, stype, gram_case, number)
|
||
}
|
||
|
||
// ── Regular declension paradigms ─────────────────────────────────────────────
|
||
//
|
||
// Handles hard, soft, and sibilant stems across all three genders.
|
||
|
||
fn ru_decline_regular(noun: String, gender: String, stype: String, gram_case: String, number: String) -> String {
|
||
if str_eq(gender, "m") {
|
||
return ru_decline_masc(noun, stype, gram_case, number)
|
||
}
|
||
if str_eq(gender, "f") {
|
||
return ru_decline_fem(noun, stype, gram_case, number)
|
||
}
|
||
return ru_decline_neut(noun, stype, gram_case, number)
|
||
}
|
||
|
||
// ── Masculine declension ──────────────────────────────────────────────────────
|
||
//
|
||
// Hard (стол): stem = noun itself (zero ending in nom sg)
|
||
// Sg: стол, стол, стола, столу, столом, столе
|
||
// Pl: столы, столы, столов, столам, столами, столах
|
||
//
|
||
// Soft ending in -й (трамвай → трамва-):
|
||
// Sg: трамвай, трамвай, трамвая, трамваю, трамваем, трамвае
|
||
// Pl: трамваи, трамваи, трамваев, трамваям, трамваями, трамваях
|
||
//
|
||
// Soft ending in -ь (рубль → рубл-):
|
||
// Sg: рубль, рубль, рубля, рублю, рублём, рубле
|
||
// Pl: рубли, рубли, рублей, рублям, рублями, рублях
|
||
//
|
||
// Sibilant (нож → нож-):
|
||
// Same as hard but ins sg = -ом only when stressed; unstressed = -ем
|
||
// Here we assume stressed stem and use -ом.
|
||
|
||
fn ru_decline_masc(noun: String, stype: String, gram_case: String, number: String) -> String {
|
||
let n: Int = str_len(noun)
|
||
|
||
if str_eq(stype, "soft") {
|
||
let last: String = str_slice(noun, n - 1, n)
|
||
|
||
// Stem in -й (трамвай)
|
||
if str_eq(last, "й") {
|
||
let stem: String = str_drop_last(noun, 1)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return noun } // inanimate
|
||
if str_eq(gram_case, "gen") { return stem + "я" }
|
||
if str_eq(gram_case, "dat") { return stem + "ю" }
|
||
if str_eq(gram_case, "ins") { return stem + "ем" }
|
||
if str_eq(gram_case, "pre") { return stem + "е" }
|
||
return noun
|
||
}
|
||
if str_eq(gram_case, "nom") { return stem + "и" }
|
||
if str_eq(gram_case, "acc") { return stem + "и" }
|
||
if str_eq(gram_case, "gen") { return stem + "ев" }
|
||
if str_eq(gram_case, "dat") { return stem + "ям" }
|
||
if str_eq(gram_case, "ins") { return stem + "ями" }
|
||
if str_eq(gram_case, "pre") { return stem + "ях" }
|
||
return stem + "и"
|
||
}
|
||
|
||
// Stem in -ь (рубль, гость)
|
||
if str_eq(last, "ь") {
|
||
let stem: String = str_drop_last(noun, 1)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return noun }
|
||
if str_eq(gram_case, "gen") { return stem + "я" }
|
||
if str_eq(gram_case, "dat") { return stem + "ю" }
|
||
if str_eq(gram_case, "ins") { return stem + "ём" }
|
||
if str_eq(gram_case, "pre") { return stem + "е" }
|
||
return noun
|
||
}
|
||
if str_eq(gram_case, "nom") { return stem + "и" }
|
||
if str_eq(gram_case, "acc") { return stem + "и" }
|
||
if str_eq(gram_case, "gen") { return stem + "ей" }
|
||
if str_eq(gram_case, "dat") { return stem + "ям" }
|
||
if str_eq(gram_case, "ins") { return stem + "ями" }
|
||
if str_eq(gram_case, "pre") { return stem + "ях" }
|
||
return stem + "и"
|
||
}
|
||
}
|
||
|
||
// Hard and sibilant: stem = noun (zero ending in nom sg)
|
||
let stem: String = noun
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return stem }
|
||
if str_eq(gram_case, "acc") { return stem } // inanimate; animate = gen
|
||
if str_eq(gram_case, "gen") { return stem + "а" }
|
||
if str_eq(gram_case, "dat") { return stem + "у" }
|
||
if str_eq(gram_case, "ins") { return stem + "ом" }
|
||
if str_eq(gram_case, "pre") { return stem + "е" }
|
||
return stem
|
||
}
|
||
// Plural
|
||
if str_eq(gram_case, "nom") { return stem + "ы" }
|
||
if str_eq(gram_case, "acc") { return stem + "ы" }
|
||
if str_eq(gram_case, "gen") { return stem + "ов" }
|
||
if str_eq(gram_case, "dat") { return stem + "ам" }
|
||
if str_eq(gram_case, "ins") { return stem + "ами" }
|
||
if str_eq(gram_case, "pre") { return stem + "ах" }
|
||
return stem + "ы"
|
||
}
|
||
|
||
// ── Feminine declension ───────────────────────────────────────────────────────
|
||
//
|
||
// Hard in -а (женщина):
|
||
// Sg: женщина, женщину, женщины, женщине, женщиной, женщине
|
||
// Pl: женщины, женщин, женщин, женщинам, женщинами, женщинах
|
||
//
|
||
// Soft in -я (земля):
|
||
// Sg: земля, землю, земли, земле, землёй, земле
|
||
// Pl: земли, земли, земель, землям, землями, землях
|
||
//
|
||
// Soft in -ь (тетрадь, ночь):
|
||
// Sg: тетрадь, тетрадь, тетради, тетради, тетрадью, тетради
|
||
// Pl: тетради, тетради, тетрадей, тетрадям, тетрадями, тетрадях
|
||
|
||
fn ru_decline_fem(noun: String, stype: String, gram_case: String, number: String) -> String {
|
||
let n: Int = str_len(noun)
|
||
let last: String = str_slice(noun, n - 1, n)
|
||
|
||
// Soft in -ь
|
||
if str_eq(last, "ь") {
|
||
let stem: String = str_drop_last(noun, 1)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return noun }
|
||
if str_eq(gram_case, "gen") { return stem + "и" }
|
||
if str_eq(gram_case, "dat") { return stem + "и" }
|
||
if str_eq(gram_case, "ins") { return stem + "ью" }
|
||
if str_eq(gram_case, "pre") { return stem + "и" }
|
||
return noun
|
||
}
|
||
if str_eq(gram_case, "nom") { return stem + "и" }
|
||
if str_eq(gram_case, "acc") { return stem + "и" }
|
||
if str_eq(gram_case, "gen") { return stem + "ей" }
|
||
if str_eq(gram_case, "dat") { return stem + "ям" }
|
||
if str_eq(gram_case, "ins") { return stem + "ями" }
|
||
if str_eq(gram_case, "pre") { return stem + "ях" }
|
||
return stem + "и"
|
||
}
|
||
|
||
// Soft in -я (земля, семья)
|
||
if str_eq(last, "я") {
|
||
let stem: String = str_drop_last(noun, 1)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return stem + "ю" }
|
||
if str_eq(gram_case, "gen") { return stem + "и" }
|
||
if str_eq(gram_case, "dat") { return stem + "е" }
|
||
if str_eq(gram_case, "ins") { return stem + "ей" }
|
||
if str_eq(gram_case, "pre") { return stem + "е" }
|
||
return noun
|
||
}
|
||
if str_eq(gram_case, "nom") { return stem + "и" }
|
||
if str_eq(gram_case, "acc") { return stem + "и" }
|
||
// Genitive plural: zero ending (with possible fleeting vowel) — use -ей for soft
|
||
if str_eq(gram_case, "gen") { return stem + "ей" }
|
||
if str_eq(gram_case, "dat") { return stem + "ям" }
|
||
if str_eq(gram_case, "ins") { return stem + "ями" }
|
||
if str_eq(gram_case, "pre") { return stem + "ях" }
|
||
return stem + "и"
|
||
}
|
||
|
||
// Hard in -а (женщина, страна, рука)
|
||
if str_eq(last, "а") {
|
||
let stem: String = str_drop_last(noun, 1)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return stem + "у" }
|
||
if str_eq(gram_case, "gen") { return stem + "ы" }
|
||
if str_eq(gram_case, "dat") { return stem + "е" }
|
||
if str_eq(gram_case, "ins") { return stem + "ой" }
|
||
if str_eq(gram_case, "pre") { return stem + "е" }
|
||
return noun
|
||
}
|
||
// Plural: gen pl = zero ending (stem only) for most -а feminines
|
||
if str_eq(gram_case, "nom") { return stem + "ы" }
|
||
if str_eq(gram_case, "acc") { return stem + "ы" }
|
||
if str_eq(gram_case, "gen") { return stem }
|
||
if str_eq(gram_case, "dat") { return stem + "ам" }
|
||
if str_eq(gram_case, "ins") { return stem + "ами" }
|
||
if str_eq(gram_case, "pre") { return stem + "ах" }
|
||
return stem + "ы"
|
||
}
|
||
|
||
// Fallback: treat as hard -а (unexpected ending)
|
||
return noun
|
||
}
|
||
|
||
// ── Neuter declension ─────────────────────────────────────────────────────────
|
||
//
|
||
// Hard in -о (слово):
|
||
// Sg: слово, слово, слова, слову, словом, слове
|
||
// Pl: слова, слова, слов, словам, словами, словах
|
||
//
|
||
// Soft in -е (море, поле):
|
||
// Sg: море, море, моря, морю, морем, море
|
||
// Pl: моря, моря, морей, морям, морями, морях
|
||
//
|
||
// Soft in -ие (здание):
|
||
// Sg: здание, здание, здания, зданию, зданием, здании
|
||
// Pl: здания, здания, зданий, зданиям, зданиями, зданиях
|
||
|
||
fn ru_decline_neut(noun: String, stype: String, gram_case: String, number: String) -> String {
|
||
let n: Int = str_len(noun)
|
||
let last: String = str_slice(noun, n - 1, n)
|
||
|
||
// Nouns in -ие (здание, здравоохранение)
|
||
if str_ends_with(noun, "ие") {
|
||
let stem: String = str_drop_last(noun, 2)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return noun }
|
||
if str_eq(gram_case, "gen") { return stem + "ия" }
|
||
if str_eq(gram_case, "dat") { return stem + "ию" }
|
||
if str_eq(gram_case, "ins") { return stem + "ием" }
|
||
if str_eq(gram_case, "pre") { return stem + "ии" }
|
||
return noun
|
||
}
|
||
if str_eq(gram_case, "nom") { return stem + "ия" }
|
||
if str_eq(gram_case, "acc") { return stem + "ия" }
|
||
if str_eq(gram_case, "gen") { return stem + "ий" }
|
||
if str_eq(gram_case, "dat") { return stem + "иям" }
|
||
if str_eq(gram_case, "ins") { return stem + "иями" }
|
||
if str_eq(gram_case, "pre") { return stem + "иях" }
|
||
return stem + "ия"
|
||
}
|
||
|
||
// Soft in -е (море, поле)
|
||
if str_eq(last, "е") {
|
||
let stem: String = str_drop_last(noun, 1)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return noun }
|
||
if str_eq(gram_case, "gen") { return stem + "я" }
|
||
if str_eq(gram_case, "dat") { return stem + "ю" }
|
||
if str_eq(gram_case, "ins") { return stem + "ем" }
|
||
if str_eq(gram_case, "pre") { return noun }
|
||
return noun
|
||
}
|
||
if str_eq(gram_case, "nom") { return stem + "я" }
|
||
if str_eq(gram_case, "acc") { return stem + "я" }
|
||
if str_eq(gram_case, "gen") { return stem + "ей" }
|
||
if str_eq(gram_case, "dat") { return stem + "ям" }
|
||
if str_eq(gram_case, "ins") { return stem + "ями" }
|
||
if str_eq(gram_case, "pre") { return stem + "ях" }
|
||
return stem + "я"
|
||
}
|
||
|
||
// Hard in -о (слово, окно, место)
|
||
if str_eq(last, "о") {
|
||
let stem: String = str_drop_last(noun, 1)
|
||
if str_eq(number, "sg") {
|
||
if str_eq(gram_case, "nom") { return noun }
|
||
if str_eq(gram_case, "acc") { return noun }
|
||
if str_eq(gram_case, "gen") { return stem + "а" }
|
||
if str_eq(gram_case, "dat") { return stem + "у" }
|
||
if str_eq(gram_case, "ins") { return stem + "ом" }
|
||
if str_eq(gram_case, "pre") { return stem + "е" }
|
||
return noun
|
||
}
|
||
if str_eq(gram_case, "nom") { return stem + "а" }
|
||
if str_eq(gram_case, "acc") { return stem + "а" }
|
||
// Genitive plural: zero ending (слов, окон, мест)
|
||
if str_eq(gram_case, "gen") { return stem }
|
||
if str_eq(gram_case, "dat") { return stem + "ам" }
|
||
if str_eq(gram_case, "ins") { return stem + "ами" }
|
||
if str_eq(gram_case, "pre") { return stem + "ах" }
|
||
return stem + "а"
|
||
}
|
||
|
||
// Fallback
|
||
return noun
|
||
}
|
||
|
||
// ── Past-tense agreement ──────────────────────────────────────────────────────
|
||
//
|
||
// Russian past tense agrees with the subject in gender and number.
|
||
// The past stem is derived from the infinitive (drop -ть/-ти/-чь).
|
||
//
|
||
// Endings: masc → (bare stem), fem → -а, neut → -о, pl → -и
|
||
//
|
||
// Special: verbs in -чь (мочь → мог), -ти (идти → шёл) are irregular.
|
||
|
||
fn ru_past_agree(verb_stem: String, gender: String, number: String) -> String {
|
||
if str_eq(number, "pl") {
|
||
return verb_stem + "и"
|
||
}
|
||
if str_eq(gender, "f") { return verb_stem + "а" }
|
||
if str_eq(gender, "n") { return verb_stem + "о" }
|
||
return verb_stem
|
||
}
|
||
|
||
// ── First-conjugation present tense ──────────────────────────────────────────
|
||
//
|
||
// Model: читать (чита-) → читаю, читаешь, читает, читаем, читаете, читают
|
||
// Model: писать (пиш-) — stem changes in conjugation (handled by caller)
|
||
//
|
||
// This function takes an already-computed present stem (e.g. "чита") and
|
||
// applies first-conjugation endings.
|
||
//
|
||
// 1sg: stem + -ю (after vowel) or -у (after consonant)
|
||
// 2sg: stem + -ешь
|
||
// 3sg: stem + -ет
|
||
// 1pl: stem + -ем
|
||
// 2pl: stem + -ете
|
||
// 3pl: stem + -ют (after vowel) or -ут (after consonant)
|
||
|
||
fn ru_conjugate_1st(stem: String, tense: String, person: String, number: String) -> String {
|
||
if str_eq(tense, "present") {
|
||
let n: Int = str_len(stem)
|
||
let last: String = str_slice(stem, n - 1, n)
|
||
let vowels: Bool = false
|
||
let vowels =
|
||
str_eq(last, "а") ||
|
||
str_eq(last, "е") ||
|
||
str_eq(last, "и") ||
|
||
str_eq(last, "о") ||
|
||
str_eq(last, "у") ||
|
||
str_eq(last, "ю") ||
|
||
str_eq(last, "я") ||
|
||
str_eq(last, "э") ||
|
||
str_eq(last, "ё") ||
|
||
str_eq(last, "ы")
|
||
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") {
|
||
if vowels { return stem + "ю" }
|
||
return stem + "у"
|
||
}
|
||
if str_eq(person, "2") { return stem + "ешь" }
|
||
return stem + "ет"
|
||
}
|
||
if str_eq(person, "1") { return stem + "ем" }
|
||
if str_eq(person, "2") { return stem + "ете" }
|
||
if vowels { return stem + "ют" }
|
||
return stem + "ут"
|
||
}
|
||
// Past and future handled by ru_conjugate
|
||
return stem
|
||
}
|
||
|
||
// ── Second-conjugation present tense ─────────────────────────────────────────
|
||
//
|
||
// Model: говорить (говор-) → говорю, говоришь, говорит, говорим, говорите, говорят
|
||
//
|
||
// 1sg: stem + -ю / -у
|
||
// 2sg: stem + -ишь
|
||
// 3sg: stem + -ит
|
||
// 1pl: stem + -им
|
||
// 2pl: stem + -ите
|
||
// 3pl: stem + -ят / -ат
|
||
|
||
fn ru_conjugate_2nd(stem: String, tense: String, person: String, number: String) -> String {
|
||
if str_eq(tense, "present") {
|
||
let n: Int = str_len(stem)
|
||
let last: String = str_slice(stem, n - 1, n)
|
||
let after_vowel: Bool =
|
||
str_eq(last, "а") ||
|
||
str_eq(last, "е") ||
|
||
str_eq(last, "и") ||
|
||
str_eq(last, "о") ||
|
||
str_eq(last, "у") ||
|
||
str_eq(last, "ю") ||
|
||
str_eq(last, "я") ||
|
||
str_eq(last, "э") ||
|
||
str_eq(last, "ё") ||
|
||
str_eq(last, "ы")
|
||
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") {
|
||
if after_vowel { return stem + "ю" }
|
||
return stem + "у"
|
||
}
|
||
if str_eq(person, "2") { return stem + "ишь" }
|
||
return stem + "ит"
|
||
}
|
||
if str_eq(person, "1") { return stem + "им" }
|
||
if str_eq(person, "2") { return stem + "ите" }
|
||
if after_vowel { return stem + "ят" }
|
||
return stem + "ат"
|
||
}
|
||
return stem
|
||
}
|
||
|
||
// ── Irregular verb forms ──────────────────────────────────────────────────────
|
||
//
|
||
// Returns the inflected form for known irregular verbs, or "" if unknown.
|
||
// gender is used for past tense agreement.
|
||
|
||
fn ru_irregular(verb: String, tense: String, person: String, number: String) -> String {
|
||
// быть (to be) — present tense: есть (invariant) / rarely conjugated
|
||
if str_eq(verb, "быть") {
|
||
if str_eq(tense, "present") {
|
||
// Modern Russian uses invariant "есть" for all persons in present
|
||
return "есть"
|
||
}
|
||
// Future: буду, будешь, будет, будем, будете, будут
|
||
if str_eq(tense, "future") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") { return "буду" }
|
||
if str_eq(person, "2") { return "будешь" }
|
||
return "будет"
|
||
}
|
||
if str_eq(person, "1") { return "будем" }
|
||
if str_eq(person, "2") { return "будете" }
|
||
return "будут"
|
||
}
|
||
// Past: returned without gender agreement — caller must call ru_past_agree
|
||
return ""
|
||
}
|
||
|
||
// идти (to go on foot) — present: иду, идёшь, идёт, идём, идёте, идут
|
||
if str_eq(verb, "идти") {
|
||
if str_eq(tense, "present") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") { return "иду" }
|
||
if str_eq(person, "2") { return "идёшь" }
|
||
return "идёт"
|
||
}
|
||
if str_eq(person, "1") { return "идём" }
|
||
if str_eq(person, "2") { return "идёте" }
|
||
return "идут"
|
||
}
|
||
// Past stem: шёл (m), шла (f), шло (n), шли (pl)
|
||
return ""
|
||
}
|
||
|
||
// ехать (to go by vehicle) — present: еду, едешь, едет, едем, едете, едут
|
||
if str_eq(verb, "ехать") {
|
||
if str_eq(tense, "present") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") { return "еду" }
|
||
if str_eq(person, "2") { return "едешь" }
|
||
return "едет"
|
||
}
|
||
if str_eq(person, "1") { return "едем" }
|
||
if str_eq(person, "2") { return "едете" }
|
||
return "едут"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// говорить (to speak) — 2nd conjugation
|
||
if str_eq(verb, "говорить") {
|
||
if str_eq(tense, "present") {
|
||
return ru_conjugate_2nd("говор", "present", person, number)
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// знать (to know) — 1st conjugation, regular: зна-
|
||
if str_eq(verb, "знать") {
|
||
if str_eq(tense, "present") {
|
||
return ru_conjugate_1st("зна", "present", person, number)
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// видеть (to see) — 2nd conjugation, 1sg mutation: вижу
|
||
if str_eq(verb, "видеть") {
|
||
if str_eq(tense, "present") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") { return "вижу" }
|
||
if str_eq(person, "2") { return "видишь" }
|
||
return "видит"
|
||
}
|
||
if str_eq(person, "1") { return "видим" }
|
||
if str_eq(person, "2") { return "видите" }
|
||
return "видят"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// делать (to do) — 1st conjugation: дела-
|
||
if str_eq(verb, "делать") {
|
||
if str_eq(tense, "present") {
|
||
return ru_conjugate_1st("дела", "present", person, number)
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// хотеть (to want) — mixed conjugation (1st pl, 2nd sg/3sg)
|
||
if str_eq(verb, "хотеть") {
|
||
if str_eq(tense, "present") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") { return "хочу" }
|
||
if str_eq(person, "2") { return "хочешь" }
|
||
return "хочет"
|
||
}
|
||
if str_eq(person, "1") { return "хотим" }
|
||
if str_eq(person, "2") { return "хотите" }
|
||
return "хотят"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// мочь (can/be able) — present: могу, можешь, может, можем, можете, могут
|
||
if str_eq(verb, "мочь") {
|
||
if str_eq(tense, "present") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") { return "могу" }
|
||
if str_eq(person, "2") { return "можешь" }
|
||
return "может"
|
||
}
|
||
if str_eq(person, "1") { return "можем" }
|
||
if str_eq(person, "2") { return "можете" }
|
||
return "могут"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// сказать (to say, pfv) — present (= future perfective):
|
||
// скажу, скажешь, скажет, скажем, скажете, скажут
|
||
if str_eq(verb, "сказать") {
|
||
if str_eq(tense, "present") {
|
||
if str_eq(number, "sg") {
|
||
if str_eq(person, "1") { return "скажу" }
|
||
if str_eq(person, "2") { return "скажешь" }
|
||
return "скажет"
|
||
}
|
||
if str_eq(person, "1") { return "скажем" }
|
||
if str_eq(person, "2") { return "скажете" }
|
||
return "скажут"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
return ""
|
||
}
|
||
|
||
// ── Past stem table ───────────────────────────────────────────────────────────
|
||
//
|
||
// Returns the masculine past stem (without agreement ending) for known verbs.
|
||
// The caller must run ru_past_agree(stem, gender, number) to get the full form.
|
||
|
||
fn ru_past_stem(verb: String) -> String {
|
||
// Regular -ать: drop -ть, add nothing (past stem = infinitive minus -ть)
|
||
if str_eq(verb, "читать") { return "чита" }
|
||
if str_eq(verb, "знать") { return "зна" }
|
||
if str_eq(verb, "делать") { return "дела" }
|
||
if str_eq(verb, "сказать") { return "сказа" }
|
||
if str_eq(verb, "думать") { return "дума" }
|
||
if str_eq(verb, "работать") { return "работа" }
|
||
if str_eq(verb, "писать") { return "писа" }
|
||
if str_eq(verb, "слушать") { return "слуша" }
|
||
if str_eq(verb, "отвечать") { return "отвеча" }
|
||
|
||
// Regular -ить/-еть
|
||
if str_eq(verb, "говорить") { return "говори" }
|
||
if str_eq(verb, "видеть") { return "виде" }
|
||
if str_eq(verb, "смотреть") { return "смотре" }
|
||
if str_eq(verb, "иметь") { return "име" }
|
||
if str_eq(verb, "хотеть") { return "хоте" }
|
||
|
||
// Irregular pasts
|
||
if str_eq(verb, "быть") { return "бы" } // был/была/было/были
|
||
if str_eq(verb, "идти") { return "шё" } // шёл — caller must handle -л suffix
|
||
if str_eq(verb, "ехать") { return "еха" } // ехал
|
||
if str_eq(verb, "мочь") { return "мо" } // мог (m: мог, not мол)
|
||
if str_eq(verb, "нести") { return "нё" } // нёс
|
||
if str_eq(verb, "вести") { return "вё" } // вёл
|
||
|
||
// Derive from infinitive: drop -ть
|
||
let n: Int = str_len(verb)
|
||
if n > 2 {
|
||
let last2: String = str_slice(verb, n - 2, n)
|
||
if str_eq(last2, "ть") {
|
||
return str_drop_last(verb, 2)
|
||
}
|
||
}
|
||
return verb
|
||
}
|
||
|
||
// ── Unified Russian verb conjugation ─────────────────────────────────────────
|
||
//
|
||
// tense: "present" | "past" | "future"
|
||
// person: "1" | "2" | "3"
|
||
// number: "sg" | "pl"
|
||
// gender: "m" | "f" | "n" — used for past tense agreement only
|
||
|
||
fn ru_conjugate(verb: String, tense: String, person: String, number: String, gender: String) -> String {
|
||
// ── Copula "byt" (transliterated быть) ────────────────────────────────
|
||
// Russian present tense has zero copula — "Кошка большая" not "Кошка есть большая".
|
||
// Return empty string so gram_order_constituents skips the verb slot.
|
||
if str_eq(verb, "byt") {
|
||
if str_eq(tense, "present") { return "" }
|
||
// Future: буду/будешь/будет... Past: был/была/было/были
|
||
// For now return transliterated placeholder; full Unicode tables TBD.
|
||
if str_eq(tense, "future") { return "budet" }
|
||
return "byl"
|
||
}
|
||
|
||
// ── Past tense ──────────────────────────────────────────────────────────
|
||
if str_eq(tense, "past") {
|
||
// Special-case verbs with non-standard past forms
|
||
if str_eq(verb, "идти") {
|
||
if str_eq(number, "pl") { return "шли" }
|
||
if str_eq(gender, "f") { return "шла" }
|
||
if str_eq(gender, "n") { return "шло" }
|
||
return "шёл"
|
||
}
|
||
if str_eq(verb, "мочь") {
|
||
if str_eq(number, "pl") { return "могли" }
|
||
if str_eq(gender, "f") { return "могла" }
|
||
if str_eq(gender, "n") { return "могло" }
|
||
return "мог"
|
||
}
|
||
if str_eq(verb, "нести") {
|
||
if str_eq(number, "pl") { return "несли" }
|
||
if str_eq(gender, "f") { return "несла" }
|
||
if str_eq(gender, "n") { return "несло" }
|
||
return "нёс"
|
||
}
|
||
if str_eq(verb, "вести") {
|
||
if str_eq(number, "pl") { return "вели" }
|
||
if str_eq(gender, "f") { return "вела" }
|
||
if str_eq(gender, "n") { return "вело" }
|
||
return "вёл"
|
||
}
|
||
let ps: String = ru_past_stem(verb)
|
||
return ru_past_agree(ps, gender, number)
|
||
}
|
||
|
||
// ── Future tense (imperfective) ─────────────────────────────────────────
|
||
//
|
||
// Imperfective future = быть (conjugated) + infinitive
|
||
if str_eq(tense, "future") {
|
||
let aux: String = ru_irregular("быть", "future", person, number)
|
||
return aux + " " + verb
|
||
}
|
||
|
||
// ── Present tense ───────────────────────────────────────────────────────
|
||
|
||
// Try the irregular table first
|
||
let irr: String = ru_irregular(verb, tense, person, number)
|
||
if !str_eq(irr, "") {
|
||
return irr
|
||
}
|
||
|
||
// Derive conjugation class from infinitive ending
|
||
let n: Int = str_len(verb)
|
||
if n > 4 {
|
||
let last4: String = str_slice(verb, n - 4, n)
|
||
// -ить verbs → 2nd conjugation (most)
|
||
if str_eq(last4, "ить ") {
|
||
// remove trailing space if any — defensive
|
||
}
|
||
}
|
||
|
||
// Detect -ить suffix → 2nd conjugation
|
||
if str_ends_with(verb, "ить") {
|
||
let stem: String = str_drop_last(verb, 3)
|
||
return ru_conjugate_2nd(stem, "present", person, number)
|
||
}
|
||
|
||
// Detect -еть suffix → 2nd conjugation
|
||
if str_ends_with(verb, "еть") {
|
||
let stem: String = str_drop_last(verb, 3)
|
||
return ru_conjugate_2nd(stem, "present", person, number)
|
||
}
|
||
|
||
// Detect -ать suffix → 1st conjugation
|
||
if str_ends_with(verb, "ать") {
|
||
let stem: String = str_drop_last(verb, 2)
|
||
return ru_conjugate_1st(stem, "present", person, number)
|
||
}
|
||
|
||
// Detect -ять suffix → 1st conjugation
|
||
if str_ends_with(verb, "ять") {
|
||
let stem: String = str_drop_last(verb, 2)
|
||
return ru_conjugate_1st(stem, "present", person, number)
|
||
}
|
||
|
||
// Detect -овать suffix → 1st conjugation with -у- stem
|
||
if str_ends_with(verb, "овать") {
|
||
let stem: String = str_drop_last(verb, 5) + "у"
|
||
return ru_conjugate_1st(stem, "present", person, number)
|
||
}
|
||
|
||
// Detect -нуть suffix → 1st conjugation
|
||
if str_ends_with(verb, "нуть") {
|
||
let stem: String = str_drop_last(verb, 4) + "н"
|
||
return ru_conjugate_1st(stem, "present", person, number)
|
||
}
|
||
|
||
// Fallback: return the infinitive
|
||
return verb
|
||
}
|