This module needs documentation.
Please document this module by describing its purpose and usage on the documentation page.

local declensions = {}

local com = require("Module:sla-common")
local m_table_tools = require("Module:table tools")
local lang = require("Module:languages").getByCode("sla-pro")

local u = mw.ustring.char
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 uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local ugmatch = mw.ustring.gmatch

local GRAVE = u(0x0300) -- grave =  ̀
local TILDE = u(0x0303) -- tilde =  ̃
local INVBREVE = u(0x0311) -- inverse breve =  ̑
local MACRON = u(0x0304) -- macron =  ̄
local UNK = "-" -- unknown accent (placeholder; existing accents removed)
local LEAVE = nil -- leave the accent alone

local contract_across_j_note = "The second form occurs in languages that contract early across /j/ (e.g. Czech), while the first form occurs in languages that do not (e.g. Russian)."
local a_stem_loc_pl_note = "''-asъ'' is the expected Balto-Slavic form but is found only in some Old Czech documents; ''-axъ'' is found everywhere else and is formed by analogy with other locative plurals in ''-xъ''."
local hard_o_stem_ins_sg_note = "''-ъmь'' in North Slavic, ''-omь'' in South Slavic."
local soft_o_stem_ins_sg_note = "''-ьmь'' in North Slavic, ''-emь'' in South Slavic."
local cons_stem_alt_pl_note = "''-ьmъ''/etc. are the original consonant-stem endings, while ''-amъ''/etc. are later Common Slavic endings formed by analogy with a-stems."

local function combine_stem_ending(stem, endinfo)
	if endinfo.override then
		return endinfo.override
	end
	local accent = endinfo[1]
	local ending = endinfo[2]
	if endinfo.pal1 then
		stem = com.first_palatalization(stem)
	elseif endinfo.pal2 then
		stem = com.second_palatalization(stem)
	elseif endinfo.iotate then
		stem = com.iotate(stem)
	end
	if accent then
		stem = com.set_accent(stem, accent)
	end
	return stem .. ending
end

local next_notesym_table = {
	["*"]="**",
	["**"]="***",
	["***"]="†",
	["†"]="††",
	["††"]="†††",
	["†††"]="‡",
	["‡"]="‡‡",
	["‡‡"]="‡‡",
	["‡‡‡"]="1"
}

local function get_next_notesym(sym)
	return next_notesym_table[sym] or sym + 1
end
	
-- Value is either a single string (substitute that exact string),
-- an "ending table" or a list of ending tables. An ending table is
-- a table of one item (a string, substitute that exact string) or
-- two items (the accent to apply to the stem, and the ending).
-- Either form can have optional named values attached. In both
-- forms, 'footnote' can be used to specify a footnote; in the
-- two-item form, 'pal1', 'pal2', and 'iotate' can additionally be
-- given to cause the appropriate modification to the end of the
-- stem.
local function add_form(data, stem, endinfo)
	if type(endinfo) ~= "table" then
		endinfo = {endinfo}
	end
	if type(endinfo[1]) ~= "table" then
		endinfo = {endinfo}
	end
	local retval = {}
	for _, endi in ipairs(endinfo) do
		local form
		if endi[2] then
			form = combine_stem_ending(stem, endi)
		else
			form = endi[1]
		end
		if endi.footnote then
			local notesym = data.footnote_to_sym[endi.footnote]
			if not notesym then
				notesym = data.next_notesym
				data.next_notesym = get_next_notesym(notesym)
				data.footnote_to_sym[endi.footnote] = notesym
				table.insert(data.footnotes, m_table_tools.superscript_notes(notesym) .. " " .. endi.footnote)
			end
			table.insert(retval, {form, notesym=notesym})
		else
			table.insert(retval, form)
		end
	end
	return retval
end
		
-- Add a full set of forms to the forms in FORMS for a given stem and the
-- ending specs for all endings. The value of SG, DU and PL is a table of
-- ending specs, containing entries for individual cases (n=nominative,
-- a=accusative, g=genitive, l=locative, d=dative, i=instrumental, v=vocative).
-- All seven values should be provided for the singular; all but vocative
-- should be provided for the plural (vocative plural is always the same as
-- nominative plural); for the dual, only nominative, genitive and dative
-- should be provided (accusative and vocative are the same as nominative,
-- locative is the same as genitive, and instrumental is the same as dative).
--
-- Each ending spec is either a string (use that exact string for the entire
-- case form) or a table SPEC indicating how to construct the case form, or
-- a list of such table SPECS if there are multiple possibilities. SPEC should
-- be either a list containing a single string (use that exact string for the
-- entire case form), or a list containing two strings and optional additional
-- flags. In the latter case, where SPEC[1] is the accent to place on the
-- stem (or an empty string to remove any stressed accent but leave macrons,
-- or the value UNK [for "unknown"] to remove all accents, or the value
-- LEAVE to leave any existing accents), SPEC[2] is the ending to add to the
-- stem, optional SPEC.pal1 causes the stem to have the first palatalization
-- applied, optional SPEC.pal2 causes the stem to have the second palatalization 
-- applied, optional SPEC.iotate causes the stem to have iotation applied.
local function add_forms(data, forms, stem, sg, du, pl)
	forms.nom = {
		s = add_form(data, stem, sg.n),
		d = add_form(data, stem, du.n),
		p = add_form(data, stem, pl.n),
	}
	forms.acc = {
		s = add_form(data, stem, sg.a),
		d = forms.nom.d,
		p = add_form(data, stem, pl.a),
	}
	forms.gen = {
		s = add_form(data, stem, sg.g),
		d = add_form(data, stem, du.g),
		p = add_form(data, stem, pl.g),
	}
	forms.loc = {
		s = add_form(data, stem, sg.l),
		d = forms.gen.d,
		p = add_form(data, stem, pl.l),
	}
	forms.dat = {
		s = add_form(data, stem, sg.d),
		d = add_form(data, stem, du.d),
		p = add_form(data, stem, pl.d),
	}
	forms.ins = {
		s = add_form(data, stem, sg.i),
		d = forms.dat.d,
		p = add_form(data, stem, pl.i),
	}
	forms.voc = {
		s = add_form(data, stem, sg.v),
		d = forms.nom.d,
		p = forms.nom.p,
	}
end

-- All consonant stems (ū, r, masc/neut n, s, nt) behave very similarly so
-- we merge their functionality. All such stems have an infix composed of
-- a vowel (V) + a consonant (C), which occurs between the stem and ending in
-- all cases but the nominative/vocative (and accusative singular of neuters).
-- DOPAL should be true if the stem should be first-palatalized before the
-- infix, and ALTFEMPL is true if alternative endings in -am* should be given
-- as well (used for v-stems).
local function handle_consonant_stem(data, v, c, g, dopal, altfempl)
	local forms = {}
	local palstem = dopal and com.first_palatalization(data.stem) or data.stem
	local nonsyll = com.is_nonsyllabic(palstem)
	local ap = data.ap
	-- FIXME! Don't know the actual accents of *dьnь (pattern c),
	-- so treat as if unknown.
	if nonsyll and g == "m" then
		ap = nil
	end
	
	if ap == "a" or ap == "b" then
		add_forms(data, forms, palstem .. v .. (data.ap == "b" and GRAVE or "") .. c,
			-- FIXME! Need to check voc, need accents for dual
			-- FIXME! Formerly had ьmi for inst pl of all genders
			{n=data.nom_sg, a=g == "n" and data.nom_sg or {GRAVE, "ь"}, g={GRAVE, "e"}, l={GRAVE, "e"}, d={GRAVE, "i"}, i=g == "f" and {{GRAVE, "ьjǫ"}, {GRAVE, "ǭ", iotate=true, footnote=contract_across_j_note}} or {GRAVE, "ьmь"}, v=data.nom_sg},
			{n={GRAVE, "i"}, g={GRAVE, "u"},
				d=altfempl and {{GRAVE, "ьma"}, {GRAVE, "ama", footnote=cons_stem_alt_pl_note}} or {GRAVE, "ьma"}},
			{n={GRAVE, g == "m" and "e" or g == "f" and "i" or "ā"}, a={GRAVE, g == "n" and "ā" or "i"}, g={GRAVE, "ъ"},
				l=altfempl and {{GRAVE, "ьxъ"}, {GRAVE, "axъ", footnote=cons_stem_alt_pl_note}} or {GRAVE, "ьxъ"},
				d=altfempl and {{GRAVE, "ьmъ"}, {GRAVE, "amъ", footnote=cons_stem_alt_pl_note}} or {GRAVE, "ьmъ"},
				i=altfempl and {{GRAVE, "ьmī"}, {GRAVE, "amī", footnote=cons_stem_alt_pl_note}} or {GRAVE, g == "n" and "ȳ" or "ьmī"}
			}
		)
	elseif ap == "c" then
		local ldac = nonsyll and INVBREVE or TILDE
		add_forms(data, forms, palstem .. v .. c,
			-- FIXME! Need to check voc
			-- FIXME! Formerly had ьmi for inst pl of all genders
			{n=data.nom_sg, a=g == "n" and data.nom_sg or {INVBREVE, "ь"}, g={INVBREVE, "e"}, l={INVBREVE, "e"}, d={INVBREVE, "i"}, i=g == "f" and {"", "ьjǫ́"} or {INVBREVE, "ьmь"}, v=data.nom_sg},
			{n={INVBREVE, "i"}, g={"", "ù"},
				d=altfempl and {{"", "ьmà"}, {"", "àma", footnote=cons_stem_alt_pl_note}} or {"", "ьmà"}},
			{n=g == "n" and {"", "à"} or g == "m" and {INVBREVE, "e"} or {INVBREVE, "i"}, a=g == "n" and {"", "à"} or {INVBREVE, "i"}, g={TILDE, "ъ"},
				l=altfempl and {{ldac, "ьxъ"}, {ldac, "axъ", footnote=cons_stem_alt_pl_note}} or {ldac, "ьxъ"},
				d=altfempl and {{ldac, "ьmъ"}, {ldac, "amъ", footnote=cons_stem_alt_pl_note}} or {ldac, "ьmъ"},
				i=altfempl and {{"", "ьmì"}, {"", "amì", footnote=cons_stem_alt_pl_note}} or {"", g == "n" and "ý" or "ьmì"}
			}
		)
	else
		add_forms(data, forms, palstem .. v .. c,
			-- FIXME! Need to check voc, need accents for dual
			-- FIXME! Formerly had ьmi for inst pl of all genders
			{n=data.nom_sg, a=g == "n" and data.nom_sg or {UNK, "ь"}, g={UNK, "e"}, l={UNK, "e"}, d={UNK, "i"}, i=g == "f" and {{UNK, "ьjǫ"}, {UNK, "ǫ", iotate=true, footnote=contract_across_j_note}} or {UNK, "ьmь"}, v=nonsyll and g == "m" and {UNK, "y"} or data.nom_sg},
			{n={UNK, "i"}, g={UNK, "u"},
				d=altfempl and {{UNK, "ьma"}, {UNK, "ama", footnote=cons_stem_alt_pl_note}} or {UNK, "ьma"}},
			{n={UNK, g == "m" and "e" or g == "f" and "i" or "a"}, a={UNK, g == "n" and "a" or "i"}, g={UNK, "ъ"},
				l=altfempl and {{UNK, "ьxъ"}, {UNK, "axъ", footnote=cons_stem_alt_pl_note}} or {UNK, "ьxъ"},
				d=altfempl and {{UNK, "ьmъ"}, {UNK, "amъ", footnote=cons_stem_alt_pl_note}} or {UNK, "ьmъ"},
				i=altfempl and {{UNK, "ьmi"}, {UNK, "ami", footnote=cons_stem_alt_pl_note}} or {UNK, g == "n" and "y" or "ьmi"}
			}
		)
	end
 
	return forms
end

declensions["v-stem"] = function(data)
	if data.desinence ~= "y" and data.desinence ~= "i" then
		error("u-stem nouns must end in -y or -i")
	end

	return handle_consonant_stem(data, "ъ", "v", "f", false, "altfempl"), "v-stem"
end

declensions["s-stem"] = function(data)
	if data.desinence ~= "e" and data.desinence ~= "o" then
		error("s-stem nouns must end in -e or -o")
	end

	return handle_consonant_stem(data, "e", "s", "n", "palatalize"), "s-stem"
end

declensions["r-stem"] = function(data)
	if (data.unom_sg ~= "mati") and (data.unom_sg ~= "dъťi") then
		error("Must be used only for nouns *mati and *dъťi")
	end

	return handle_consonant_stem(data, "e", "r", "f", "palatalize"), "r-stem"
end

declensions["nt-stem"] = function(data)
	if data.desinence ~= "ę" then
		error("nt-stem nouns must end in -ę")
	end

	return handle_consonant_stem(data, "ę", "t", "n", "palatalize"), "nt-stem"
end

declensions["n-stem"] = function(data)
	local forms

	-- masculine nouns have desinence end in -nь or -y, neuters in -ę
	local y_type = data.desinence == "y"
	local ni_type = data.desinence == "ь"
	local e_type = data.desinence == "ę"

	if not(y_type or ni_type or e_type) then
		error("Wrong ending for an n-stem noun")
	end

	if ni_type then
		if data.ustem ~= "dьn" then
			error("n-stem noun ending in -ь must be *dьnь")
		end
		data.stem = "d"
		data.ustem = "d"
		forms = handle_consonant_stem(data, "", "", "m", "palatalize")
	else
		forms = handle_consonant_stem(data, "e", "n", y_type and "m" or "n", "palatalize")
	end

	return forms, "n-stem", e_type and {"neuter n-stem nouns"} or {"masculine n-stem nouns"}
end

-- Masculine and feminine i-stems differ only in the instrumental singular and
-- nominative plural so handle them together.
local function handle_i_stem(data, masc)
	local forms = {}

	if data.ap == "a" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc, dual
			{n=data.nom_sg, a=data.nom_sg, g={GRAVE, "ī"}, l={GRAVE, "ī"}, d={GRAVE, "i"}, i=masc and {GRAVE, "ьmь"} or {{GRAVE, "ьjǫ"}, {GRAVE, "ǭ", iotate=true, footnote=contract_across_j_note}}, v={UNK, "i"}},
			{n={GRAVE, "i"}, g={{GRAVE, "ьju"}, {GRAVE, "u", iotate=true, footnote=contract_across_j_note}}, d={GRAVE, "ьma"}},
			{n=masc and {{GRAVE, "ьjē"}, {GRAVE, "ē", iotate=true, footnote=contract_across_j_note}} or {GRAVE, "i"}, a={GRAVE, "i"}, g={{GRAVE, "ьjь"}, {GRAVE, "ī", footnote=contract_across_j_note}}, l={GRAVE, "ьxъ"}, d={GRAVE, "ьmъ"}, i={GRAVE, "ьmī"}}
		)
	elseif data.ap == "b" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc, dual
			{n={TILDE, "ь"}, a={TILDE, "ь"}, g={TILDE, "i"}, l={TILDE, "i"}, d={MACRON, "ì"}, i=masc and {MACRON, "ь̀mь"} or {{TILDE, "ьjǫ"}, {TILDE, "ǫ", iotate=true, footnote=contract_across_j_note}}, v={UNK, "i"}},
			{n={MACRON, "ì"}, g={{TILDE, "ьju"}, {UNK, "u", iotate=true, footnote=contract_across_j_note}}, d={TILDE, "ьma"}},
			{n=masc and {{TILDE, "ьjē"}, {TILDE, "ē", iotate=true, footnote=contract_across_j_note}} or {MACRON, "ì"}, a={MACRON, "ì"}, g={{MACRON, "ь̀jь"}, {TILDE, "i", footnote=contract_across_j_note}}, l={MACRON, "ь̀xъ"}, d={MACRON, "ь̀mъ"}, i={TILDE, "ьmī"}}
		)
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc
			{n={INVBREVE, "ь"}, a={INVBREVE, "ь"}, g={"", "í"}, l={"", "í"}, d={INVBREVE, "i"}, i=masc and {INVBREVE, "ьmь"} or {"", "ьjǫ́"}, v={UNK, "i"}},
			{n={INVBREVE, "i"}, g={{"", "ьjù"}, {UNK, "u", iotate=true, footnote=contract_across_j_note}}, d={"", "ьmà"}},
			{n=masc and {{INVBREVE, "ьjē"}, {INVBREVE, "ē", iotate=true, footnote=contract_across_j_note}} or {INVBREVE, "i"}, a={INVBREVE, "i"}, g={"", "ь̀jь"}, l={INVBREVE, "ьxъ"}, d={INVBREVE, "ьmъ"}, i={"", "ьmì"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={UNK, "i"}, l={UNK, "i"}, d={UNK, "i"}, i=masc and {UNK, "ьmь"} or {{UNK, "ьjǫ"}, {UNK, "ǫ", iotate=true, footnote=contract_across_j_note}}, v={UNK, "i"}},
			{n={UNK, "i"}, g={{UNK, "ьju"}, {UNK, "u", iotate=true, footnote=contract_across_j_note}}, d={UNK, "ьma"}},
			{n=masc and {{UNK, "ьje"}, {UNK, "e", iotate=true, footnote=contract_across_j_note}} or {UNK, "i"}, a={UNK, "i"}, g={{UNK, "ьjь"}, {UNK, "i", footnote=contract_across_j_note}}, l={UNK, "ьxъ"}, d={UNK, "ьmъ"}, i={UNK, "ьmi"}}
		)
	end

	return forms, "i-stem", masc and {"masculine i-stem nouns"} or {"feminine i-stem nouns"}
end

declensions["feminine i-stem"] = function(data)
	return handle_i_stem(data, false)
end

declensions["masculine i-stem"] = function(data)
	return handle_i_stem(data, "masc")
end

declensions["u-stem"] = function(data)
	local forms = {}

	if data.desinence ~= "ъ" then
		error("u-stem nouns must end in -ъ")
	end
	
	if data.ap == "a" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc, dual
			{n=data.nom_sg, a=data.nom_sg, g={GRAVE, "u"}, l={GRAVE, "ū"}, d={GRAVE, "ovi"}, i={GRAVE, "ъmь"}, v={GRAVE, "u"}},
			{n={GRAVE, "y"}, g={GRAVE, "ovu"}, d={GRAVE, "ъma"}},
			{n={GRAVE, "ove"}, a={GRAVE, "y"}, g={GRAVE, "ovъ"}, l={GRAVE, "ъxъ"}, d={GRAVE, "ъmъ"}, i={GRAVE, "ъmī"}}
		)
	elseif data.ap == "b" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc
			{n={TILDE, "ъ"}, a={TILDE, "ъ"}, g={MACRON, "ù"}, l={TILDE, "u"}, d={MACRON, "òvi"}, i={MACRON, "ъ̀mь"}, v={UNK, "u"}},
			{n={MACRON, "ỳ"}, g={MACRON, "òvu"}, d={TILDE, "ъma"}},
			{n={MACRON, "òve"}, a={MACRON, "ỳ"}, g={MACRON, "òvъ"}, l={MACRON, "ъ̀xъ"}, d={MACRON, "ъ̀mъ"}, i={TILDE, "ъmī"}}
		)
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc
			{n={INVBREVE, "ъ"}, a={INVBREVE, "ъ"}, g={INVBREVE, "u"}, l={"", "ú"}, d={INVBREVE, "ovi"}, i={INVBREVE, "ъmь"}, v={UNK, "u"}},
			{n={INVBREVE, "y"}, g={"", "ovù"}, d={"", "ъmà"}},
			{n={INVBREVE, "ove"}, a={INVBREVE, "y"}, g={"", "òvъ"}, l={INVBREVE, "ъxъ"}, d={INVBREVE, "ъmъ"}, i={"", "ъmì"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={UNK, "u"}, l={UNK, "u"}, d={UNK, "ovi"}, i={UNK, "ъmь"}, v={UNK, "u"}},
			{n={UNK, "y"}, g={UNK, "ovu"}, d={UNK, "ъma"}},
			{n={UNK, "ove"}, a={UNK, "y"}, g={UNK, "ovъ"}, l={UNK, "ъxъ"}, d={UNK, "ъmъ"}, i={UNK, "ъmi"}}
		)
	end

	return forms, "u-stem"
end

declensions["hard a-stem"] = function(data)
	local forms = {}

	if data.ap == "a" then
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a={GRAVE, "ǫ"}, g={GRAVE, "y"}, l={GRAVE, "ě", pal2=true}, d={GRAVE, "ě", pal2=true}, i={{GRAVE, "ojǫ"}, {GRAVE, "ǭ", footnote=contract_across_j_note}}, v={GRAVE, "o"}},
			{n={GRAVE, "ě", pal2=true}, g={GRAVE, "u"}, d={GRAVE, "ama"}},
			{n={GRAVE, "y"}, a={GRAVE, "y"}, g={GRAVE, "ъ"}, l={{GRAVE, "asъ"}, {GRAVE, "axъ", footnote=a_stem_loc_pl_note}}, d={GRAVE, "amъ"}, i={GRAVE, "amī"}}
		)
	elseif data.ap == "b" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc
			{n={MACRON, "à"}, a={MACRON, "ǫ̀"}, g={MACRON, "ỳ"}, l={MACRON, "ě̀", pal2=true}, d={MACRON, "ě̀", pal2=true}, i={{MACRON, "òjǫ"}, {TILDE, "ǫ", footnote=contract_across_j_note}}, v={UNK, "o"}},
			{n={TILDE, "ě", pal2=true}, g={MACRON, "ù"}, d={MACRON, "àma"}},
			{n={MACRON, "ỳ"}, a={MACRON, "ỳ"}, g={TILDE, "ъ"}, l={{MACRON, "àsъ"}, {MACRON, "àxъ", footnote=a_stem_loc_pl_note}}, d={MACRON, "àmъ"}, i={MACRON, "àmī"}}
		)
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc
			{n={"", "à"}, a={INVBREVE, "ǫ"}, g={"", "ý"}, l={INVBREVE, "ě", pal2=true}, d={"", "ě̀", pal2=true}, i={"", "ojǫ́"}, v={UNK, "o"}},
			{n={INVBREVE, "ě", pal2=true}, g={"", "ù"}, d={"", "àma"}},
			{n={INVBREVE, "y"}, a={INVBREVE, "y"}, g={TILDE, "ъ"}, l={{"", "àsъ"}, {"", "àxъ", footnote=a_stem_loc_pl_note}}, d={"", "àmъ"}, i={"", "àmi"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a={UNK, "ǫ"}, g={UNK, "y"}, l={UNK, "ě", pal2=true}, d={UNK, "ě", pal2=true}, i={{UNK, "ojǫ"}, {UNK, "ǫ", footnote=contract_across_j_note}}, v={LEAVE, "o"}},
			{n={UNK, "ě", pal2=true}, g={UNK, "u"}, d={UNK, "ama"}},
			{n={UNK, "y"}, a={UNK, "y"}, g={UNK, "ъ"}, l={{UNK, "asъ"}, {UNK, "axъ", footnote=a_stem_loc_pl_note}}, d={UNK, "amъ"}, i={UNK, "ami"}}
		)
	end

	return forms, "hard a-stem"
end

declensions["soft a-stem"] = function(data)
	local forms = {}
	local stem = data.ustem
	local title
	-- ī-stems share the same endings as soft a-stems in all cases except for the nominative singular, so they are handled as a
	-- special case of them.
	-- for genuine ī-stems that are not preceded by a soft, palatal consonant (e.g. olni) we add "j" everywhere except in nom_sg	
	if data.desinence == "i" then
		title = "ī-stem"
		stem = com.iotate(stem)
	else
		if not rfind(stem, "[cčďjľňřšťž]$") and not rfind(stem, "dz$") then
			error("Soft a-stem must end in a soft consonant (given: " .. stem .. ")")
		end
		title = "soft a-stem"
	end
	
	if data.ap == "a" then
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a={GRAVE, "ǫ"}, g={GRAVE, "ę̇"}, l={GRAVE, "ī"}, d={GRAVE, "ī"}, i={{GRAVE, "ējǫ"}, {GRAVE, "ǭ", footnote=contract_across_j_note}}, v={GRAVE, "e"}},
			{n={GRAVE, "i"}, g={GRAVE, "u"}, d={GRAVE, "ama"}},
			{n={GRAVE, "ę̇"}, a={GRAVE, "ę̇"}, g={GRAVE, "ь"}, l={GRAVE, "āsъ"}, d={GRAVE, "āmъ"}, i={GRAVE, "āmī"}}
		)
	elseif data.ap == "b" then
		-- Some accent-b ja-stem nouns behave more like a-stem nouns (e.g.
		-- *svě̄ťà), while others take the standard form with neoacute accent
		-- (vòlja). Remember that data.nom_sg and data.stem are decomposed.
		if rfind(data.nom_sg, "a" .. GRAVE .. "$") then
			add_forms(data, forms, data.stem,
				-- FIXME! Need accents for voc
				{n={MACRON, "à"}, a={MACRON, "ǫ̀"}, g={MACRON, "ę̇̀"}, l={MACRON, "ì"}, d={MACRON, "ì"}, i={{MACRON, "èjǫ"}, {TILDE, "ǫ", footnote=contract_across_j_note}}, v={UNK, "e"}},
				{n={TILDE, "i"}, g={MACRON, "ù"}, d={MACRON, "àma"}},
				{n={MACRON, "ę̇̀"}, a={MACRON, "ę̇̀"}, g={TILDE, "ь"}, l={{MACRON, "àsъ"}, {MACRON, "àxъ", footnote=a_stem_loc_pl_note}}, d={MACRON, "àmъ"}, i={MACRON, "àmī"}}
			)
		else
			add_forms(data, forms, data.stem,
				-- FIXME! Need accents for voc
				{n={TILDE, "a"}, a={TILDE, "ǫ"}, g={TILDE, "ę̇"}, l={TILDE, "i"}, d={TILDE, "i"}, i={{TILDE, "ejǫ"}, {TILDE, "ǫ", footnote=contract_across_j_note}}, v={UNK, "e"}},
				{n={TILDE, "i"}, g={TILDE, "u"}, d={TILDE, "ama"}},
				{n={TILDE, "ę̇"}, a={TILDE, "ę̇"}, g={TILDE, "ь"}, l={{TILDE, "asъ"}, {TILDE, "axъ", footnote=a_stem_loc_pl_note}}, d={TILDE, "amъ"}, i={TILDE, "amī"}}
			)
		end
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for voc
			{n={"", "à"}, a={INVBREVE, "ǫ"}, g={"", "ę̇́"}, l={INVBREVE, "ī"}, d={"", "ì"}, i={"", "ejǫ́"}, v={UNK, "e"}},
			{n={INVBREVE, "i"}, g={"", "ù"}, d={"", "àma"}},
			{n={INVBREVE, "ę̇"}, a={INVBREVE, "ę̇"}, g={TILDE, "ь"}, l={{"", "àsъ"}, {"", "àxъ", footnote=a_stem_loc_pl_note}}, d={"", "àmъ"}, i={"", "àmi"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a={UNK, "ǫ"}, g={UNK, "ę̇"}, l={UNK, "i"}, d={UNK, "i"}, i={{UNK, "ejǫ"}, {UNK, "ǫ", footnote=contract_across_j_note}}, v={LEAVE, "e"}},
			{n={UNK, "i"}, g={UNK, "u"}, d={UNK, "ama"}},
			{n={UNK, "ę̇"}, a={UNK, "ę̇"}, g={UNK, "ь"}, l={{UNK, "asъ"}, {UNK, "axъ", footnote=a_stem_loc_pl_note}}, d={UNK, "amъ"}, i={UNK, "ami"}}
		)
	end

	return forms, title
end

declensions["soft neuter o-stem"] = function(data)
	local forms = {}

	-- FIXME! No forms given for ap=a in Verweij
	if data.ap == "b" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for dual
			{n={MACRON, "è"}, a={MACRON, "è"}, g={MACRON, "à"}, l={MACRON, "ì"}, d={MACRON, "ù"}, i={{MACRON, "ь̀mь"}, {MACRON, "èmь", footnote=soft_o_stem_ins_sg_note}}, v={MACRON, "è"}},
			{n={TILDE, "i"}, g={UNK, "u"}, d={UNK, "ema"}}, -- The plural has neoacute throughout. Does the dual have this also?
			{n={TILDE, "a"}, a={TILDE, "a"}, g={TILDE, "ь"}, l={TILDE, "ixъ"}, d={TILDE, "emъ"}, i={TILDE, "i"}}
		)
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			{n={INVBREVE, "e"}, a={INVBREVE, "e"}, g={INVBREVE, "a"}, l={INVBREVE, "i"}, d={INVBREVE, "u"}, i={{INVBREVE, "ьmь"}, {INVBREVE, "emь", footnote=soft_o_stem_ins_sg_note}}, v={INVBREVE, "e"}},
			{n={INVBREVE, "i"}, g={"", "ù"}, d={"", "emà"}},
			{n={"", "à"}, a={"", "à"}, g={TILDE, "ь"}, l={"", "íxъ"}, d={"", "émъ"}, i={"", "í"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={UNK, "a"}, l={UNK, "i"}, d={UNK, "u"}, i={{UNK, "ьmь"}, {UNK, "emь", footnote=soft_o_stem_ins_sg_note}}, v=data.nom_sg},
			{n={UNK, "i"}, g={UNK, "u"}, d={UNK, "ema"}},
			{n={UNK, "a"}, a={UNK, "a"}, g={UNK, "ь"}, l={UNK, "ixъ"}, d={UNK, "emъ"}, i={UNK, "i"}}
		)
	end

	return forms, "soft o-stem", {"soft neuter o-stem nouns"}
end

declensions["hard neuter o-stem"] = function(data)
	local forms = {}

	if data.ap == "a" then
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={GRAVE, "a"}, l={GRAVE, "ě", pal2=true}, d={GRAVE, "u"}, i={{GRAVE, "ъmь"}, {GRAVE, "omь", footnote=hard_o_stem_ins_sg_note}}, v=data.nom_sg},
			{n={GRAVE, "ě", pal2=true}, g={GRAVE, "u"}, d={GRAVE, "oma"}},
			{n={GRAVE, "a"}, a={GRAVE, "a"}, g={GRAVE, "ъ"}, l={GRAVE, "ě̄xъ", pal2=true}, d={GRAVE, "omъ"}, i={GRAVE, "ȳ"}}
		)
	elseif data.ap == "b" then
		add_forms(data, forms, data.stem,
			{n={MACRON, "ò"}, a={MACRON, "ò"}, g={MACRON, "à"}, l={MACRON, "ě̀", pal2=true}, d={MACRON, "ù"}, i={{MACRON, "ъ̀mь"}, {MACRON, "òmь", footnote=hard_o_stem_ins_sg_note}}, v={MACRON, "ò"}},
			{n={TILDE, "ě", pal2=true}, g={MACRON, "ù"}, d={MACRON, "òma"}},
			{n={MACRON, "à"}, a={MACRON, "à"}, g={TILDE, "ъ"}, l={TILDE, "ěxъ", pal2=true}, d={MACRON, "òmъ"}, i={TILDE, "y"}}
		)
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			{n={INVBREVE, "o"}, a={INVBREVE, "o"}, g={INVBREVE, "a"}, l={INVBREVE, "ě", pal2=true}, d={INVBREVE, "u"}, i={{INVBREVE, "ъmь"}, {INVBREVE, "omь", footnote=hard_o_stem_ins_sg_note}}, v=data.nom_sg},
			{n={INVBREVE, "ě", pal2=true}, g={"", "ù"}, d={"", "omà"}},
			{n={"", "à"}, a={"", "à"}, g={TILDE, "ъ"}, l={"", "ě̃xъ", pal2=true}, d={"", "òmъ"}, i={"", "ý"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={UNK, "a"}, l={UNK, "ě", pal2=true}, d={UNK, "u"}, i={{UNK, "ъmь"}, {UNK, "omь", footnote=hard_o_stem_ins_sg_note}}, v=data.nom_sg},
			{n={UNK, "ě", pal2=true}, g={UNK, "u"}, d={UNK, "oma"}},
			{n={UNK, "a"}, a={UNK, "a"}, g={UNK, "ъ"}, l={UNK, "ěxъ", pal2=true}, d={UNK, "omъ"}, i={UNK, "y"}}
		)
	end

	return forms, "hard o-stem", {"hard neuter o-stem nouns"}
end

declensions["hard masculine o-stem"] = function(data)
	local forms = {}

	if data.ap == "a" then
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={GRAVE, "a"}, l={GRAVE, "ě", pal2=true}, d={GRAVE, "u"}, i={{GRAVE, "ъmь"}, {GRAVE, "omь", footnote=hard_o_stem_ins_sg_note}}, v={GRAVE, "e", pal1=true}},
			{n={GRAVE, "a"}, g={GRAVE, "u"}, d={GRAVE, "oma"}},
			{n={GRAVE, "i", pal2=true}, a={GRAVE, "y"}, g={GRAVE, "ъ"}, l={GRAVE, "ě̄xъ", pal2=true}, d={GRAVE, "omъ"}, i={GRAVE, "ȳ"}}
		)
	elseif data.ap == "b" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for vocative sg
			{n={TILDE, "ъ"}, a={TILDE, "ъ"}, g={MACRON, "à"}, l={MACRON, "ě̀", pal2=true}, d={MACRON, "ù"}, i={{MACRON, "ъ̀mь"}, {MACRON, "òmь", footnote=hard_o_stem_ins_sg_note}}, v={UNK, "e", pal1=true}},
			{n={MACRON, "à"}, g={MACRON, "ù"}, d={MACRON, "òma"}},
			{n={MACRON, "ì", pal2=true}, a={MACRON, "ỳ"}, g={TILDE, "ъ"}, l={TILDE, "ěxъ", pal2=true}, d={MACRON, "òmъ"}, i={TILDE, "y"}}
		)
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for vocative sg
			{n={INVBREVE, "ъ"}, a={INVBREVE, "ъ"}, g={INVBREVE, "a"}, l={INVBREVE, "ě", pal2=true}, d={INVBREVE, "u"}, i={{INVBREVE, "ъmь"}, {INVBREVE, "omь", footnote=hard_o_stem_ins_sg_note}}, v={UNK, "e", pal1=true}},
			{n={INVBREVE, "a"}, g={"", "ù"}, d={"", "omà"}},
			{n={INVBREVE, "i", pal2=true}, a={INVBREVE, "y"}, g={TILDE, "ъ"}, l={"", "ě̃xъ", pal2=true}, d={"", "òmъ"}, i={"", "ý"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={UNK, "a"}, l={UNK, "ě", pal2=true}, d={UNK, "u"}, i={{UNK, "ъmь"}, {UNK, "omь", footnote=hard_o_stem_ins_sg_note}}, v={UNK, "e", pal1=true}},
			{n={UNK, "a"}, g={UNK, "u"}, d={UNK, "oma"}},
			{n={UNK, "i", pal2=true}, a={UNK, "y"}, g={UNK, "ъ"}, l={UNK, "ěxъ", pal2=true}, d={UNK, "omъ"}, i={UNK, "y"}}
		)
	end

	return forms, "hard o-stem", {"hard masculine o-stem nouns"}
end

declensions["soft masculine o-stem"] = function(data)
	local forms = {}

	local voc_e = rfind(data.stem, "c$") or rfind(data.stem, "dz$") or
		rfind(data.stem, "sc$") or rfind(data.stem, "zdz$")

	if data.ap == "a" then
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={GRAVE, "a"}, l={GRAVE, "i"}, d={GRAVE, "u"}, i={{GRAVE, "ьmь"}, {GRAVE, "emь", footnote=soft_o_stem_ins_sg_note}}, v={GRAVE, voc_e and "e" or "u", pal1=true}},
			{n={GRAVE, "a"}, g={GRAVE, "u"}, d={GRAVE, "ema"}},
			{n={GRAVE, "i"}, a={GRAVE, "ę̇"}, g={GRAVE, "ь"}, l={GRAVE, "īxъ"}, d={GRAVE, "ēmъ"}, i={GRAVE, "ī"}}
		)
	elseif data.ap == "b" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for vocative sg
			{n={TILDE, "ь"}, a={TILDE, "ь"}, g={MACRON, "à"}, l={MACRON, "ì"}, d={MACRON, "ù"}, i={{MACRON, "ь̀mь"}, {MACRON, "èmь", footnote=soft_o_stem_ins_sg_note}}, v={UNK, voc_e and "e" or "u", pal1=true}},
			{n={MACRON, "à"}, g={MACRON, "ù"}, d={MACRON, "èma"}},
			{n={MACRON, "ì"}, a={MACRON, "ę̇̀"}, g={TILDE, "ь"}, l={TILDE, "ixъ"}, d={TILDE, "emъ"}, i={TILDE, "i"}}
		)
	elseif data.ap == "c" then
		add_forms(data, forms, data.stem,
			-- FIXME! Need accents for vocative sg
			{n={INVBREVE, "ь"}, a={INVBREVE, "ь"}, g={INVBREVE, "a"}, l={INVBREVE, "i"}, d={INVBREVE, "u"}, i={{INVBREVE, "ьmь"}, {INVBREVE, "emь", footnote=soft_o_stem_ins_sg_note}}, v={UNK, voc_e and "e" or "u", pal1=true}},
			{n={INVBREVE, "a"}, g={"", "ù"}, d={"", "emà"}},
			{n={INVBREVE, "i"}, a={INVBREVE, "ę̇"}, g={TILDE, "ь"}, l={"", "ĩxъ"}, d={"", "èmъ"}, i={"", "í"}}
		)
	else
		add_forms(data, forms, data.stem,
			{n=data.nom_sg, a=data.nom_sg, g={UNK, "a"}, l={UNK, "i"}, d={UNK, "u"}, i={{UNK, "ьmь"}, {UNK, "emь", footnote=soft_o_stem_ins_sg_note}}, v={UNK, voc_e and "e" or "u", pal1=true}},
			{n={UNK, "a"}, g={UNK, "u"}, d={UNK, "ema"}},
			{n={UNK, "i"}, a={UNK, "ę̇"}, g={UNK, "ь"}, l={UNK, "ixъ"}, d={UNK, "emъ"}, i={UNK, "i"}}
		)
	end

	return forms, "soft o-stem", {"soft masculine o-stem nouns"}
end

return declensions