Files
el/elp/src/morphology-ru.el
T
will.anderson c2cd5e01e1
El SDK CI - dev / build-and-test (pull_request) Successful in 3m34s
fix: elb macOS OpenSSL + C master declarations header; add ELP missing imports
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
2026-05-08 19:44:31 -05:00

922 lines
40 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}