Модуль:inflection/units/ru-noun (en)

Материал из Викисловаря

Для документации этого модуля может быть создана страница Модуль:inflection/units/ru-noun (en)/Документация

-- Inflection unit for Russian nouns
-- Version 2.0.38
-- Date: 2015-08-25

local dev_prefix = ''
-- dev_prefix = 'User:Vitalik/'  -- comment this on active version

local export = {}
local _ = require('Module:' .. dev_prefix .. 'inflection-tools')
-- local lang = require("Module:languages").getByCode("ru")
-- local m_links = require("Module:links")
-- local strutils = require("Module:string utilities")

-- constants:
local unstressed = 1
local stressed = 2

function export.template(base, args)
	return dev_prefix .. 'ru-decl-noun-table-z'
end

local function get_standard_endings()
	return {
		m = {  -- masculine endings
			hard = {
				nom_sg = '',
				gen_sg = 'а',
				dat_sg = 'у',
				ins_sg = 'ом',
				nom_pl = 'ы',
				gen_pl = {'ов', 'ов'},  -- possibly we can join them together again (m_hard_gen_pl stressed and unstressed)
			},
			soft = {
				nom_sg = 'ь',
				gen_sg = 'я',
				dat_sg = 'ю',
				ins_sg = {'ем', 'ём'},
				nom_pl = 'и',
				gen_pl = {'ей', 'ей'},
			},
		},
		f = {  -- feminine endings
			hard = {
				nom_sg = 'а',
				gen_sg = 'ы',
				dat_sg = 'е',
				acc_sg = 'у',
				ins_sg = 'ой',
				nom_pl = 'ы',
				gen_pl = {'', ''},
			},
			soft = {
				nom_sg = 'я',
				gen_sg = 'и',
				dat_sg = {'е', 'е'},
				acc_sg = 'ю',
				ins_sg = {'ей', 'ёй'},
				nom_pl = 'и',
				gen_pl = {'ь', 'ей'},
			},
		},
		n = {  -- neuter endings
			hard = {
				nom_sg = 'о',
				gen_sg = 'а',
				dat_sg = 'у',
				ins_sg = 'ом',
				nom_pl = 'а',
				gen_pl = {'', ''},
			},
			soft = {
				nom_sg = 'е',  -- was: {'е', 'ё'}
				gen_sg = 'я',
				dat_sg = 'ю',
				ins_sg = {'ем', 'ём'},
				nom_pl = 'я',
				gen_pl = {'ь', 'ей'},
			},
		},
		common = {  -- common endings
			hard = {
				prp_sg = {'е', 'е'},
				dat_pl = 'ам',
				ins_pl = 'ами',
				prp_pl = 'ах',
			},
			soft = {
				prp_sg = {'е', 'е'},
				dat_pl = 'ям',
				ins_pl = 'ями',
				prp_pl = 'ях',
			},
		}
	}
end

local function get_stress_flags(stress_type)
	return {
		stem = {
			sg     = _.equals(stress_type, {"a", "c", "e"})                          and stressed or unstressed,
			acc_sg = _.equals(stress_type, {"a", "c", "e", "d'", "f'"})              and stressed or unstressed,
			ins_sg = _.equals(stress_type, {"a", "c", "e", "b'", "f''"})             and stressed or unstressed,
			pl     = _.equals(stress_type, {"a", "d", "d'"})                         and stressed or unstressed,
			nom_pl = _.equals(stress_type, {"a", "d", "d'", "e", "f", "f'", "f''"})  and stressed or unstressed,
		},
		ending = {
			sg     = _.equals(stress_type, {"b", "b'", "d", "d'", "f", "f'", "f''"}) and stressed or unstressed,
			acc_sg = _.equals(stress_type, {"b", "b'", "d", "f", "f''"})             and stressed or unstressed,
			ins_sg = _.equals(stress_type, {"b", "d", "d'", "f", "f'"})              and stressed or unstressed,
			pl     = _.equals(stress_type, {"b", "b'", "c", "e", "f", "f'", "f''"})  and stressed or unstressed,
			nom_pl = _.equals(stress_type, {"b", "b'", "c"})                         and stressed or unstressed,
		}
	}
end

local function get_stem_type(z)
	if _.endswith(z.stem, '[гкх]') then
		z.stem_type = 'velar'
	elseif _.endswith(z.stem, '[жчшщ]') then
		z.stem_type = 'sibilant'
	elseif _.endswith(z.stem, 'ц') then
		z.stem_type = 'letter-ц'
	elseif _.endswith(z.stem, {'[йь]', '[аоеёуыэюя]'}) then
		z.stem_type = 'vowel'
	elseif _.endswith(z.stem, 'и') then
		z.stem_type = 'letter-и'
	else
		if z.gender == 'm' then
			if z.stem == z.word or _.endswith(z.word, 'ы') then
				z.stem_type = 'hard'
			elseif _.endswith(z.word, 'путь') then
				z.stem_type = 'm-3rd'
			elseif _.endswith(z.word, 'ь') or _.endswith(z.word, 'и') then
				z.stem_type = 'soft'
			elseif _.endswith(z.word, 'а') then
				z.gender = 'f'
				z.stem_type = 'hard'
			elseif _.endswith(z.word, 'я') then
				z.gender = 'f'
				z.stem_type = 'soft'
			end
		elseif z.gender == 'f' then
			if _.endswith(z.word, 'а') or _.endswith(z.word, 'ы') then
				z.stem_type = 'hard'
			elseif _.endswith(z.word, 'я') or _.endswith(z.word, 'и') then
				z.stem_type = 'soft'
			elseif _.endswith(z.word, 'ь') then  -- conflict in pl
				z.stem_type = 'f-3rd'
			end
		elseif z.gender == 'n' then
			if _.endswith(z.word, 'о') or _.endswith(z.word, 'а') then
				z.stem_type = 'hard'
			elseif _.endswith(z.word, 'е') or _.endswith(z.word, 'я') then
				z.stem_type = 'soft'
			elseif _.endswith(z.word, 'мя')  or _.endswith(z.word, 'мена') then
				z.stem_type = 'n-3rd'
			end
		end
	end
	
	if z.gender == 'm' then
		if _.endswith(z.word, {'а', 'я'}) then
			z.gender = 'f'
		end
	end

	if z.gender == 'f' and z.stem_type == 'sibilant' and _.endswith(z.word, 'ь') then
		z.stem_type = 'f-3rd-sibilant'
	end
	if z.stem_type == '' then
		z.stem_type = 'hard'
	end
end

local function change_endings_for_other_stem_types(z)
	if _.equals(z.stem_type, {'velar', 'sibilant'}) then
		-- Replace "ы" to "и"
		z.e['f']['hard']['gen_sg'] = 'и'
		z.e['m']['hard']['nom_pl'] = 'и'
		z.e['f']['hard']['nom_pl'] = 'и'
	end

	if _.equals(z.stem_type, {'sibilant', 'letter-ц'}) then
		-- Replace unstressed "о" to "е"
		if z.stress_flags['ending']['sg'] == unstressed then
			z.e['n']['hard']['nom_sg'] = 'е'
		end
		if z.stress_flags['ending']['ins_sg'] == unstressed then
			z.e['m']['hard']['ins_sg'] = 'ем'
			z.e['n']['hard']['ins_sg'] = 'ем'
			z.e['f']['hard']['ins_sg'] = 'ей'
		end
		if z.stress_flags['ending']['pl'] == unstressed then
			z.e['m']['hard']['gen_pl'] = {'ев', 'ев'}  -- TODO: should we change stressed value here?
		end
	end

	if _.equals(z.stem_type, 'sibilant') then
		-- Replace "ов", "ев", "ёв" and null to "ей"
		z.e['m']['hard']['gen_pl'] = {'ей', 'ей'}
		z.e['n']['hard']['gen_pl'][stressed] = 'ей'
		-- z.e['n']['hard']['gen_pl']_unstressed = ''  this is just don't changed
		z.e['f']['hard']['gen_pl'][stressed] = 'ей'
		-- z.e['f']['hard']['gen_pl']_unstressed = ''  this is just don't changed
	end

	if _.equals(z.stem_type, {'vowel', 'letter-и'}) then
		-- Replace "ь" to "й"
		z.e['m']['soft']['nom_sg'] = 'й'
		z.e['n']['soft']['gen_pl'][unstressed] = 'й'
		z.e['f']['soft']['gen_pl'][unstressed] = 'й'
	end

	if _.equals(z.stem_type, {'vowel', 'letter-и'}) then
		-- Replace "ей" to "ев/ёв", and "ь,ей" to "й"
		z.e['m']['soft']['gen_pl'] = {'ев', 'ёв'}
		z.e['n']['soft']['gen_pl'] = {'й', 'й'}
		z.e['f']['soft']['gen_pl'] = {'й', 'й'}
	end

	if _.equals(z.stem_type, 'letter-и') then
		z.e['f']['soft']['dat_sg'][unstressed] = 'и'
		z.e['common']['soft']['prp_sg'][unstressed] = 'и'
	end

	if _.equals(z.stem_type, 'm-3rd') then
		z.e['m']['soft']['gen_sg'] = 'и'
		z.e['m']['soft']['dat_sg'] = 'и'
		z.e['common']['soft']['prp_sg'] = {'и', 'и'}
	end

	if _.equals(z.stem_type, {'f-3rd', 'f-3rd-sibilant'}) then
		z.e['f']['soft']['nom_sg'] = 'ь'
		z.e['f']['soft']['dat_sg'] = {'и', 'и'}
		z.e['f']['soft']['acc_sg'] = 'ь'
		z.e['f']['soft']['ins_sg'] = {'ью', 'ью'}
		z.e['common']['soft']['prp_sg'] = {'и', 'и'}
		z.e['f']['soft']['gen_pl'] = {'ей', 'ей'}
	end

	if _.equals(z.stem_type, 'f-3rd-sibilant') then
		z.e['common']['soft']['dat_pl'] = 'ам'
		z.e['common']['soft']['ins_pl'] = 'ами'
		z.e['common']['soft']['prp_pl'] = 'ах'
	end
end

local function apply_specific_1_2(z)
-- If we have specific (1) or (2)
	if _.contains(z.specific, '%(1%)') then
		z.e['m']['hard']['nom_pl'] = 'а'
		z.e['m']['soft']['nom_pl'] = 'я'
		z.e['n']['hard']['nom_pl'] = 'ы'
		z.e['n']['soft']['nom_pl'] = 'и'
		if _.equals(z.stem_type, {'velar', 'sibilant'}) then
			-- Replace "ы" to "и"
			z.e['n']['hard']['nom_pl'] = 'и'
		end
	end
	if _.contains(z.specific, '%(2%)') then
		z.e['m']['hard']['gen_pl'] = {'', ''}
		z.e['m']['soft']['gen_pl'] = {'ь', 'ь'}
		z.e['n']['hard']['gen_pl'] = {'ов', 'ов'}
		z.e['n']['soft']['gen_pl'] = {'ев', 'ёв'}
		z.e['f']['hard']['gen_pl'] = {'ей', 'ей'}
		z.e['f']['soft']['gen_pl'] = {'ей', 'ей'}
		if _.equals(z.stem_type, {'sibilant', 'letter-ц'}) then
			-- Replace unstressed "о" to "е"
			z.e['n']['hard']['gen_pl'][unstressed] = 'ев'
		end
	--[=[ Possibly we don't need this:
			-- Replace "ов", "ев", "ёв" and null to "ей"
			if z.stem_type = {'sibilant'}}
				z.e['n']['hard']['gen_pl'] = {'ей', 'ей'}
				z.e['m']['hard']['gen_pl'][stressed] = 'ей'
			end
			-- Replace "ь" to "й"
			if z.stem_type = {'vowel', 'letter-и'}}
				z.e['m']['soft']['gen_pl'][stressed] = {'й', 'й'}
			end
			-- Replace "ей" to "ев/ёв", and "ь,ей" to "й"
			if z.stem_type = {'vowel', 'letter-и'}}
				z.e['f']['soft']['gen_pl'][unstressed] = {'ев', 'ёв'}
				z.e['m']['soft']['gen_pl'][stressed] = {'й', 'й'}
			end
	]=]--
	end
end

local function choose(endings, case, type)
	endings[case] = endings[case][type]
end

local function choose_stress_for_endings(z)
	local s
	s = z.stress_flags['ending']['sg']
	choose(z.e['f']['soft'], 'dat_sg', s)
	choose(z.e['common']['hard'], 'prp_sg', s)
	choose(z.e['common']['soft'], 'prp_sg', s)

	s = z.stress_flags['ending']['ins_sg']
	choose(z.e['m']['soft'], 'ins_sg', s)
	choose(z.e['n']['soft'], 'ins_sg', s)
	choose(z.e['f']['soft'], 'ins_sg', s)

	s = z.stress_flags['ending']['pl']
	choose(z.e['m']['hard'], 'gen_pl', s)
	choose(z.e['m']['soft'], 'gen_pl', s)
	choose(z.e['n']['hard'], 'gen_pl', s)
	choose(z.e['n']['soft'], 'gen_pl', s)
	choose(z.e['f']['hard'], 'gen_pl', s)
	choose(z.e['f']['soft'], 'gen_pl', s)
end

local function choose_endings_by_stem_type(z)
	local base_stem_type
	if _.equals(z.stem_type, {'hard', 'soft'}) then
		base_stem_type = z.stem_type
	elseif _.equals(z.stem_type, {'velar', 'sibilant', 'letter-ц'}) then
		base_stem_type = 'hard'
	elseif _.equals(z.stem_type, {'vowel', 'letter-и', 'm-3rd', 'f-3rd', 'f-3rd-sibilant'}) then
		base_stem_type = 'soft'
	else
		return {error_msg = 'Unexpected internal error: Unknown stem type'}
	end
	for key, value in pairs(z.e['common'][base_stem_type]) do
		z.e[z.gender][base_stem_type][key] = value
	end
	z.endings = z.e[z.gender][base_stem_type]
end

local function add_stress(endings, case)
	endings[case] = _.replace(endings[case], '^({vowel})', '%1́ ')
end

local function apply_stress_type(z)
	if z.stress_flags['stem']['sg'] == stressed then
		z.stems['sg'] = z.stem_stressed
	else
		z.stems['sg'] = z.stem
		add_stress(z.endings, 'nom_sg')
		add_stress(z.endings, 'gen_sg')
		add_stress(z.endings, 'dat_sg')
		add_stress(z.endings, 'prp_sg')
	end

	if z.stress_flags['stem']['ins_sg'] == stressed then
		z.stems['ins_sg'] = z.stem_stressed
	else
		z.stems['ins_sg'] = z.stem
		add_stress(z.endings, 'ins_sg')
	end

	if z.gender == 'f' then
		if z.stress_flags['stem']['acc_sg'] == stressed then
			z.stems['acc_sg'] = z.stem_stressed
		else
			z.stems['acc_sg'] = z.stem
			add_stress(z.endings, 'acc_sg')
		end
	end

	if z.stress_flags['stem']['nom_pl'] == stressed then
		z.stems['nom_pl'] = z.stem_stressed
	else
		z.stems['nom_pl'] = z.stem
		add_stress(z.endings, 'nom_pl')
	end

	if z.stress_flags['stem']['pl'] == stressed then
		z.stems['pl'] = z.stem_stressed
	else
		z.stems['pl'] = z.stem
		add_stress(z.endings, 'gen_pl')
		add_stress(z.endings, 'dat_pl')
		add_stress(z.endings, 'ins_pl')
		add_stress(z.endings, 'prp_pl')
	end
end

local function apply_specific_degree(z)
	-- If degree sign °
	if _.endswith(z.word, '[ая]нин') and z.animacy == 'an' and z.word ~= 'семьянин' then
		z.stems['pl'] = _.replace(z.stems['pl'], '([ая])ни́ н$', '%1́ н')
		z.stems['pl'] = _.replace(z.stems['pl'], '([ая]́ ?н)ин$', '%1')
		z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], '([ая])ни́ н$', '%1́ н')
		z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], '([ая]́ ?н)ин$', '%1')
		z.endings['nom_pl'] = 'е'
		z.endings['gen_pl'] = ''
		z.specific = z.specific .. '°'
	end
	if _.endswith(z.word, {'ёнок', 'онок', 'ёночек', 'оночек'}) then
		if _.endswith(z.word, 'ёнок') then
			z.stems['pl'] = _.replace(z.stems['pl'], 'ёнок$', 'я́т')
			z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], 'ёнок$', 'я́т')
		end
		if _.endswith(z.word, 'онок') then
			z.stems['pl'] = _.replace(z.stems['pl'], 'о́нок$', 'а́т')
			z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], 'о́нок$', 'а́т')
		end
		if _.endswith(z.word, 'ёночек') then
			z.stems['pl'] = _.replace(z.stems['pl'], 'ёночек$', 'я́тк')
			z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], 'ёночек$', 'я́тк')
		end
		if _.endswith(z.word, 'оночек') then
			z.stems['pl'] = _.replace(z.stems['pl'], 'о́ночек$', 'а́тк')
			z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], 'о́ночек$', 'а́тк')
		end
		z.endings['nom_pl'] = z.e['f']['hard']['nom_pl']
		z.endings['gen_pl'] = z.e['f']['hard']['gen_pl']
		z.specific = z.specific .. '*'
		z.specific = z.specific .. '°'
	end
end

local function apply_specific_reducable(z)
	if _.contains(z.specific, '%*') then
		if z.gender == 'm' then
			reduced = 'A'
		elseif z.gender == 'n' then
			reduced = 'B'
		elseif z.gender == 'f' then
			if _.equals(z.stem_type, {'f-3rd', 'f-3rd-sibilant'}) then 
				reduced = 'A'
			else
				reduced = 'B'
			end
		end
	
		if reduced == 'A' then
			reduced_letter = _.extract(z.word, '({vowel+ё}){consonant}+$')
			if reduced_letter == 'о' then
				z.stems['sg'] = _.replace(z.stems['sg'], 'о́ ?([^о]+)$', '%1')
				if not _.equals(z.stem_type, {'f-3rd', 'f-3rd-sibilant'}) then
					z.stems['ins_sg'] = _.replace(z.stems['ins_sg'], 'о́ ?([^о]+)$', '%1')
				end
				z.stems['pl'] = _.replace(z.stems['pl'], 'о́ ?([^о]+)$', '%1')
				z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], 'о́ ?([^о]+)$', '%1')
			elseif _.equals(reduced_letter, {'е', 'ё'}) then
				prev = _.extract(z.word, '(.)[её][^её]+$')
				if _.contains(prev, '{vowel+ё}') then                                     -- 1).
					z.stems['sg'] = _.replace(z.stems['sg'], '[её]́ ?([^её]+)$', 'й%1')
					if not _.equals(z.stem_type, {'f-3rd', 'f-3rd-sibilant'}) then  
						z.stems['ins_sg'] = _.replace(z.stems['ins_sg'], '[её]́ ?([^её]+)$', 'й%1')
					end
					z.stems['pl'] = _.replace(z.stems['pl'], '[её]́ ?([^её]+)$', 'й%1')
					z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], '[её]́ ?([^её]+)$', 'й%1')
				elseif z.stem_type == 'vowel'                                                  -- 2) а).
						or z.stem_type == 'velar' and _.contains(prev, '[^аеёиоуыэюяшжчщц]')   -- 2) б).
						or not _.equals(z.stem_type, {'vowel', 'velar'}) and prev == 'л' then  -- 2) в).
					z.stems['sg'] = _.replace(z.stems['sg'], '[её]́ ?([^её]*)$', 'ь%1')
					if not _.equals(z.stem_type, {'f-3rd', 'f-3rd-sibilant'}) then  
						z.stems['ins_sg'] = _.replace(z.stems['ins_sg'], '[её]́ ?([^её]*)$', 'ь%1')
					end
					z.stems['pl'] = _.replace(z.stems['pl'], '[её]́ ?([^её]*)$', 'ь%1')
					z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], '[её]́ ?([^её]*)$', 'ь%1')
				else                                                                         -- 3).
					z.stems['sg'] = _.replace(z.stems['sg'], '[её]́ ?([^её]*)$', '%1')
					if not _.equals(z.stem_type, {'f-3rd', 'f-3rd-sibilant'}) then  
						z.stems['ins_sg'] = _.replace(z.stems['ins_sg'], '[её]́ ?([^её]*)$', '%1')
					end
					z.stems['pl'] = _.replace(z.stems['pl'], '[её]́ ?([^её]*)$', '%1')
					z.stems['nom_pl'] = _.replace(z.stems['nom_pl'], '[её]́ ?([^её]*)$', '%1')
				end
			end
		end  -- reduced A
	
		-- TODO: pcerhaps this line is redundant?
		z.stems['gen_pl'] = z.stems['pl']  -- apply changes in stems['pl'] for stems['gen_pl']
	
		if reduced == 'B' and 
			not (z.stem_type == 'soft' and _.equals(z.stress_type, {'b', 'f'}) -- we should ignore asterix for 2*b and 2*f (so to process it just like 2b or 2f)
				 or _.contains(z.specific, '(2)') and _.equals(z.stem_type, {'velar', 'letter-ц', 'vowel'}))  -- and also the same for (2)-specific and 3,5,6 stem-types
		then 
			if z.stem_type == 'vowel' then  -- 1).
				if _.equals(z.stress_type, {'b', 'c', 'e', 'f', "f'", "b'" }) then  -- gen_pl ending stressed  -- TODO: special vars for that
					z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], 'ь$', 'е́')
				else
					z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], 'ь$', 'и')
				end
			elseif _.contains(z.stem, '[ьй]{consonant}$') then  -- 2).
				if z.stem_type == 'letter-ц' or _.equals(z.stress_type, {'a', 'd', "d'"}) then  -- gen_pl ending unstressed  -- TODO: special vars for that
					z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], '[ьй]({consonant})$', 'е%1')
				else
					z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], '[ьй]({consonant})$', 'ё%1')
				end
			else  -- 3).
				prev = _.extract(z.stem, '(.){consonant}$')
				if z.stem_type == 'velar' and _.contains(prev, '[^жшчщц]')  -- 3). а).
						or _.contains(prev, '[кгх]') then                -- 3). б).
					z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], '(.)({consonant})$', '%1о%2')
				else  -- 3). в).
					if z.stem_type == 'letter-ц' then
						z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], '(.)({consonant})$', '%1е%2')
					else 
						if _.equals(z.stress_type, {'b', 'c', 'e', 'f', "f'", "b'" }) then  -- gen_pl ending stressed  -- TODO: special vars for that
							if _.contains(prev, '[жшчщ]') then
								z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], '(.)({consonant})$', '%1о́%2')
							else
								z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], '(.)({consonant})$', '%1ё%2')
							end
						else 
							z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], '(.)({consonant})$', '%1е%2')
						end
					end
				end
			end
			if z.stem_type == 'soft' and _.endswith(z.word, 'ня') and z.stress_type == 'a' then
				z.endings['gen_pl'] = ''
			end
		end  -- reduced B
	end  -- specific *
end

local function choose_accusative_forms(z, forms)
	forms['acc_sg_in'] = ''
	forms['acc_sg_an'] = ''
	forms['acc_pl_in'] = ''
	forms['acc_pl_an'] = ''
	
	if z.gender == 'n' then
		forms['acc_sg'] = forms['nom_sg']
	elseif z.gender == 'm' then
		if z.animacy == 'in' then
			forms['acc_sg'] = forms['nom_sg']
		elseif z.animacy == 'an' then
			forms['acc_sg'] = forms['gen_sg']
		else
			forms['acc_sg_in'] = forms['nom_sg']
			forms['acc_sg_an'] = forms['gen_sg']
		end
	elseif z.gender == 'f' then
		if _.equals(z.stem_type, {'f-3rd', 'f-3rd-sibilant'}) then
			forms['acc_sg'] = forms['nom_sg']
		else
			forms['acc_sg'] = z.stems['acc_sg'] .. z.endings['acc_sg']
		end
	end

	if z.animacy == 'in' then
		forms['acc_pl'] = forms['nom_pl']
	elseif z.animacy == 'an' then
		forms['acc_pl'] = forms['gen_pl']
	else
		forms['acc_pl_in'] = forms['nom_pl']
		forms['acc_pl_an'] = forms['gen_pl']
	end
end

function get_zaliznyak_index(z)
	local stem_types = {
		['hard'] = '1',
		['soft'] = '2',
		['velar'] = '3',
		['sibilant'] = '4',
		['letter-ц'] = '5',
		['vowel'] = '6',
		['letter-и'] = '7',
		['m-3rd'] = '8',
		['f-3rd'] = '8',
		['f-3rd-sibilant'] = '8',
		['n-3rd'] = '8',
	}
	local index = z.gender_animacy .. ' ' .. stem_types[z.stem_type]
	if _.contains(z.specific, '°') then
		index = index .. '°'
	elseif _.contains(z.specific, '%*') then
		index = index .. '*'
	end
	index = index .. _.replace(z.stress_type, "'", "'")
	if _.contains(z.specific, '%(1%)') then
		index = index .. '①'
	end
	if _.contains(z.specific, '%(2%)') then
		index = index .. '②'
	end
	if _.contains(z.specific, '%(3%)') then
		index = index .. '③'
	end
	if _.contains(z.specific, 'ё') then
		index = index .. ', ё'
	end
	return index
end

function export.forms(base, args)
	_.clear_stash()
	_.add_stash('{vowel}', '[аеиоуыэюяАЕИОУЫЭЮЯ]')
	_.add_stash('{vowel+ё}', '[аеёиоуыэюяАЕЁИОУЫЭЮЯ]')
	_.add_stash('{consonant}', '[^аеёиоуыэюяАЕЁИОУЫЭЮЯ]')

	local z = {}
	z.e = get_standard_endings()

	z.word_stressed = args['word_stressed']
	if _.contains_several(z.word_stressed, '{vowel+ё}') and not _.contains(z.word_stressed, '[́ ё]') then
		return {error_msg = 'Error in template {{ru-decl-noun-z}}: You should add stress mark for the argument "word_stressed"'}
	end
	z.word = _.replace(z.word_stressed, '́ ', '')

	-- Parse "gender_animacy" argument and get values for "gender" and "animacy"'
	z.gender_animacy = args['gender_animacy']
	z.gender = _.extract(z.gender_animacy, '([mnf])%-[a-z]+')
	z.animacy = _.extract(z.gender_animacy, '[mnf]%-([a-z]+)')
	
	z.stress_type = args['stress_type']
	if not _.equals(z.stress_type, {'a', 'b', "b'", 'c', 'd', "d'", 'e', 'f', "f'", "f''"}) then
		return {error_msg = 'Error in template {{ru-decl-noun-z}}: Wrong value for the argument "stress_type"'}
	end
	z.stress_flags = get_stress_flags(z.stress_type)

	z.specific = args['specific']

	-- Remove ending (-а, -е, -ё, -о, -я, -й, -ь) to get stem
	z.stem = _.replace(z.word, '[аеёояйьиы]$', '')
	z.stem_stressed = _.replace(z.word_stressed, '[аеёиоыяйь]́ ?$', '')

	-- Add stress to stem_stressed if stress is absent (i.e. there is only one syllable or stress was on the ending)
	if not _.contains(z.stem_stressed, '[́ ё]') then
		if _.equals(z.stress_type, {"f", "f'"}) then
			z.stem_stressed = _.replace(z.stem_stressed, '^({consonant}*)({vowel})', '%1%2́ ')
		else
		-- if _.equals(stress_type, {'a', "b'", 'b', 'c', 'd', "d'", 'e'}) then
			z.stem_stressed = _.replace(z.stem_stressed, '({vowel})({consonant}*)$', '%1́ %2')
		end
		-- TODO: process cases with * (stress on penultimate syllable)
	end

	-- Determination of stem type
	get_stem_type(z)

	-- Special changes in endings for velar, sibilant, vowel etc. stem types
	change_endings_for_other_stem_types(z)

	-- apply special cases (1) or (2) in specific
	apply_specific_1_2(z)

	-- Resolve stressed/unstressed cases of endings
	choose_stress_for_endings(z)

	if z.gender == 'n' and _.endswith(z.word, 'ё') then
		z.e['n']['soft']['nom_sg'] = 'ё'
	end

	-- Choose endings by stem type and gender (either 'hard' or 'soft' key from changed "z.e")
	choose_endings_by_stem_type(z)

	-- If we have "ё" specific
	if _.contains(z.specific, 'ё') then
		z.stem_stressed = _.replace(z.stem_stressed, 'е́?([^е]*)$', 'ё%1')
	end

	z.stems = {}
	apply_stress_type(z)

	apply_specific_degree(z)

	-- Changes for reduceable cases
	z.stems['nom_sg'] = z.stems['sg']  -- *save* unchanged stem value for nominative singular
	z.stems['gen_pl'] = z.stems['pl']  -- set default stem value for genitive plural	
	apply_specific_reducable(z)

	-- If we have "ё" specific
	if _.contains(z.specific, 'ё') and not _.contains(z.endings['gen_pl'], '{vowel+ё}') and not _.contains(z.stems['gen_pl'], 'ё') then
		z.stems['gen_pl'] = _.replace(z.stems['gen_pl'], 'е́?([^е]*)$', 'ё%1')
		z.specific = z.specific .. 'ё'
	end

	-- Generate forms
	local forms = {
		nom_sg = z.stems['nom_sg'] .. z.endings['nom_sg'],
		gen_sg = z.stems['sg'] .. z.endings['gen_sg'],
		dat_sg = z.stems['sg'] .. z.endings['dat_sg'],
		acc_sg = '',
		ins_sg = z.stems['ins_sg'] .. z.endings['ins_sg'],
		prp_sg = z.stems['sg'] .. z.endings['prp_sg'],
		nom_pl = z.stems['nom_pl'] .. z.endings['nom_pl'],
		gen_pl = z.stems['gen_pl'] .. z.endings['gen_pl'],
		dat_pl = z.stems['pl'] .. z.endings['dat_pl'],
		acc_pl = '',
		ins_pl = z.stems['pl'] .. z.endings['ins_pl'],
		prp_pl = z.stems['pl'] .. z.endings['prp_pl'],
	}

	-- Add stress if there is no one
	if _.contains_several(forms['nom_sg'], '{vowel}') and not _.contains(forms['nom_sg'], '[́ ё]') then
		-- perhaps this is redundant for nom_sg?
		forms['nom_sg'] = _.replace(forms['nom_sg'], '({vowel})({consonant}*)$', '%1́ %2')
	end
	if _.contains_several(forms['gen_pl'], '{vowel+ё}') and not _.contains(forms['gen_pl'], '[́ ё]') then
		forms['gen_pl'] = _.replace(forms['gen_pl'], '({vowel})({consonant}*)$', '%1́ %2')
	end

	for key, value in pairs(forms) do
		-- Remove stress if there is only one syllable
		if _.contains_once(value, '{vowel+ё}') then
			forms[key] = _.replace(value, '́ ', '')
		end
		-- Replace 'ё' with 'е' when unstressed
		if _.contains_once(value, 'ё') and _.contains(value, '́ ') then
			forms[key] = _.replace(value, 'ё', 'е')
		end
	end

	choose_accusative_forms(z, forms)

	-- partitive case
	if args['par'] and args['par'] ~= '' then
		forms['par'] = forms['dat_sg']
	end

	-- locative case
	if args['loc'] and args['loc'] ~= '' then
		loc = forms['dat_sg']
		loc = _.replace(loc, '́ ', '')
		loc = _.replace(loc, 'ё', 'е')
		loc = _.replace(loc, '({vowel})({consonant}*)$', '%1́ %2')
		forms['loc'] = loc
		if args['loc'] == 'в' or args['loc'] == 'на' then
			forms['loc_preposition'] = '(' .. args['loc'] .. ')'
		else
			forms['loc_preposition'] = '(в, на) '
		end
	end

	forms['stem_type'] = z.stem_type  -- for testcases
	forms['stress_type'] = z.stress_type  -- for categories
	forms['index'] = get_zaliznyak_index(z)
	forms['dev'] = dev_prefix

	local keys = {
		'nom_sg', 'gen_sg', 'dat_sg', 'acc_sg', 'ins_sg', 'prp_sg', 
		'nom_pl', 'gen_pl', 'dat_pl', 'acc_pl', 'ins_pl', 'prp_pl', 
		'voc', 'n', 'note',
	}
	for i, key in pairs(keys) do
		if args[key] and args[key] ~= '' then
			forms[key] = args[key]
		end
		--[=[ -- I think we don't need this here, let's do it in template
		if form_key == 'nom_sg' then
			if forms[form_key] and forms[form_key] ~= '' then
				value = forms[form_key]
				transliterated = lang:transliterate(m_links.remove_links(value))
				link = m_links.full_link(value, nil, lang, nil, nil, nil, {tr = "-"}, false)
				forms[form_key] = strutils.format("{link}<br/><span style='color: #888'>{transliterated}</span>", {link=link, transliterated=transliterated})
			else
				forms[form_key] = '&mdash;'
			end
		end
		]=]--
	end
	
	return forms
end

return export