Module:ru-adjective
Jump to navigation
Jump to search
- The following documentation is located at Module:ru-adjective/documentation. [edit] Categories were auto-generated by Module:module categorization. [edit]
- Useful links: subpage list • links • transclusions • testcases • sandbox
This module implements {{ru-decl-adj}}
and {{ru-decl-adj-irreg}}
, as well as {{ru-generate-adj-forms}}
. It is not meant to be invoked directly from user code. If you have questions about it, please contact User:Benwing2.
--[=[
This module contains functions for creating inflection tables for Russian
adjectives.
Author: Benwing, rewritten from early version by Wikitiki89
Arguments:
1: lemma; nom sg, or just the stem if an explicit declension type is
given in arg 2
2: declension type (usually omitted to autodetect based on the lemma),
along with any short accent type and optional irregular short stem;
see below.
CASE_NUMGEN: Override a given form; see abbreviations below
suffix: any suffix to attach unchanged to the end of each form
notes: Notes to add to the end of the table
title: Override the title
shorttail: Footnote (e.g. *, 1, 2, etc.) to add to short forms if
there's more than one; automatically superscripted
shorttailall: Same as shorttail= but applies to all short forms even
if there's only one
CASE_NUMGEN_tail: Like shorttailall but only for a specific form
nofull: Short forms only
Case abbreviations:
nom: nominative
gen: genitive
dat: dative
acc: accusative
ins: instrumental
pre: prepositional
short: short form
Number/gender abbreviations:
m: masculine singular
n: neuter singular
f: feminine singular
p: plural
mp: masculine plural (old-style and special numeral tables only)
fp: masculine plural (special numeral tables only)
Animacy abbreviations:
an: animate
in: inanimate
Declension-type argument (arg 2):
Form is DECLSPEC or DECLSPEC,DECLSPEC,... where DECLSPEC is
one of the following:
DECLTYPE:SHORTACCENT:SHORTSTEM
DECLTYPE:SHORTACCENT
DECLTYPE
SHORTACCENT:SHORTSTEM
SHORTACCENT
(blank)
DECLTYPE should normally be omitted, and the declension autodetected
from the ending; or it should be ь, to indicate that an adjective
in -ий is of the possessive variety with an extra -ь- in most of
the endings. Alternatively, it can be an explicit declension type,
in which case the lemma field needs to be replaced with the bare
stem; the following are the possibilities:
ый ий ой ьий short mixed manual (new-style)
ый ій ьій short stressed-short mixed proper stressed-proper
ъ-short ъ-stressed-short ъ-mixed ъ-proper ъ-stressed-proper
manual (old-style, where ъ-* is a synonym for *, for any *)
SHORTACCENT is one of a a' b b' c c' c'' to auto-generate the
short forms with the specified accent pattern (following
Zaliznyak); if omitted, no short forms will be auto-generated.
SHORTACCENT can contain a * in it for "reducible" adjectives,
where the short masculine singular has an epenthetic vowel
in the final syllable. It can also contain (1) or (2) to
indicate special cases. Both are used in conjunction with
adjectives in -нный/-нний. Special case (1) causes the
short masculine singular to end in -н instead of -нн; special
case (2) causes all short forms to end this way.
SHORTSTEM, if present, is used as the short stem to base the
short forms off of, instead of the normal adjective long stem
(possibly with a final-syllable accent added in the case of
declension type -о́й).
TODO:
1. Figure out what the symbol X-inside-square (⊠) means, which seems to go with
all adjectives in -о́й with multi-syllabic stems. It may mean that the
masculine singular short form is missing. If this indeed a regular thing,
we need to implement it (and if it's regular but means something else,
we need to implement that, too). Also figure out what the other signs
on pages 68-76 etc. mean: -, X, ~, П₂, Р₂, diamond (♢), triangle (△; might
simply mean a misc. irregularity; explained on p. 61).
2. Mark irregular overridden forms with △ (esp. appropriate for short forms).
3. Should non-reducible adjectives in -нный and -нний default to special case
(1)?
4. In the case of a non-dereducible short masc sing of stress type b, we don't
currently move the stress to the last syllable. Should we?
5. FIXME: In decline(), we used to default acc_n to nom_n. Now we do that in
handle_forms_and_overrides(). Verify that this is more correct.
6. FIXME: Allow multiple heads, both to handle cases where two manual translits
exist (or rather, one automatic and one manual), and cases where two
stresses are possible, e.g. мину́вший or ми́нувший.
7. FIXME: Add code to generate the regular comparative. The rules are as
follows: If the stem doesn't end in к г х, add -ee with no stress change
if the short form is type a, else add -е́е (including type a'). If the stem
ends in к г х, turn the last consonant into ч ж ш, add -е, and place the
stress on the syllable preceding the ending. (E.g. дорого́й -> доро́же)
]=]--
local m_utilities = require("Module:utilities")
local m_links = require("Module:links")
local m_table = require("Module:table")
local com = require("Module:ru-common")
local nom = require("Module:ru-nominal")
local strutils = require("Module:string utilities")
local m_table_tools = require("Module:table tools")
local m_debug = require("Module:debug")
local export = {}
local lang = require("Module:languages").getByCode("ru")
----------------------------- Utility functions ------------------------
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local usub = mw.ustring.sub
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- Insert a single form (consisting of {RUSSIAN, TR}) or a list of such
-- forms into an existing list of such forms, adding NOTESYM (a string or nil)
-- to the new forms if not nil. Return whether an insertion was performed.
local function insert_forms_into_existing_forms(existing, newforms, notesym)
if type(newforms) ~= "table" then
newforms = {newforms}
end
local inserted = false
for _, item in ipairs(newforms) do
if not m_table.contains(existing, item) then
if notesym then
item = com.concat_paired_russian_tr(item, {notesym})
end
table.insert(existing, item)
inserted = true
end
end
return inserted
end
local function track(page)
m_debug.track("ru-adjective/" .. page)
return true
end
-- Fancy version of ine() (if-not-empty). Converts empty string to nil,
-- but also strips leading/trailing space and then single or double quotes,
-- to allow for embedded spaces.
local function ine(arg)
if not arg then return nil end
arg = rsub(arg, "^%s*(.-)%s*$", "%1")
if arg == "" then return nil end
local inside_quotes = rmatch(arg, '^"(.*)"$')
if inside_quotes then
return inside_quotes
end
inside_quotes = rmatch(arg, "^'(.*)'$")
if inside_quotes then
return inside_quotes
end
return arg
end
-------------------- Global declension/case/etc. variables -------------------
-- 'enable_categories' is a special hack for testing, which disables all
-- category insertion if false. Delete this as soon as we've verified the
-- working of the category code and created all the necessary categories.
local enable_categories = true
local declensions = {}
local declensions_old = {}
local internal_notes_table = {}
local internal_notes_table_old = {}
local internal_notes_genders = {}
local internal_notes_genders_old = {}
local short_declensions = {}
local short_declensions_old = {}
-- Formerly used for the ой-rare type, which has been removed; but keep
-- the code around in case we need it later.
local short_internal_notes_table = {}
local short_internal_notes_table_old = {}
local short_stress_patterns = {}
local long_cases = {
"nom_m", "nom_n", "nom_f", "nom_p",
"gen_m", "gen_f", "gen_p",
"dat_m", "dat_f", "dat_p",
"acc_m_an", "acc_m_in", "acc_p_an", "acc_p_in", "acc_f", "acc_n",
"ins_m", "ins_f", "ins_p",
"pre_m", "pre_f", "pre_p",
-- extra old long cases
"nom_mp",
"acc_mp_an", "acc_mp_in",
-- extra dva cases
"nom_fp",
"acc_fp_an", "acc_fp_in",
-- extra oba cases
"gen_mp", "gen_fp",
"dat_mp", "dat_fp",
"ins_mp", "ins_fp",
"pre_mp", "pre_fp",
-- extra cases for compounds of два
"acc_mp", "acc_fp",
}
-- Short cases and corresponding numbered arguments
local short_cases = {
"short_m", "short_n", "short_f", "short_p"
}
-- Create master list of all possible cases (actually case/number/gender pairs)
local all_cases = mw.clone(long_cases)
for _, case in ipairs(short_cases) do
m_table.insertIfNot(all_cases, case)
end
-- If enabled, compare this module with new version of module to make
-- sure all declensions are the same.
local test_new_ru_adjective_module = false
-- Forward references to functions
local tracking_code
local categorize
local detect_stem_and_accent_type
local construct_bare_and_short_stem
local decline
local canonicalize_override
local handle_forms_and_overrides
local decline_short
local make_table
--------------------------------------------------------------------------
-- Main code --
--------------------------------------------------------------------------
-- Implementation of main entry point
function export.do_generate_forms(args, old, manual)
local orig_args
if test_new_ru_adjective_module then
orig_args = mw.clone(args)
end
if args[3] or args[4] or args[5] or args[6] then
error("Numbered short forms no longer supported")
end
if args.shorttailall then
track("shorttailall")
end
local SUBPAGENAME = mw.title.getCurrentTitle().subpageText
args.forms = {}
args.categories = {}
old = old or args.old
args.old = old
args.suffix = com.split_russian_tr(args.suffix or "", "dopair")
args.internal_notes = {}
-- Superscript footnote marker at beginning of note, similarly to what's
-- done at end of forms.
if args.notes then
local notes, entry = m_table_tools.get_initial_notes(args.notes)
args.notes = notes .. entry
end
local overall_short_forms_allowed
manual = manual or args[2] == "manual"
args.manual = manual
local decl_types = manual and "$" or args[2] or ""
local lemmas = manual and "-" or args[1] or SUBPAGENAME
local normal_short_classes, rare_short_classes, dated_short_classes
local saw_no_short
for _, lemma_and_tr in ipairs(rsplit(lemmas, ",")) do
-- reset these for each lemma so we get the short classes of the last
-- lemma (doesn't really matter, as they should be the same for all
-- lemmas)
normal_short_classes = {}
rare_short_classes = {}
dated_short_classes = {}
saw_no_short = false
for _, decl_type in ipairs(rsplit(decl_types, ",")) do
local lemma, lemmatr = com.split_russian_tr(lemma_and_tr)
-- if lemma ends with -ся, strip it and act as if suffix=ся given
-- (or rather, prepend ся to suffix)
local active_base = rmatch(lemma, "^(.*)ся$")
if active_base then
lemma = active_base
lemmatr = com.strip_tr_ending(lemmatr, "ся")
args.refl = true
args.real_suffix = com.concat_paired_russian_tr({"ся"}, args.suffix)
else
args.refl = false
args.real_suffix = args.suffix
end
-- Auto-detect actual decl type, and get short accent and overriding
-- short stem, if specified.
local stem, stemtr, short_accent, short_stem, short_stemtr, datedrare
stem, stemtr, decl_type, short_accent, short_stem, datedrare =
detect_stem_and_accent_type(lemma, lemmatr, decl_type, args)
if rfind(decl_type, "^[іи]й$") and rfind(stem, "[" .. com.velar .. com.sib .. "]$") then
decl_type = "ый"
end
if datedrare == "none" then
saw_no_short = true
end
local proper_decl = m_table.contains({"proper", "stressed-proper"}, decl_type)
-- Use args.real_surname to avoid overwriting args.surname, since we may be overwriting
-- `args` multiple times. FIXME: Use a separate data structure for this.
args.real_surname = proper_decl or args.surname
local short_title
if short_accent then
short_title = short_accent
elseif args.real_surname then
short_title = "surname"
elseif m_table.contains({"ьій", "ьий", "ьей", "short", "stressed-short", "й-short", "mixed"}, decl_type) then
short_title = "possessive"
end
if short_title then
if datedrare == "dated" then
m_table.insertIfNot(dated_short_classes, short_title)
elseif datedrare == "rare" then
m_table.insertIfNot(rare_short_classes, short_title)
else
m_table.insertIfNot(normal_short_classes, short_title)
end
end
if short_stem then
short_stem, short_stemtr = com.split_russian_tr(short_stem)
end
stem, args.allow_unaccented = rsubb(stem, "^%*", "")
if args.allow_unaccented then
track("allow-unaccented")
end
-- Treat suffixes without an accent, and suffixes with an accent on
-- the initial hyphen, as if they were preceded with a *, which
-- overrides all the logic that normally (a) normalizes the accent,
-- and (b) complains about multisyllabic words without an accent.
-- Don't do this if lemma is just -, which is used specially in
-- manual declension tables.
if lemma ~= "-" and (rfind(lemma, "^%-́") or (com.is_unstressed(lemma) and rfind(lemma, "^%-"))) then
args.allow_unaccented = true
end
if not args.allow_unaccented and com.needs_accents(lemma) then
-- Technically we don't need accents in -ой adjectives (which are
-- always ending-stressed) and in -ый adjectives with a monosyllabic
-- stem, such as полный (which are always stem-stressed), but it's
-- better to enforce accents in all multisyllabic words, for
-- consistency with nouns and verbs. Note that we still don't
-- require an accent in monosyllabic adjectives such as злой.
error("Lemma must have an accent in it: " .. lemma)
end
-- Set stem and unstressed version. Also construct end-accented version
-- of stem if unstressed; needed for short forms of adjectives of
-- type -о́й. We do this here before doing the dereduction
-- transformation so that we don't end up stressing an unstressed
-- epenthetic vowel, and so that the previous syllable instead ends
-- up stressed (in type -о́й adjectives the stem won't have any stress).
-- Note that the closest equivalent in nouns is handled in
-- attach_unstressed(), which puts the stress onto the final syllable
-- if the stress pattern calls for ending stress in the genitive
-- plural. This works there because
-- (1) It won't stress an unstressed epenthetic vowel because the
-- cases where the epenthetic vowel is unstressed are precisely
-- those with stem stress in the gen pl, not ending stress;
-- (2) There isn't a need to stress the syllable preceding an
-- unstressed epenthetic vowel because that syllable should
-- already have stress, since we require that the base stem form
-- (parameter 2) have stress in it whenever any case form has
-- stem stress. This isn't the case here in type -о́й adjectives.
-- NOTE: FIXME: I can't actually quote any forms from Zaliznyak
-- (at least not from pages 58-60, where this is discussed) that
-- seem to require last-stem-syllable-stress, i.e. where the stem is
-- multisyllabic. The two examples given are both unusual: дорого́й
-- has stress on the initial syllable до́рог etc. and these forms are
-- marked with a triangle (indicating an apparent irregularity); and
-- голубо́й seems not to have a masculine singular short form, and it's
-- accent pattern b, so the remaining forms are all ending-stressed.
-- In fact it's possible that these examples don't exist: It appears
-- that all the multisyllabic adjectives in -о́й listed in the
-- dictionary have a marking next to them consisting of an X inside of
-- a square, which is glossed p. 69 to something I don't understand,
-- but may be saying that the masculine singular short form is
-- missing. If this is regular, we need to implement it.
args.stem, args.stemtr = stem, stemtr
args.ustem, args.ustemtr = com.make_unstressed_once(stem, stemtr)
local accented_stem, accented_stemtr = stem, stemtr
if not args.allow_unaccented then
if accented_stemtr and com.is_unstressed(accented_stem) ~= com.is_unstressed(accented_stemtr) then
error("Stem " .. accented_stem .. " and translit " .. accented_stemtr .. " must have same accent pattern")
end
if com.is_unstressed(accented_stem) then
accented_stem, accented_stemtr =
com.make_ending_stressed(accented_stem, accented_stemtr)
end
end
local short_forms_allowed = manual and true or decl_type == "ый" or
decl_type == "ой" or decl_type == (old and "ій" or "ий")
overall_short_forms_allowed = overall_short_forms_allowed or
short_forms_allowed
if not short_forms_allowed then
-- FIXME: We might want to allow this in case we have a
-- reducible short, mixed or proper possessive adjective. But
-- in that case we need to reduce rather than dereduce to get
-- the stem.
if short_accent or short_stem then
error("Cannot specify short accent or short stem with declension type " .. decl_type .. ", as short forms aren't allowed")
end
if args.short_m or args.short_f or args.short_n or args.short_p then
error("Cannot specify explicit short forms with declension type " .. decl_type .. ", as short forms aren't allowed")
end
end
local orig_short_accent = short_accent
local short_decl_type
short_accent, short_decl_type = construct_bare_and_short_stem(args,
short_accent, short_stem, short_stemtr, accented_stem,
accented_stemtr, old, decl_type)
local decls = old and declensions_old or declensions
local short_decls = old and short_declensions_old or short_declensions
if not decls[decl_type] then
error("Unrecognized declension type " .. decl_type)
end
if short_accent == "" then
error("Short accent type cannot be blank, should be omitted or given")
end
if short_accent and not short_stress_patterns[short_accent] then
error("Unrecognized short accent type " .. short_accent)
end
tracking_code(decl_type, args, orig_short_accent, short_accent,
short_stem, datedrare, short_forms_allowed)
if not manual and enable_categories then
categorize(decl_type, args, orig_short_accent, short_accent,
short_stem)
end
decline(args, decls[decl_type], m_table.contains({"ой", "ьей", "й-short", "stressed-short", "stressed-proper"}, decl_type))
if short_forms_allowed and short_accent then
decline_short(args, short_decls[short_decl_type],
short_stress_patterns[short_accent], datedrare)
end
local intable = old and internal_notes_table_old or internal_notes_table
local shortintab = old and short_internal_notes_table_old or
short_internal_notes_table
local internal_note = intable[decl_type] or shortintab[short_decl_type]
if internal_note then
m_table.insertIfNot(args.internal_notes, internal_note)
end
end
end
local short_class_titles = {}
local function make_short_class_title(short_classes, datedrare)
local sct = table.concat(short_classes, ",") -- short class title
if sct == "" then
return
end
if not m_table.contains({"surname", "possessive"}, sct) then
-- Convert e.g. a*,a(1) into a*[(1)], either finally or followed by
-- comma.
sct = rsub(sct, "([abc]'*)%*,%1%*?(%([12]%))$", "%1*[%2]")
sct = rsub(sct, "([abc]'*)%*,%1%*?(%([12]%)),", "%1*[%2],")
-- Same for a(1),a*.
sct = rsub(sct, "([abc]'*)%*?(%([12]%)),%1%*$", "%1*[%2]")
sct = rsub(sct, "([abc]'*)%*?(%([12]%)),%1%*,", "%1*[%2],")
-- Convert (1), (2) to ①, ②.
sct = rsub(sct, "%(1%)", "①")
sct = rsub(sct, "%(2%)", "②")
-- Add a * before ①, ②, consistent with Zaliznyak.
sct = rsub(sct, "([abc]'*)([①②])", "%1*%2")
-- Convert ' and '' to ʹ and ʺ, consistent with Zaliznyak.
sct = sct:gsub("''", "ʺ"):gsub("'", "ʹ")
if args.nofull then
sct = "short-form-only class " .. sct
else
sct = "short class " .. sct
end
end
if datedrare then
sct = datedrare .. " " .. sct
end
table.insert(short_class_titles, sct)
end
make_short_class_title(normal_short_classes, nil)
make_short_class_title(rare_short_classes, "rare")
make_short_class_title(dated_short_classes, "dated")
if #short_class_titles == 0 then
if saw_no_short then
args.short_class_title = "no short forms"
else
args.short_class_title = "unknown short forms"
end
else
args.short_class_title = table.concat(short_class_titles, " / ")
end
handle_forms_and_overrides(args, overall_short_forms_allowed)
-- Test code to compare existing module to new one.
if test_new_ru_adjective_module then
local m_new_ru_adjective = require("Module:User:Benwing2/ru-adjective")
local newargs = m_new_ru_adjective.do_generate_forms(orig_args, old, manual)
local difdecl = false
for _, case in ipairs(all_cases) do
local arg = args[case]
local newarg = newargs[case]
if not m_table.deepEquals(arg, newarg) then
-- Uncomment this to display the particular case and
-- differing forms.
--error(case .. " " .. (arg and com.concat_forms(arg) or "nil") .. " || " .. (newarg and com.concat_forms(newarg) or "nil"))
track("different-decl")
difdecl = true
end
break
end
if not difdecl then
track("same-decl")
end
end
return args
end
local function process_params(frame, include_form)
local boolean = {type = "boolean"}
local params = {
[1] = true,
[2] = true,
suffix = true,
title = true,
special = true,
shorttail = true,
shorttailall = true,
notes = true,
old = boolean,
noneuter = boolean,
nofull = boolean,
surname = boolean,
pronoun = boolean
}
if include_form then
params.form = {required = true}
end
for _, case in ipairs(all_cases) do
params[case] = true
params[case .. "_tail"] = true
params[case .. "_tailall"] = true
end
return require("Module:parameters").process(frame:getParent().args, params)
end
-- Implementation of main entry point
local function do_show(frame, old, manual)
local args = export.do_generate_forms(process_params(frame), old, manual)
return make_table(args) .. m_utilities.format_categories(args.categories, lang)
end
-- The main entry point for modern declension tables.
function export.show(frame)
return do_show(frame, false)
end
-- The main entry point for old declension tables.
function export.show_old(frame)
return do_show(frame, true)
end
-- The main entry point for manual declension tables.
function export.show_manual(frame)
return do_show(frame, false, "manual")
end
-- The main entry point for manual old declension tables.
function export.show_manual_old(frame)
return do_show(frame, true, "manual")
end
-- Entry point for use in Module:ru-noun.
function export.get_nominal_decl(decl, gender, old)
local d = old and declensions_old[decl] or declensions[decl]
local n = {}
if gender == "m" then
n.nom_sg = d.nom_m
n.gen_sg = d.gen_m
n.dat_sg = d.dat_m
n.ins_sg = d.ins_m
n.pre_sg = d.pre_m
elseif gender == "f" then
n.nom_sg = d.nom_f
n.gen_sg = d.gen_f
n.dat_sg = d.dat_f
n.acc_sg = d.acc_f
n.ins_sg = d.ins_f
n.pre_sg = d.pre_f
elseif gender == "n" then
n.nom_sg = d.nom_n
n.gen_sg = d.gen_m
n.dat_sg = d.dat_m
n.acc_sg = d.acc_n
n.ins_sg = d.ins_m
n.pre_sg = d.pre_m
else
assert(false, "Unrecognized gender: " .. gender)
end
n.nom_pl = d.nom_p
n.gen_pl = d.gen_p
n.dat_pl = d.dat_p
n.ins_pl = d.ins_p
n.pre_pl = d.pre_p
if gender == "m" and d.nom_mp then
n.nom_pl = d.nom_mp
end
local intable = old and internal_notes_table_old or internal_notes_table
local ingenders = old and internal_notes_genders_old or internal_notes_genders
-- FIXME, what if there are multiple internal notes? See comment in
-- do_generate_forms().
local internal_notes = ingenders[decl] and m_table.contains(ingenders[decl], gender) and intable[decl]
return n, internal_notes
end
local function get_form(forms)
local canon_forms = {}
for _, form in ipairs(forms) do
local ru, tr = form[1], form[2]
local ruentry = m_table_tools.get_notes(ru)
local trentry, trnotes
if tr then
trentry, trnotes = m_table_tools.get_notes(tr)
end
ruentry = m_links.remove_links(ruentry)
m_table.insertIfNot(canon_forms, {ruentry, trentry})
end
return com.concat_forms(canon_forms)
end
-- The entry point for 'ru-adj-forms' to generate all adjective forms.
function export.generate_forms(frame)
local args = export.do_generate_forms(process_params(frame), false)
local ins_text = {}
for _, case in ipairs(all_cases) do
if args[case] then
table.insert(ins_text, case .. "=" .. get_form(args[case]))
end
end
return table.concat(ins_text, "|")
end
-- The entry point for 'ru-adj-form' to generate a particular adjective form.
function export.generate_form(frame)
local args = process_params(frame, "include form")
local form = args.form
if not m_table.contains(all_cases, form) then
error("Unrecognized form " .. form)
end
args = export.do_generate_forms(args, false)
if not args[form] then
return ""
else
return get_form(args[form])
end
end
--------------------------------------------------------------------------
-- Tracking and categorization --
--------------------------------------------------------------------------
tracking_code = function(decl_class, args, orig_short_accent, short_accent,
short_stem, datedrare)
local hint_types = com.get_stem_trailing_letter_type(args.stem)
local function dotrack(prefix)
if prefix ~= "" then
track(prefix)
prefix = prefix .. "/"
end
track(prefix .. decl_class)
for _, hint_type in ipairs(hint_types) do
track(prefix .. hint_type)
track(prefix .. decl_class .. "/" .. hint_type)
end
end
dotrack("")
if args.short_m or args.short_f or args.short_n or args.short_p then
dotrack("short")
end
if orig_short_accent then
if rfind(orig_short_accent, "%*") then
dotrack("reducible")
dotrack("reducible/" .. short_accent)
end
if rfind(orig_short_accent, "%(1%)") then
dotrack("special-case-1")
dotrack("special-case-1/" .. short_accent)
end
if rfind(orig_short_accent, "%(2%)") then
dotrack("special-case-2")
dotrack("special-case-2/" .. short_accent)
end
end
if short_accent then
dotrack("short-accent/" .. short_accent)
if datedrare == "dated" then
dotrack("short-accent-dated")
dotrack("short-accent-dated/" .. short_accent)
elseif datedrare == "rare" then
dotrack("short-accent-rare")
dotrack("short-accent-rare/" .. short_accent)
end
elseif datedrare == "none" then
dotrack("short-accent/none")
else
dotrack("short-accent/unknown")
end
if short_stem then
dotrack("explicit-short-stem")
dotrack("explicit-short-stem/" .. short_accent)
end
for _, case in ipairs(all_cases) do
if args[case] then
track("irreg/" .. case)
-- questionable use: dotrack("irreg/" .. case)
end
end
end
-- Insert the category CAT (a string) into list CATEGORIES. String will
-- have "Russian " prepended and ~ substituted for the part of speech.
local function insert_category(categories, cat, plpos)
table.insert(categories, "Russian " .. rsub(cat, "~", plpos))
end
categorize = function(decl_type, args, orig_short_accent, short_accent,
short_stem)
local plpos = args.real_surname and "surnames" or args.pronoun and "pronouns" or "adjectives"
-- Insert category CAT into the list of categories in ARGS.
local function insert_cat(cat)
insert_category(args.categories, cat, plpos)
end
-- FIXME: For compatibility with old {{temp|ru-adj7}}, {{temp|ru-adj8}},
-- {{temp|ru-adj9}}; maybe there's a better way.
if m_table.contains({"ьій", "ьий", "ьей", "short", "stressed-short", "й-short", "mixed",
"proper", "stressed-proper"}, decl_type) then
insert_cat("possessive ~")
end
if not args.pronoun then
if m_table.contains({"ьій", "ьий", "ьей"}, decl_type) then
insert_cat("long possessive ~")
elseif m_table.contains({"short", "stressed-short", "й-short"}, decl_type) then
insert_cat("short possessive ~")
elseif m_table.contains({"mixed"}, decl_type) then
insert_cat("mixed possessive ~")
elseif m_table.contains({"proper", "stressed-proper"}, decl_type) then
-- Don't insert a category like [[:Category:Russian proper-name surnames]]. Instead, rely on
-- [[:Category:Russian possessive surnames]] generated above.
if not args.real_surname then
insert_cat("proper-name ~")
end
elseif decl_type == "$" then
insert_cat("indeclinable ~")
else
local hint_types = com.get_stem_trailing_letter_type(args.stem)
-- insert English version of Zaliznyak stem type
local stem_type =
m_table.contains(hint_types, "velar") and "velar-stem" or
m_table.contains(hint_types, "sibilant") and "sibilant-stem" or
m_table.contains(hint_types, "c") and "ц-stem" or
m_table.contains(hint_types, "i") and "i-stem" or
m_table.contains(hint_types, "vowel") and "vowel-stem" or
m_table.contains(hint_types, "soft-cons") and "vowel-stem" or
m_table.contains(hint_types, "palatal") and "vowel-stem" or
decl_type == "ий" and "soft-stem" or
"hard-stem"
if stem_type == "soft-stem" or stem_type == "vowel-stem" then
insert_cat(stem_type .. " ~")
else
insert_cat(stem_type .. " " .. (m_table.contains({"ой", "ьей", "й-short"}, decl_type) and "ending-stressed" or "stem-stressed") .. " ~")
end
end
if m_table.contains({"ой", "ьей", "й-short"}, decl_type) then
insert_cat("ending-stressed ~")
end
end
local short_forms_allowed = m_table.contains({"ый", "ой", "ій", "ий"}, decl_type)
if short_forms_allowed then
local override_m = args.short_m
local override_f = args.short_f
local override_n = args.short_n
local override_p = args.short_p
local has_short = short_accent or override_m or override_f or
override_n or override_p
local missing_short = override_m == "-" or
override_f == "-" or override_n == "-" or override_p == "-" or
not short_accent and (not override_m or not override_f or
not override_n or not override_p)
if has_short then
insert_cat("~ with short forms")
if missing_short then
insert_cat("~ with missing short forms")
end
end
if short_accent then
insert_cat("~ with short accent pattern " .. (short_accent:gsub("''", "ʺ"):gsub("'", "ʹ")))
end
if orig_short_accent then
if rfind(orig_short_accent, "%*") then
insert_cat("~ with reducible short stem")
end
if rfind(orig_short_accent, "%(1%)") then
insert_cat("~ with Zaliznyak short form special case 1")
end
if rfind(orig_short_accent, "%(2%)") then
insert_cat("~ with Zaliznyak short form special case 2")
end
end
if short_stem and short_stem ~= args.stem then
insert_cat("~ with irregular short stem")
end
end
for _, case in ipairs(all_cases) do
if args[case] and args[case] ~= "-" then
local engcase = rsub(case, "^([a-z]*)", {
nom="nominative", gen="genitive", dat="dative",
acc="accusative", ins="instrumental", pre="prepositional",
short="short",
})
engcase = rsub(engcase, "(_[a-z]*)$", {
_m=" masculine singular", _f=" feminine singular",
_n=" neuter singular", _p=" plural",
_mp=" masculine plural"
})
insert_cat("~ with irregular " .. engcase)
end
end
if args.nofull then
insert_cat("short-form-only ~")
end
end
--------------------------------------------------------------------------
-- Autodetection and stem munging --
--------------------------------------------------------------------------
-- Attempt to detect the type of the lemma based on its ending, separating
-- off the base and the ending; also extract the accent type for short
-- adjectives and optional short stem. DECL is the value passed in, and
-- might already specify the ending. Return five values: STEM, STEMTR, DECL,
-- SHORT_ACCENT (accent class of short adjective, or nil for no short
-- adjectives other than specified through overrides), SHORT_STEM (special
-- stem of short adjective, nil if same as long stem). The return value of
-- SHORT_STEM is taken directly from the argument and will include any
-- manual translit.
detect_stem_and_accent_type = function(lemma, tr, decl, args)
local datedrare = false
-- If it looks like a short decl type, canonicalize. [abc] for types
-- a, b, c'', etc.; [d] for dated-*; [r] for rare-*;
-- [-] for - (no short forms); [*(] for * and (1) and (2) modifiers,
-- which might precede the letter.
if rfind(decl, "^[abcdr*(-]") then
decl = ":" .. decl
end
local splitvals = rsplit(decl, ":")
if #splitvals > 3 then
error("Should be at most three colon-separated parts of a declension spec: " .. decl)
end
local short_accent, short_stem
decl, short_accent, short_stem = splitvals[1], splitvals[2], splitvals[3]
-- Check for dated variant of short accent
local dated_short = short_accent and rmatch(short_accent, "^dated%-(.*)$")
if dated_short then
datedrare = "dated"
short_accent = dated_short
else
-- Check for rare variant of short accent
local rare_short = short_accent and rmatch(short_accent, "^rare%-(.*)$")
if rare_short then
datedrare = "rare"
short_accent = rare_short
end
end
decl = ine(decl)
-- Resolve aliases
if decl then
decl = rsub(decl, "^ъ%-", "")
end
if short_accent == "-" then
short_accent = nil
datedrare = "none"
end
short_accent = ine(short_accent)
short_stem = ine(short_stem)
if short_stem and not short_accent then
error("With explicit short stem " .. short_stem .. ", must specify short accent")
end
local base, ending
-- The while loop appears to function solely as a way of allowing a
-- jump to the end of the loop with a 'break' statement. I don't think
-- the loop is ever run more than once.
while true do
if not decl or decl == "ь" then
base, ending = rmatch(lemma, "^(.*)([ыиіое]́?й)$")
if base then
if ending == "ий" and decl == "ь" then
ending = "ьий"
elseif ending == "ій" and decl == "ь" then
ending = "ьій"
elseif ending == "е́й" or (ending == "ей" and com.is_monosyllabic(lemma)) then
ending = "ье́й"
end
tr = com.strip_tr_ending(tr, ending)
-- -ий/-ій will be converted to -ый after velars and sibilants
-- by the caller
decl = com.make_unstressed(ending)
break
else
-- It appears that short, mixed and proper adjectives are always
-- either of the -ов/ев/ёв or -ин/ын types. The former type is
-- always (or almost always?) short, while the latter can be
-- either; apparently mixed is the more "modern" type of
-- declension, and short is "older". However, both -ов/ев/ёв or
-- -ин/ын are of type "proper" (similar to "short") when
-- capitalized.
--
-- NOTE: Following regexp is accented
base = rmatch(lemma, "^([" .. com.uppercase .. "].*[иы]́н)ъ?$")
if base then
decl = "stressed-proper"
break
end
base = rmatch(lemma, "^([" .. com.uppercase .. "].*[еёо]́?в)ъ?$")
if not base then
-- Following regexp is not stressed
base = rmatch(lemma, "^([" .. com.uppercase .. "].*[иы]н)ъ?$")
end
if base then
decl = "proper"
break
end
base = rmatch(lemma, "^(.*[еёо]́?в)ъ?$")
if base then
decl = "short"
break
end
base = rmatch(lemma, "(.*[иы]́н)ъ?$") --accented
if base then
decl = "stressed-short"
break
end
base = rmatch(lemma, "(.*[иы]н)ъ?$") --unaccented
if base then
decl = "mixed"
break
-- error("With -ин/ын adjectives, must specify 'short' or 'mixed':" .. lemma)
end
error("Cannot determine stem type of adjective: " .. lemma)
end
elseif m_table.contains({"short", "stressed-short", "mixed", "proper",
"stressed-proper"}, decl) then
base = rmatch(lemma, "^(.-)ъ?$")
assert(base)
break
elseif decl == "й" or decl == "й-short" then
base = rmatch(lemma, "^(.-)й$")
assert(base)
tr = com.strip_tr_ending(tr, "й")
decl = "й-short"
break
else
base = lemma
break
end
end
if not datedrare and (args.refl or not (decl == "ый" or decl == "ий" and not rfind(base, "[цс]к$"))) then
datedrare = "none"
end
return base, tr, decl, short_accent, short_stem, datedrare
end
-- Add a possible suffix to the bare stem, according to the declension and
-- value of OLD. This may be -ь, -ъ, -й or nothing. We need to do this here
-- because we don't actually attach such a suffix in attach_unstressed() due
-- to situations where we don't want the suffix added, e.g. бескра́йний with
-- dereduced nom sg бескра́ен without expected -ь.
local function add_bare_suffix(bare, baretr, old, decl, dereduced)
if decl == "й-short" then
return bare .. "й", baretr and baretr .. "j" or nil
elseif old and decl ~= "ій" and decl ~= "$" then
return bare .. "ъ", baretr
elseif decl == "ий" or decl == "ій" then
-- This next special case is mentioned in Zaliznyak's 1980 grammatical
-- dictionary for adjectives (footnote, p. 60).
if dereduced and rfind(bare, "[нН]$") then
-- FIXME: What happens in this case old-style? I assume that
-- -ъ is added, but this is a guess.
return bare .. (old and "ъ" or ""), baretr
elseif rfind(bare, "[" .. com.vowel .. "]́?$") then
-- This happens with adjectives like длинноше́ий, short masculine
-- singular длинноше́й.
return bare .. "й", baretr and baretr .. "j" or nil
else
return bare .. "ь", baretr and baretr .. "ʹ" or nil
end
else
return bare, baretr
end
end
-- Construct and set bare and short form in args, and canonicalize
-- short accent spec, handling cases *, (1) and (2). Return canonicalized
-- short accent and the short declension, which is usually the same as
-- the corresponding long one.
construct_bare_and_short_stem = function(args, short_accent, short_stem,
short_stemtr, accented_stem, accented_stemtr, old, decl)
-- Check if short forms allowed; if not, no short-form params can be given.
-- Construct bare version of stem; used for cases where the ending
-- is non-syllabic (i.e. short masculine singular of long adjectives,
-- and masculine singular of short, mixed and proper adjectives). Comes
-- from short masculine or 3rd argument if explicitly given, else from the
-- accented stem, possibly with the dereduction transformation applied
-- (if * occurs in the short accent spec).
local reducible, sc1, sc2
if short_accent then
short_accent, reducible = rsubb(short_accent, "%*", "")
short_accent, sc1 = rsubb(short_accent, "%(1%)", "")
short_accent, sc2 = rsubb(short_accent, "%(2%)", "")
end
if sc1 or sc2 then
-- Reducible isn't compatible with sc1 or sc2, but Zaliznyak's
-- dictionary always seems to notate sc1 and sc2 with reducible *,
-- so ignore it.
reducible = false
end
if sc1 and sc2 then
error("Special cases 1 and 2, i.e. (1) and (2), not compatible")
end
local short_decl = decl
-- Construct short stem. May be explicitly given, else comes from
-- end-accented stem.
if not short_stem then
short_stem, short_stemtr = accented_stem, accented_stemtr
end
-- Try to accent unaccented short stem (happens only when explicitly given),
-- but be conservative -- only if monosyllabic, since otherwise we have
-- no idea where stress should end up; after all, the explicit short stem
-- is for exceptional cases.
if not args.allow_unaccented then
if short_stemtr and com.is_unstressed(short_stem) ~= com.is_unstressed(short_stemtr) then
error("Explicit short stem " .. short_stem .. " and translit " .. short_stemtr .. " must have same accent pattern")
end
if com.is_monosyllabic(short_stem) then
short_stem, short_stemtr = com.make_ending_stressed(short_stem, short_stemtr)
elseif com.needs_accents(short_stem) then
error("Explicit short stem " .. short_stem .. " needs an accent")
end
end
if sc2 then
if not rfind(short_stem, "нн$") then
error("With special case 2, stem needs to end in -нн: " .. short_stem)
end
short_stem = rsub(short_stem, "нн$", "н")
if short_stemtr then
if not rfind(short_stemtr, "nn$") then
error("With special case 2, stem translit needs to end in -nn: " .. short_stemtr)
end
short_stemtr = rsub(short_stemtr, "nn$", "n")
end
end
-- Construct bare form, used for short masculine.
local bare, baretr
if reducible then
bare, baretr = com.dereduce_stem(short_stem, short_stemtr, rfind(short_accent, "^b"))
if not bare then
error("Unable to dereduce stem: " .. short_stem)
end
bare, baretr = add_bare_suffix(bare, baretr, old, decl, true)
else
bare, baretr = short_stem, short_stemtr
if sc1 then
if not rfind(bare, "нн$") then
error("With special case 1, stem needs to end in -нн: " .. bare)
end
bare = rsub(bare, "нн$", "н")
if baretr then
if not rfind(baretr, "nn$") then
error("With special case 1, stem translit needs to end in -nn: " .. baretr)
end
baretr = rsub(baretr, "nn$", "n")
end
end
-- With special case 1 or 2, we don't ever want -ь added, so treat
-- it like a reducible (that may be why these are marked as
-- reducible in Zaliznyak).
bare, baretr = add_bare_suffix(bare, baretr, old, decl, sc1 or sc2)
end
args.short_stem, args.short_stemtr = short_stem, short_stemtr
args.short_ustem, args.short_ustemtr = com.make_unstressed_once(short_stem, short_stemtr)
args.bare, args.baretr = bare, baretr
return short_accent, short_decl
end
--------------------------------------------------------------------------
-- Declensions --
--------------------------------------------------------------------------
declensions["ый"] = {
["nom_m"] = "ый",
["nom_n"] = "ое",
["nom_f"] = "ая",
["nom_p"] = "ые",
["gen_m"] = "ого",
["gen_f"] = "ой",
["gen_p"] = "ых",
["dat_m"] = "ому",
["dat_f"] = "ой",
["dat_p"] = "ым",
["acc_f"] = "ую",
["acc_n"] = "ое",
["ins_m"] = "ым",
["ins_f"] = {"ой", "ою"},
["ins_p"] = "ыми",
["pre_m"] = "ом",
["pre_f"] = "ой",
["pre_p"] = "ых",
}
declensions["ий"] = {
["nom_m"] = "ий",
["nom_n"] = "ее",
["nom_f"] = "яя",
["nom_p"] = "ие",
["gen_m"] = "его",
["gen_f"] = "ей",
["gen_p"] = "их",
["dat_m"] = "ему",
["dat_f"] = "ей",
["dat_p"] = "им",
["acc_f"] = "юю",
["acc_n"] = "ее",
["ins_m"] = "им",
["ins_f"] = {"ей", "ею"},
["ins_p"] = "ими",
["pre_m"] = "ем",
["pre_f"] = "ей",
["pre_p"] = "их",
}
declensions["ой"] = {
["nom_m"] = "о́й",
["nom_n"] = "о́е",
["nom_f"] = "а́я",
["nom_p"] = "ы́е",
["gen_m"] = "о́го",
["gen_f"] = "о́й",
["gen_p"] = "ы́х",
["dat_m"] = "о́му",
["dat_f"] = "о́й",
["dat_p"] = "ы́м",
["acc_f"] = "у́ю",
["acc_n"] = "о́е",
["ins_m"] = "ы́м",
["ins_f"] = {"о́й", "о́ю"},
["ins_p"] = "ы́ми",
["pre_m"] = "о́м",
["pre_f"] = "о́й",
["pre_p"] = "ы́х",
}
-- These can be stressed in ничья́ "draw, tie (in sports)".
declensions["ьий"] = {
["nom_m"] = "и́й",
["nom_n"] = "ье́",
["nom_f"] = "ья́",
["nom_p"] = "ьи́",
["gen_m"] = "ье́го",
["gen_f"] = "ье́й",
["gen_p"] = "ьи́х",
["dat_m"] = "ье́му",
["dat_f"] = "ье́й",
["dat_p"] = "ьи́м",
["acc_f"] = "ью́",
["acc_n"] = "ье́",
["ins_m"] = "ьи́м",
["ins_f"] = {"ье́й", "ье́ю"},
["ins_p"] = "ьи́ми",
["pre_m"] = "ье́м",
["pre_f"] = "ье́й",
["pre_p"] = "ьи́х",
}
declensions["ьей"] = {
["nom_m"] = "е́й",
["nom_n"] = "ьё",
["nom_f"] = "ья́",
["nom_p"] = "ьи́",
["gen_m"] = "ьего́",
["gen_f"] = "ье́й",
["gen_p"] = "ьи́х",
["dat_m"] = "ьему́",
["dat_f"] = "ье́й",
["dat_p"] = "ьи́м",
["acc_f"] = "ью́",
["acc_n"] = "ьё",
["ins_m"] = "ьи́м",
["ins_f"] = {"ье́й", "ье́ю"},
["ins_p"] = "ьи́ми",
["pre_m"] = "ьём",
["pre_f"] = "ье́й",
["pre_p"] = "ьи́х",
}
declensions["short"] = {
["nom_m"] = "",
["nom_n"] = "о́",
["nom_f"] = "а́",
["nom_p"] = "ы́",
["gen_m"] = "а́",
["gen_f"] = "о́й",
["gen_p"] = "ы́х",
["dat_m"] = "у́",
["dat_f"] = "о́й",
["dat_p"] = "ы́м",
["acc_f"] = "у́",
["acc_n"] = "о́",
["ins_m"] = "ы́м",
["ins_f"] = {"о́й", "о́ю"},
["ins_p"] = "ы́ми",
["pre_m"] = "о́м",
["pre_f"] = "о́й",
["pre_p"] = "ы́х",
}
declensions["stressed-short"] = mw.clone(declensions["short"])
declensions["stressed-short"]["pre_m"] = "е́"
declensions["й-short"] = {
["nom_m"] = "й",
["nom_n"] = "ё",
["nom_f"] = "я́",
["nom_p"] = "и́",
["gen_m"] = "его́",
["gen_f"] = "е́й",
["gen_p"] = "и́х",
["dat_m"] = "ему́",
["dat_f"] = "е́й",
["dat_p"] = "и́м",
["acc_f"] = "ю́",
["acc_n"] = "ё",
["ins_m"] = "и́м",
["ins_f"] = {"е́й", "е́ю"},
["ins_p"] = "и́ми",
["pre_m"] = "ём",
["pre_f"] = "е́й",
["pre_p"] = "и́х",
}
declensions["mixed"] = {
["nom_m"] = "",
["nom_n"] = "о",
["nom_f"] = "а",
["nom_p"] = "ы",
["gen_m"] = {"ого", "а2"},
["gen_f"] = "ой",
["gen_p"] = "ых",
["dat_m"] = {"ому", "у2"},
["dat_f"] = "ой",
["dat_p"] = "ым",
["acc_f"] = "у",
["acc_n"] = "о",
["ins_m"] = "ым",
["ins_f"] = {"ой", "ою"},
["ins_p"] = "ыми",
["pre_m"] = "ом",
["pre_f"] = "ой",
["pre_p"] = "ых",
}
internal_notes_table["mixed"] = "<sup>2</sup> Obsolete."
internal_notes_genders["mixed"] = {"m"}
internal_notes_table_old["mixed"] = "<sup>2</sup> Obsolete."
internal_notes_genders_old["mixed"] = {"m"}
declensions["proper"] = {
["nom_m"] = "",
["nom_n"] = nil,
["nom_f"] = "а́",
["nom_p"] = "ы́",
["gen_m"] = "а́",
["gen_f"] = "о́й",
["gen_p"] = "ы́х",
["dat_m"] = "у́",
["dat_f"] = "о́й",
["dat_p"] = "ы́м",
["acc_f"] = "у́",
["acc_n"] = nil,
["ins_m"] = "ы́м",
["ins_f"] = {"о́й", "о́ю1"},
["ins_p"] = "ы́ми",
["pre_m"] = "е́",
["pre_f"] = "о́й",
["pre_p"] = "ы́х",
}
declensions["stressed-proper"] = declensions["proper"]
for _, decl in ipairs({"proper", "stressed-proper"}) do
internal_notes_table[decl] = "<sup>1</sup> Rare."
internal_notes_table_old[decl] = "<sup>1</sup> Rare."
internal_notes_genders[decl] = {"f"}
internal_notes_genders_old[decl] = {"f"}
end
declensions["$"] = {
["nom_m"] = "",
["nom_n"] = "",
["nom_f"] = "",
["nom_p"] = "",
["gen_m"] = "",
["gen_f"] = "",
["gen_p"] = "",
["dat_m"] = "",
["dat_f"] = "",
["dat_p"] = "",
["acc_f"] = "",
-- don't do this; instead we default it to nom_n
-- ["acc_n"] = "",
["ins_m"] = "",
["ins_f"] = "",
["ins_p"] = "",
["pre_m"] = "",
["pre_f"] = "",
["pre_p"] = "",
-- for old-style templates, два, оба, compounds of два
["nom_mp"] = "",
["nom_fp"] = "",
["gen_mp"] = "",
["gen_fp"] = "",
["dat_mp"] = "",
["dat_fp"] = "",
-- don't do this; instead we default them to nom_mp, nom_fp
-- ["acc_mp"] = "",
-- ["acc_fp"] = "",
["ins_mp"] = "",
["ins_fp"] = "",
["pre_mp"] = "",
["pre_fp"] = "",
}
declensions_old["ый"] = {
["nom_m"] = "ый",
["nom_n"] = "ое",
["nom_f"] = "ая",
["nom_mp"] = "ые",
["nom_p"] = "ыя",
["gen_m"] = "аго",
["gen_f"] = "ой",
["gen_p"] = "ыхъ",
["dat_m"] = "ому",
["dat_f"] = "ой",
["dat_p"] = "ымъ",
["acc_f"] = "ую",
["acc_n"] = "ое",
["ins_m"] = "ымъ",
["ins_f"] = {"ой", "ою"},
["ins_p"] = "ыми",
["pre_m"] = "омъ",
["pre_f"] = "ой",
["pre_p"] = "ыхъ",
}
declensions_old["ій"] = {
["nom_m"] = "ій",
["nom_n"] = "ее",
["nom_f"] = "яя",
["nom_mp"] = "іе",
["nom_p"] = "ія",
["gen_m"] = "яго",
["gen_f"] = "ей",
["gen_p"] = "ихъ",
["dat_m"] = "ему",
["dat_f"] = "ей",
["dat_p"] = "имъ",
["acc_f"] = "юю",
["acc_n"] = "ее",
["ins_m"] = "имъ",
["ins_f"] = {"ей", "ею"},
["ins_p"] = "ими",
["pre_m"] = "емъ",
["pre_f"] = "ей",
["pre_p"] = "ихъ",
}
declensions_old["ой"] = {
["nom_m"] = "о́й",
["nom_n"] = "о́е",
["nom_f"] = "а́я",
["nom_mp"] = "ы́е",
["nom_p"] = "ы́я",
["gen_m"] = {"а́го", "о́го"},
["gen_f"] = "о́й",
["gen_p"] = "ы́хъ",
["dat_m"] = "о́му",
["dat_f"] = "о́й",
["dat_p"] = "ы́мъ",
["acc_f"] = "у́ю",
["acc_n"] = "о́е",
["ins_m"] = "ы́мъ",
["ins_f"] = {"о́й", "о́ю"},
["ins_p"] = "ы́ми",
["pre_m"] = "о́мъ",
["pre_f"] = "о́й",
["pre_p"] = "ы́хъ",
}
declensions_old["ьій"] = {
["nom_m"] = "ій",
["nom_n"] = "ье",
["nom_f"] = "ья",
["nom_p"] = "ьи",
["gen_m"] = "ьяго",
["gen_f"] = "ьей",
["gen_p"] = "ьихъ",
["dat_m"] = "ьему",
["dat_f"] = "ьей",
["dat_p"] = "ьимъ",
["acc_f"] = "ью",
["acc_n"] = "ье",
["ins_m"] = "ьимъ",
["ins_f"] = {"ьей", "ьею"},
["ins_p"] = "ьими",
["pre_m"] = "ьемъ",
["pre_f"] = "ьей",
["pre_p"] = "ьихъ",
}
declensions_old["ьей"] = {
["nom_m"] = "е́й",
["nom_n"] = "ьё",
["nom_f"] = "ья́",
["nom_p"] = "ьи́",
["gen_m"] = "ьего́",
["gen_f"] = "ье́й",
["gen_p"] = "ьи́хъ",
["dat_m"] = "ьему́",
["dat_f"] = "ье́й",
["dat_p"] = "ьи́мъ",
["acc_f"] = "ью́",
["acc_n"] = "ьё",
["ins_m"] = "ьи́мъ",
["ins_f"] = {"ье́й", "ье́ю"},
["ins_p"] = "ьи́ми",
["pre_m"] = "ьёмъ",
["pre_f"] = "ье́й",
["pre_p"] = "ьи́хъ",
}
declensions_old["short"] = {
["nom_m"] = "ъ",
["nom_n"] = "о",
["nom_f"] = "а",
["nom_p"] = "ы",
["gen_m"] = "а",
["gen_f"] = "ой",
["gen_p"] = "ыхъ",
["dat_m"] = "у",
["dat_f"] = "ой",
["dat_p"] = "ымъ",
["acc_f"] = "у",
["acc_n"] = "о",
["ins_m"] = "ымъ",
["ins_f"] = {"ой", "ою"},
["ins_p"] = "ыми",
["pre_m"] = "омъ",
["pre_f"] = "ой",
["pre_p"] = "ыхъ",
}
declensions_old["stressed-short"] = mw.clone(declensions_old["short"])
declensions_old["stressed-short"]["pre_m"] = "ѣ́"
declensions_old["й-short"] = {
["nom_m"] = "й",
["nom_n"] = "ё",
["nom_f"] = "я́",
["nom_p"] = "и́",
["gen_m"] = "его́",
["gen_f"] = {"е́й", "ея́"},
["gen_p"] = "и́хъ",
["dat_m"] = "ему́",
["dat_f"] = "е́й",
["dat_p"] = "и́мъ",
["acc_f"] = "ю́",
["acc_n"] = "ё",
["ins_m"] = "и́мъ",
["ins_f"] = {"е́й", "е́ю"},
["ins_p"] = "и́ми",
["pre_m"] = "ёмъ",
["pre_f"] = "е́й",
["pre_p"] = "и́хъ",
}
declensions_old["mixed"] = {
["nom_m"] = "ъ",
["nom_n"] = "о",
["nom_f"] = "а",
["nom_p"] = "ы",
["gen_m"] = {"аго", "а1"},
["gen_f"] = "ой",
["gen_p"] = "ыхъ",
["dat_m"] = {"ому", "у1"},
["dat_f"] = "ой",
["dat_p"] = "ымъ",
["acc_f"] = "у",
["acc_n"] = "о",
["ins_m"] = "ымъ",
["ins_f"] = {"ой", "ою"},
["ins_p"] = "ыми",
["pre_m"] = "омъ",
["pre_f"] = "ой",
["pre_p"] = "ыхъ",
}
declensions_old["proper"] = {
["nom_m"] = "ъ",
["nom_n"] = nil,
["nom_f"] = "а́",
["nom_p"] = "ы́",
["gen_m"] = "а́",
["gen_f"] = "о́й",
["gen_p"] = "ы́хъ",
["dat_m"] = "у́",
["dat_f"] = "о́й",
["dat_p"] = "ы́мъ",
["acc_f"] = "у́",
["acc_n"] = nil,
["ins_m"] = "ы́мъ",
["ins_f"] = {"о́й", "о́ю1"},
["ins_p"] = "ы́ми",
["pre_m"] = "ѣ́",
["pre_f"] = "о́й",
["pre_p"] = "ы́хъ",
}
declensions_old["stressed-proper"] = declensions_old["proper"]
declensions_old["$"] = declensions["$"]
local function frob_genitive_masc(decl)
-- signal to combine_stem_and_suffix() to use the special tr_adj()
-- function so that -го gets transliterated to -vo
if type(decl["gen_m"]) == "table" then
local entries = {}
for _, entry in ipairs(decl["gen_m"]) do
table.insert(entries, rsub(entry, "го$", "го<adj>"))
end
decl["gen_m"] = entries
else
decl["gen_m"] = rsub(decl["gen_m"], "го$", "го<adj>")
end
end
-- Frob declensions, adding <adj> to gen_m forms ending in -го. This is
-- a signal to add manual translit early on that renders -го as -vo.
for _, decl in pairs(declensions_old) do
frob_genitive_masc(decl)
end
for _, decl in pairs(declensions) do
frob_genitive_masc(decl)
end
--------------------------------------------------------------------------
-- Declension functions --
--------------------------------------------------------------------------
local function attach_unstressed(args, suf, short)
local stem, stemtr
if short then
stem, stemtr = args.short_stem, args.short_stemtr
else
stem, stemtr = args.stem, args.stemtr
end
if suf == nil then
return nil
elseif nom.nonsyllabic_suffixes[suf] then
if not args.bare then
return nil
elseif rfind(args.bare, "[йьъ]$") then
return {args.bare, args.baretr}
elseif suf == "ъ" then
return com.concat_russian_tr(args.bare, args.baretr, suf, nil, "dopair")
else
return {args.bare, args.baretr}
end
end
suf = com.make_unstressed(suf)
local rules = nom.unstressed_rules[ulower(usub(stem, -1))]
-- The parens around the return value drop all but the first return value
return (nom.combine_stem_and_suffix(stem, stemtr, suf, rules, args.old))
end
local function attach_stressed(args, suf, short)
local ustem, ustemtr
if short then
ustem, ustemtr = args.short_ustem, args.short_ustemtr
else
ustem, ustemtr = args.ustem, args.ustemtr
end
if suf == nil then
return nil
elseif not rfind(suf, "[ё́]") then -- if suf has no "ё" or accent marks
return attach_unstressed(args, suf, short)
end
local rules = nom.stressed_rules[ulower(usub(ustem, -1))]
-- The parens around the return value drop all but the first return value
return (nom.combine_stem_and_suffix(ustem, ustemtr, suf, rules, args.old))
end
local function attach_both(args, suf, short)
local results = {}
-- Examples with stems with ё on Zaliznyak p. 61 list the
-- ending-stressed forms first, so go with that.
-- NOTE: This assumes we get one value returned, not a list of such values.
table.insert(results, attach_stressed(args, suf, short))
table.insert(results, attach_unstressed(args, suf, short))
return results
end
local function attach_with(args, suf, fun, short)
if type(suf) == "table" then
local all_combineds = {}
for _, x in ipairs(suf) do
local combineds = attach_with(args, x, fun, short)
for _, combined in ipairs(combineds) do
table.insert(all_combineds, combined)
end
end
return all_combineds
else
local funval = fun(args, suf, short)
if funval then
local tbl = {}
assert(type(funval) == "table")
if type(funval[1]) ~= "table" then
funval = {funval}
end
for _, x in ipairs(funval) do
table.insert(tbl,
com.concat_paired_russian_tr(x, args.real_suffix))
end
return tbl
else
return {}
end
end
end
local function gen_form(args, decl, case, fun)
if not args.forms[case] then
args.forms[case] = {}
end
insert_forms_into_existing_forms(args.forms[case],
attach_with(args, decl[case], fun, false))
end
decline = function(args, decl, stressed)
local attacher = stressed and attach_stressed or attach_unstressed
for _, case in ipairs(long_cases) do
gen_form(args, decl, case, attacher)
end
end
canonicalize_override = function(args, case)
local val = args[case]
if not val then
return nil
end
val = rsplit(val, "%s*,%s*")
-- auto-accent/check for necessary accents
local newvals = {}
for _, v in ipairs(val) do
local ru, tr = com.split_russian_tr(v)
if not args.allow_unaccented then
if tr and com.is_unstressed(ru) ~= com.is_unstressed(tr) then
error("Override " .. ru .. " and translit " .. tr .. " must have same accent pattern")
end
-- it's safe to accent monosyllabic stems
if com.is_monosyllabic(ru) then
ru, tr = com.make_ending_stressed(ru, tr)
elseif com.needs_accents(ru) then
error("Override " .. ru .. " for case " .. case .. " requires an accent")
end
end
table.insert(newvals, {ru, tr})
end
val = newvals
return val
end
handle_forms_and_overrides = function(args, short_forms_allowed)
local f = args.forms
local function append_note_all(case, value)
value = com.split_russian_tr(value, "dopair")
if f[case] then
for i=1,#f[case] do
f[case][i] = com.concat_paired_russian_tr(f[case][i], value)
end
end
end
local function append_note_last(case, value, gt_one)
value = com.split_russian_tr(value, "dopair")
if f[case] then
local lastarg = #f[case]
if lastarg > (gt_one and 1 or 0) then
f[case][lastarg] = com.concat_paired_russian_tr(f[case][lastarg], value)
end
end
end
for _, case in ipairs(long_cases) do
if args[case .. "_tail"] then
append_note_last(case, args[case .. "_tail"])
end
if args[case .. "_tailall"] then
append_note_all(case, args[case .. "_tailall"])
end
args[case] = canonicalize_override(args, case) or f[case]
end
for _, case in ipairs(short_cases) do
if short_forms_allowed then
if args[case .. "_tail"] then
append_note_last(case, args[case .. "_tail"])
end
if args[case .. "_tailall"] then
append_note_all(case, args[case .. "_tailall"])
end
if args.shorttailall then
append_note_all(case, args.shorttailall)
end
if args.shorttail then
append_note_last(case, args.shorttail, ">1")
end
args[case] = canonicalize_override(args, case) or f[case]
else
args[case] = nil
end
end
-- Convert an empty list to nil, so that an mdash is inserted. This happens,
-- for example, with words like голубой where args.bare is set to nil.
for _, case in ipairs(all_cases) do
if args[case] and #args[case] == 0 then
args[case] = nil
end
end
-- default acc_n to nom_n; applies chiefly in the indeclinable declension
-- (used with manual declension tables)
if not args.acc_n then
args.acc_n = args.nom_n
end
-- default inanimate/animate accusative variants as appropriate; this is
-- almost always correct, but not with e.g. два́дцать оди́н, where the
-- masculine animate accusative два́дцать одного́ differs from both the
-- masculine nominative два́дцать оди́н and the masculine genitive
-- двадцати́ одного́.
if not args.acc_m_an then
args.acc_m_an = args.gen_m
end
if not args.acc_m_in then
args.acc_m_in = args.nom_m
end
-- Compounds of два do not have animacy; everything else does.
-- Only copy either the with-animacy or without-animacy variants to avoid
-- having both forms in {{ru-generate-adj-forms}}.
if args.special ~= "cdva" then
if not args.acc_p_an then
args.acc_p_an = args.gen_p
end
if not args.acc_p_in then
args.acc_p_in = args.nom_p
end
if not args.acc_mp_an then
args.acc_mp_an = args.gen_mp
end
if not args.acc_mp_in then
args.acc_mp_in = args.nom_mp
end
if not args.acc_fp_an then
args.acc_fp_an = args.gen_fp
end
if not args.acc_fp_in then
args.acc_fp_in = args.nom_fp
end
else
if not args.acc_mp then
args.acc_mp = args.nom_mp
end
if not args.acc_fp then
args.acc_fp = args.nom_fp
end
end
end
--------------------------------------------------------------------------
-- Short adjective declension --
--------------------------------------------------------------------------
short_declensions["ый"] = { m="", f="а́", n="о́", p="ы́" }
short_declensions["ой"] = short_declensions["ый"]
short_declensions["ий"] = { m="ь", f="я́", n="е́", p="и́" }
short_declensions_old["ый"] = { m="ъ", f="а́", n="о́", p="ы́" }
short_declensions_old["ой"] = short_declensions_old["ый"]
short_declensions_old["ій"] = short_declensions["ий"]
-- Short adjective stress patterns:
-- "-" = stem-stressed
-- "+" = ending-stressed (drawn onto the last syllable of stem in masculine)
-- "-+" = both possibilities
short_stress_patterns["a"] = { m="-", f="-", n="-", p="-" }
short_stress_patterns["a'"] = { m="-", f="-+", n="-", p="-" }
short_stress_patterns["b"] = { m="+", f="+", n="+", p="+" }
short_stress_patterns["b'"] = { m="+", f="+", n="+", p="-+" }
short_stress_patterns["c"] = { m="-", f="+", n="-", p="-" }
short_stress_patterns["c'"] = { m="-", f="+", n="-", p="-+" }
short_stress_patterns["c''"] = { m="-", f="+", n="-+", p="-+" }
local function gen_short_form(args, decl, case, fun, datedrare)
if not args.forms["short_" .. case] then
args.forms["short_" .. case] = {}
end
local inserted = insert_forms_into_existing_forms(
args.forms["short_" .. case], attach_with(args, decl[case], fun, true),
(datedrare == "dated" and datedrare == "rare") and "*" or nil)
if datedrare == "dated" and inserted then
m_table.insertIfNot(args.internal_notes, "<sup>*</sup> Dated.")
elseif datedrare == "rare" and inserted then
m_table.insertIfNot(args.internal_notes, "<sup>*</sup> Rare.")
end
end
local attachers = {
["+"] = attach_stressed,
["-"] = attach_unstressed,
["-+"] = attach_both,
}
decline_short = function(args, decl, stress_pattern, dated)
if stress_pattern then
for _, case in ipairs({"m", "f", "n", "p"}) do
gen_short_form(args, decl, case, attachers[stress_pattern[case]],
dated)
end
end
end
--------------------------------------------------------------------------
-- Create the table --
--------------------------------------------------------------------------
local title_temp = [=[Declension of <b lang="ru" class="Cyrl">{lemma}</b> ({short_class_title})]=]
local old_title_temp = [=[Pre-reform declension of <b lang="ru" class="Cyrl">{lemma}</b> ({short_class_title})]=]
local title_temp_no_short_msg = [=[Declension of <b lang="ru" class="Cyrl">{lemma}</b>]=]
local old_title_temp_no_short_msg = [=[Pre-reform declension of <b lang="ru" class="Cyrl">{lemma}</b>]=]
local template = nil
local full_clause = nil
local template_no_neuter = nil
local full_clause_no_neuter = nil
local surname_full_clause = nil
local template_mp = nil
local full_clause_mp = nil
local template_mp_no_neuter = nil
local full_clause_mp_no_neuter = nil
local surname_full_clause_mp = nil
local template_dva = nil
local full_clause_dva = nil
local full_clause_compound_dva = nil
local template_oba = nil
local full_clause_oba = nil
local short_clause_separator = nil
local short_clause = nil
local short_clause_no_neuter_separator = nil
local short_clause_no_neuter = nil
local short_clause_mp_separator = nil
local short_clause_mp = nil
local short_clause_mp_no_neuter_separator = nil
local short_clause_mp_no_neuter = nil
local internal_notes_template = nil
local notes_template = nil
local function get_accel_forms(old, special, has_nom_mp)
return {
-- used with all variants
nom_m = "nom|m|s",
nom_f = "nom|f|s",
nom_n = "nom|n|s",
-- not used with special; applies to all genders normally but only
-- feminine and neuter with old=1
nom_p = old and has_nom_mp and "nom|f//n|p" or "nom|p",
-- only used with old=1 or special; applies to the masculine and neuter if
-- special, but only masculine if old=1
nom_mp = special and "nom|m//n|p" or "nom|m|p",
-- only used with special
nom_fp = "nom|f|p",
-- the remaining singulars and non-gendered plurals used with all variants
-- except special == "oba"
gen_m = "gen|m//n|s",
gen_f = "gen|f|s",
gen_p = "gen|p",
dat_m = "dat|m//n|s",
dat_f = "dat|f|s",
dat_p = "dat|p",
acc_m_an = "an|acc|m|s",
acc_m_in = "in|acc|m|s",
acc_f = "acc|f|s",
acc_n = "acc|n|s",
-- the following two not used with special in ("dva", "oba"); applies to
-- all genders normally but only feminine and neuter with old=1
acc_p_an = old and has_nom_mp and "an|acc|f//n|p" or "an|acc|p",
acc_p_in = old and has_nom_mp and "in|acc|f//n|p" or "in|acc|p",
-- the following two only used with old=1 or special in ("dva|oba");
-- applies to the masculine and neuter if special, but only masculine if
-- old=1
acc_mp_an = special and "an|acc|m//n|p" or "an|acc|m|p",
acc_mp_in = special and "in|acc|m//n|p" or "in|acc|m|p",
-- the following two only used with special in ("dva", "oba")
acc_fp_an = "an|acc|f|p",
acc_fp_in = "in|acc|f|p",
-- the next 6 are used with all variants except special == "oba"
ins_m = "ins|m//n|s",
ins_f = "ins|f|s",
ins_p = "ins|p",
pre_m = "pre|m//n|s",
pre_f = "pre|f|s",
pre_p = "pre|p",
-- the following two gendered plurals are only used with special == "cdva"
acc_mp = "acc|m//n|p",
acc_fp = "acc|f|p",
-- the remaining gendered plurals are only used with special == "oba"
gen_mp = "gen|m//n|p",
gen_fp = "gen|f|p",
dat_mp = "dat|m//n|p",
dat_fp = "dat|f|p",
ins_mp = "ins|m//n|p",
ins_fp = "ins|f|p",
pre_mp = "pre|m//n|p",
pre_fp = "pre|f|p",
-- short forms
short_m = "short|m|s",
short_f = "short|f|s",
short_n = "short|n|s",
short_p = "short|p",
}
end
-- Make the table
make_table = function(args)
local lemma_forms = args.special and args.nom_mp or
args.nofull and args.short_m or args.nom_m
args.lemma = m_links.remove_links(nom.show_form(lemma_forms, true, nil, nil))
args.title = args.title or strutils.format(
(args.special or args.manual) and args.old and old_title_temp_no_short_msg or
(args.special or args.manual) and title_temp_no_short_msg or
args.old and old_title_temp or
title_temp,
args)
local has_nom_mp = args.nom_mp and not (#args.nom_mp == 1 and args.nom_mp[1][1] == "-")
local accel_forms = get_accel_forms(args.old, args.special, has_nom_mp)
for _, case in ipairs(all_cases) do
if args[case] then
local accel_form = accel_forms[case]
if not accel_form then
error("Internal error: Unrecognized case " .. case .. " when looking up accelerator form")
end
if args.noneuter or args.real_surname then
accel_form = rsub(accel_form, "//n", "")
end
if args.real_surname then
accel_form = rsub(accel_form, "an|", "")
end
args[case] = nom.show_form(args[case], false, accel_form, lemma_forms)
else
args[case] = nil
end
end
local temp, fullc
if args.special == "oba" then
temp = template_oba
fullc = full_clause_oba
elseif args.special == "dva" then
temp = template_dva
fullc = full_clause_dva
elseif args.special == "cdva" then
temp = template_dva -- no template_cdva
fullc = full_clause_compound_dva
elseif args.real_surname and args.old and has_nom_mp then
temp = template_mp_no_neuter
fullc = surname_full_clause_mp
elseif args.real_surname then
temp = template_no_neuter
fullc = surname_full_clause
elseif args.noneuter and args.old and has_nom_mp then
temp = template_mp_no_neuter
fullc = full_clause_mp_no_neuter
elseif args.noneuter then
temp = template_no_neuter
fullc = full_clause_no_neuter
elseif args.old and has_nom_mp then
temp = template_mp
fullc = full_clause_mp
else
temp = template
fullc = full_clause
end
if args.old then
if has_nom_mp then
if args.short_m or args.short_n or args.short_f or args.short_p then
args.short_m = args.short_m or "—"
args.short_n = args.short_n or "—"
args.short_f = args.short_f or "—"
args.short_p = args.short_p or "—"
args.shortsep = not args.nofull and (
args.noneuter and short_clause_mp_no_neuter_separator or short_clause_mp_separator
) or ""
args.short_clause = strutils.format(args.noneuter and short_clause_mp_no_neuter or short_clause_mp, args)
else
args.short_clause = ""
end
else
args.short_clause = ""
end
else
if args.short_m or args.short_n or args.short_f or args.short_p then
args.short_m = args.short_m or "—"
args.short_n = args.short_n or "—"
args.short_f = args.short_f or "—"
args.short_p = args.short_p or "—"
args.shortsep = not args.nofull and (
args.noneuter and short_clause_no_neuter_separator or short_clause_separator
) or ""
args.short_clause = strutils.format(args.noneuter and short_clause_no_neuter or short_clause, args)
else
args.short_clause = ""
end
end
if not args.nofull then
args.full_clause = strutils.format(fullc, args)
else
args.full_clause = ""
end
args.internal_notes = table.concat(args.internal_notes, "<br />")
args.internal_notes_clause = #args.internal_notes > 0 and strutils.format(internal_notes_template, args) or ""
args.notes_clause = args.notes and strutils.format(notes_template, args) or ""
return strutils.format(temp, args)
end
-- Used for new-style templates
short_clause_separator = [===[
! style="height:0.2em;background:#d9ebff" colspan="6" |
|-
]===]
-- Used for new-style templates
short_clause = [===[
{shortsep}! style="background:#eff7ff" colspan="2" | short form
| {short_m}
| {short_n}
| {short_f}
| {short_p}]===]
-- Used for new-style templates
short_clause_no_neuter_separator = [===[
! style="height:0.2em;background:#d9ebff" colspan="5" |
|-
]===]
-- Used for new-style templates
short_clause_no_neuter = [===[
{shortsep}! style="background:#eff7ff" colspan="2" | short form
| {short_m}
| {short_f}
| {short_p}]===]
-- Used for old-style templates
short_clause_mp_separator = [===[
! style="height:0.2em;background:#d9ebff" colspan="7" |
|-
]===]
-- Used for old-style templates
short_clause_mp = [===[
{shortsep}! style="background:#eff7ff" colspan="2" | short form
| {short_m}
| {short_n}
| {short_f}
| colspan="2" | {short_p}]===]
-- Used for old-style templates
short_clause_mp_no_neuter_separator = [===[
! style="height:0.2em;background:#d9ebff" colspan="6" |
|-
]===]
-- Used for old-style templates
short_clause_mp_no_neuter = [===[
{shortsep}! style="background:#eff7ff" colspan="2" | short form
| {short_m}
| {short_f}
| colspan="2" | {short_p}]===]
-- Used for both new-style and old-style templates
notes_template = [===[
<div style="width:100%;text-align:left">
<div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em">
{notes}
</div></div>
]===]
-- Used for both new-style and old-style templates
internal_notes_template = rsub(notes_template, "notes", "internal_notes")
local function template_prelude(min_width)
min_width = min_width or "70"
return rsub([===[
<div>
<div class="NavFrame" style="display: inline-block; min-width: MINWIDTHem">
<div class="NavHead" style="background:#eff7ff">{title}</div>
<div class="NavContent">
{\op}| style="background:#F9F9F9;text-align:center; min-width:MINWIDTHem" class="inflection-table"
|-
]===], "MINWIDTH", min_width)
end
local function template_postlude()
return [===[{full_clause}|-{short_clause}
|{\cl}{internal_notes_clause}{notes_clause}</div></div></div>]===]
end
-- Used for both new-style and old-style templates
template = template_prelude() .. [===[
! style="width:20%;background:#d9ebff" colspan="2" |
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | neuter
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | plural
]===] .. template_postlude()
-- Used for both new-style and old-style templates
full_clause = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_m}
| {nom_n}
| {nom_f}
| {nom_p}
|-
! style="background:#eff7ff" colspan="2" | genitive
| colspan="2" | {gen_m}
| {gen_f}
| {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| colspan="2" | {dat_m}
| {dat_f}
| {dat_p}
|-
! style="background:#eff7ff" rowspan="2" | accusative
! style="background:#eff7ff" | animate
| {acc_m_an}
| rowspan="2" | {acc_n}
| rowspan="2" | {acc_f}
| {acc_p_an}
|-
! style="background:#eff7ff" | inanimate
| {acc_m_in}
| {acc_p_in}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| colspan="2" | {ins_m}
| {ins_f}
| {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| colspan="2" | {pre_m}
| {pre_f}
| {pre_p}
]===]
-- Used for both new-style and old-style templates
template_no_neuter = template_prelude("55") .. [===[
! style="width:20%;background:#d9ebff" colspan="2" |
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | plural
]===] .. template_postlude()
-- Used for both new-style and old-style templates
full_clause_no_neuter = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_m}
| {nom_f}
| {nom_p}
|-
! style="background:#eff7ff" colspan="2" | genitive
| {gen_m}
| {gen_f}
| {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| {dat_m}
| {dat_f}
| {dat_p}
|-
! style="background:#eff7ff" rowspan="2" | accusative
! style="background:#eff7ff" | animate
| {acc_m_an}
| rowspan="2" | {acc_f}
| {acc_p_an}
|-
! style="background:#eff7ff" | inanimate
| {acc_m_in}
| {acc_p_in}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| {ins_m}
| {ins_f}
| {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| {pre_m}
| {pre_f}
| {pre_p}
]===]
-- Used for both new-style and old-style templates
surname_full_clause = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_m}
| {nom_f}
| {nom_p}
|-
! style="background:#eff7ff" colspan="2" | genitive
| {gen_m}
| {gen_f}
| {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| {dat_m}
| {dat_f}
| {dat_p}
|-
! style="background:#eff7ff" colspan="2" | accusative
| {acc_m_an}
| {acc_f}
| {acc_p_an}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| {ins_m}
| {ins_f}
| {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| {pre_m}
| {pre_f}
| {pre_p}
]===]
-- Used for old-style templates
template_mp = template_prelude() .. [===[
! style="width:20%;background:#d9ebff" colspan="2" |
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | neuter
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | m. plural
! style="background:#d9ebff" | n./f. plural
]===] .. template_postlude()
-- Used for old-style templates
full_clause_mp = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_m}
| {nom_n}
| {nom_f}
| {nom_mp}
| {nom_p}
|-
! style="background:#eff7ff" colspan="2" | genitive
| colspan="2" | {gen_m}
| {gen_f}
| colspan="2" | {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| colspan="2" | {dat_m}
| {dat_f}
| colspan="2" | {dat_p}
|-
! style="background:#eff7ff" rowspan="2" | accusative
! style="background:#eff7ff" | animate
| {acc_m_an}
| rowspan="2" | {acc_n}
| rowspan="2" | {acc_f}
| colspan="2" | {acc_p_an}
|-
! style="background:#eff7ff" | inanimate
| {acc_m_in}
| {acc_mp_in}
| {acc_p_in}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| colspan="2" | {ins_m}
| {ins_f}
| colspan="2" | {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| colspan="2" | {pre_m}
| {pre_f}
| colspan="2" | {pre_p}
]===]
-- Used for old-style templates
template_mp_no_neuter = template_prelude("60") .. [===[
! style="width:20%;background:#d9ebff" colspan="2" |
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | m. plural
! style="background:#d9ebff" | f. plural
]===] .. template_postlude()
-- Used for old-style templates
full_clause_mp_no_neuter = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_m}
| {nom_f}
| {nom_mp}
| {nom_p}
|-
! style="background:#eff7ff" colspan="2" | genitive
| {gen_m}
| {gen_f}
| colspan="2" | {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| {dat_m}
| {dat_f}
| colspan="2" | {dat_p}
|-
! style="background:#eff7ff" rowspan="2" | accusative
! style="background:#eff7ff" | animate
| {acc_m_an}
| rowspan="2" | {acc_f}
| colspan="2" | {acc_p_an}
|-
! style="background:#eff7ff" | inanimate
| {acc_m_in}
| {acc_mp_in}
| {acc_p_in}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| {ins_m}
| {ins_f}
| colspan="2" | {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| {pre_m}
| {pre_f}
| colspan="2" | {pre_p}
]===]
-- Used for some old-style templates
surname_full_clause_mp = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_m}
| {nom_f}
| {nom_mp}
| {nom_p}
|-
! style="background:#eff7ff" colspan="2" | genitive
| {gen_m}
| {gen_f}
| colspan="2" | {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| {dat_m}
| {dat_f}
| colspan="2" | {dat_p}
|-
! style="background:#eff7ff" colspan="2" | accusative
| {acc_m_an}
| {acc_f}
| colspan="2" | {acc_p_an}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| {ins_m}
| {ins_f}
| colspan="2" | {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| {pre_m}
| {pre_f}
| colspan="2" | {pre_p}
]===]
-- Used for два and compounds
template_dva = template_prelude("55") .. [===[
! style="width:20%;background:#d9ebff" colspan="2" |
! style="background:#d9ebff" | masculine/neuter
! style="background:#d9ebff" | feminine
]===] .. template_postlude()
-- Used for both new-style and old-style templates of два (only for два itself,
-- which has an animacy distinction; not for compounds)
full_clause_dva = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_mp}
| {nom_fp}
|-
! style="background:#eff7ff" colspan="2" | genitive
| colspan="2" | {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| colspan="2" | {dat_p}
|-
! style="background:#eff7ff" rowspan="2" | accusative
! style="background:#eff7ff" | animate
| colspan="2" | {acc_p_an}
|-
! style="background:#eff7ff" | inanimate
| {acc_mp_in}
| {acc_fp_in}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| colspan="2" | {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| colspan="2" | {pre_p}
]===]
-- Used for both new-style and old-style templates of compounds of два
full_clause_compound_dva = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_mp}
| {nom_fp}
|-
! style="background:#eff7ff" colspan="2" | genitive
| colspan="2" | {gen_p}
|-
! style="background:#eff7ff" colspan="2" | dative
| colspan="2" | {dat_p}
|-
! style="background:#eff7ff" colspan="2" | accusative
| {acc_mp}
| {acc_fp}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| colspan="2" | {ins_p}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| colspan="2" | {pre_p}
]===]
-- Used for оба
template_oba = template_prelude() .. [===[
! style="width:20%;background:#d9ebff" colspan="2" |
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | neuter
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | m./n. plural
! style="background:#d9ebff" | f. plural
]===] .. template_postlude()
-- Used for both new-style and old-style templates of оба
full_clause_oba = [===[
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_m}
| {nom_n}
| {nom_f}
| {nom_mp}
| {nom_fp}
|-
! style="background:#eff7ff" colspan="2" | genitive
| colspan="2" | {gen_m}
| {gen_f}
| {gen_mp}
| {gen_fp}
|-
! style="background:#eff7ff" colspan="2" | dative
| colspan="2" | {dat_m}
| {dat_f}
| {dat_mp}
| {dat_fp}
|-
! style="background:#eff7ff" rowspan="2" | accusative
! style="background:#eff7ff" | animate
| {acc_m_an}
| rowspan="2" | {acc_n}
| rowspan="2" | {acc_f}
| {acc_mp_an}
| {acc_fp_an}
|-
! style="background:#eff7ff" | inanimate
| {acc_m_in}
| {acc_mp_in}
| {acc_fp_in}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| colspan="2" | {ins_m}
| {ins_f}
| {ins_mp}
| {ins_fp}
|-
! style="background:#eff7ff" colspan="2" | prepositional
| colspan="2" | {pre_m}
| {pre_f}
| {pre_mp}
| {pre_fp}
]===]
return export