Rename: nlg → elp (Engram Language Protocol)

This commit is contained in:
Will Anderson
2026-05-02 22:15:25 -05:00
parent cbb27b8d87
commit 34725a3988
125 changed files with 129214 additions and 780 deletions
+920
View File
@@ -0,0 +1,920 @@
// 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 ).
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
}