Jump to content

Module:zlw-ocs-adjective

From Wiktionary, the free dictionary


local export = {}


--[=[

Authorship: Zhnka, heavily based on [[Module:cs-adjective]] by Benwing

]=]

--[=[

TERMINOLOGY:

-- "slot" = A particular combination of case/gender/number.
	 Example slot names for adjectives are "gen_f" (genitive feminine singular) and
	 "nom_mp" (animate nominative masculine plural). Each slot is filled with zero or more forms.

-- "form" = The declined Old Czech form representing the value of a given slot.

-- "lemma" = The dictionary form of a given Old Czech term. Generally the nominative
     masculine singular, but may occasionally be another form if the nominative
	 masculine singular is missing.
]=]

local lang = require("Module:languages").getByCode("zlw-ocs")
local m_links = require("Module:links")
local m_table = require("Module:table")
local m_string_utilities = require("Module:string utilities")
local iut = require("Module:inflection utilities")
local com = require("Module:zlw-ocs-common")

local current_title = mw.title.getCurrentTitle()
local NAMESPACE = current_title.nsText
local PAGENAME = current_title.text

local u = mw.ustring.char
local rsplit = mw.text.split
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rgmatch = mw.ustring.gmatch
local rsubn = mw.ustring.gsub
local ulen = mw.ustring.len
local uupper = mw.ustring.upper


-- 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


-- All slots that are used by any of the different tables. The key is the slot and the value is a list of the tables
-- that use the slot. "" = regular, "plonly" = special=plonly in {{zlw-ocs-adecl-manual}}, "dva" = special=dva in
-- {{zlw-ocs-adecl-manual}}.
local input_adjective_slots = {
	nom_m = {""},
	nom_f = {""},
	nom_n = {""},
	nom_md = {"", "duonly"},
	nom_fnd = {"", "duonly"},
	nom_mp = {"", "plonly"},
	nom_fp = {"", "plonly"},
	nom_np = {"", "plonly"},
	gen_mn = {""},
	gen_f = {""},
	gen_d = {"", "duonly"},
	gen_p = {"", "plonly"},
	dat_mn = {""},
	dat_f = {""},
	dat_d = {"", "duonly"},
	dat_p = {"", "plonly"},
	acc_m_an = {""},
	acc_m_in = {""},
	acc_f = {""},
	acc_n = {""},
	acc_md = {"", "duonly"},
	acc_fnd = {"", "duonly"},
	acc_mfp = {"", "plonly"},
	acc_np = {"", "plonly"},
	ins_mn = {""},
	ins_f = {""},
	ins_d = {"", "duonly"},
	ins_p = {"", "plonly"},
	loc_mn = {""},
	loc_f = {""},
	loc_d = {"", "duonly"},
	loc_p = {"", "plonly"},
}


local output_adjective_slots = {
	nom_m = "nom|m|s",
	nom_f = "nom|f|s",
	nom_n = "nom|n|s",
	nom_md = "nom|m|d",
	nom_fnd = "nom|f//n|d",
	nom_mp = "nom|m|p",
	nom_fp = "nom|f|p",
	nom_np = "nom|n|p",
	nom_mp = "nom|m|p",
	nom_fnp = "nom|f//n|p",
	gen_mn = "gen|m//n|s",
	gen_f = "gen|f|s",
	gen_d = "gen|d",
	gen_p = "gen|p",
	dat_mn = "dat|m//n|s",
	dat_f = "dat|f|s",
	dat_d = "dat|d",
	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",
	acc_md = "acc|m|d",
	acc_fnd = "acc|f//n|d",
	acc_mfp = "acc|m//f|p",
	acc_np = "acc|n|p",
	acc_mp = "acc|m|p",
	acc_fnp = "acc|f//n|p",
	ins_mn = "ins|m//n|s",
	ins_f = "ins|f|s",
	ins_d = "ins|d",
	ins_p = "ins|p",
	loc_mn = "loc|m//n|s",
	loc_f = "loc|f|s",
	loc_d = "loc|d",
	loc_p = "loc|p",
}


local function get_output_adjective_slots(alternant_multiword_spec)
	return output_adjective_slots
end


local function combine_stem_ending(stem, ending)
	if stem == "?" then
		return "?"
	else
		return stem .. ending
	end
end


local function add(base, slot, stems, endings, footnote)
	if stems then
		stems = iut.combine_form_and_footnotes(stems, footnote)
	end
	iut.add_forms(base.forms, slot, stems, endings, combine_stem_ending)
end


local function add_normal_decl(base, stems,
	nom_m, nom_f, nom_n, nom_md, nom_fnd, nom_mp, nom_fp, nom_np,
	gen_mn, gen_f, gen_d, gen_p,
	dat_mn, dat_f, dat_d, dat_p,
	acc_f,
	loc_mn, loc_f, loc_d, loc_p,
	ins_mn, ins_f, ins_d, ins_p,
	footnote)
	if stems then
		stems = iut.combine_form_and_footnotes(stems, footnote)
	end	add(base, "nom_m", stems, nom_m)
	add(base, "nom_f", stems, nom_f)
	add(base, "nom_n", stems, nom_n)
	add(base, "nom_md", stems, nom_md)
	add(base, "nom_fnd", stems, nom_fnd)
	add(base, "nom_mp", stems, nom_mp)
	add(base, "nom_fp", stems, nom_fp)
	add(base, "nom_np", stems, nom_np)
	add(base, "gen_mn", stems, gen_mn)
	add(base, "gen_f", stems, gen_f)
	add(base, "gen_d", stems, gen_d)
	add(base, "gen_p", stems, gen_p)
	add(base, "dat_mn", stems, dat_mn)
	add(base, "dat_f", stems, dat_f)
	add(base, "dat_d", stems, dat_d)
	add(base, "dat_p", stems, dat_p)
	add(base, "acc_f", stems, acc_f)
	add(base, "loc_mn", stems, loc_mn)
	add(base, "loc_f", stems, loc_f)
	add(base, "loc_d", stems, loc_d)
	add(base, "loc_p", stems, loc_p)
	add(base, "ins_mn", stems, ins_mn)
	add(base, "ins_f", stems, ins_f)
	add(base, "ins_d", stems, ins_d)
	add(base, "ins_p", stems, ins_p)
end

local decls = {}

decls["normal"] = function(base)
	local stem, suffix

	-- hard in -ý
	stem, suffix = rmatch(base.lemma, "^(.*)(ý)$")
	if stem then
		if vowel_alt ~= nil then
		add_normal_decl(base, com.apply_second_palatalization(vowel_alt, "is adj"), nil, nil, nil, nil, nil, "í")
		if not rfind(base.lemma, ".*lý$") then
		add_normal_decl(base, com.apply_second_palatalization(vowel_alt, "is adj"), nil, nil, nil, nil, "iej", nil, nil, nil, nil, nil, nil, nil, nil, "iej", nil, nil, nil, "iem", "iej")
		else
		add_normal_decl(base, vowel_alt, nil, nil, nil, nil, "éj", nil, nil, nil, nil, nil, nil, nil, nil, "éj", nil, nil, nil, "ém", "éj")
		end
		add_normal_decl(base, stem,
			"ý", "á", "é", "á", "éj", {}, "é", "á",
			"ého", "é", "ú", "ých",
			"ému", "éj", "ýma", "ým",
			"ú",
			"ém", "éj", "ú", "ých",
			"ým", "ú", "ýma", "ými"
		)
		return
		
		else
		add_normal_decl(base, com.apply_second_palatalization(stem, "is adj"), nil, nil, nil, nil, nil, "í")
		if not rfind(base.lemma, ".*lý$") then
		add_normal_decl(base, com.apply_second_palatalization(stem, "is adj"), nil, nil, nil, nil, "iej", nil, nil, nil, nil, nil, nil, nil, nil, "iej", nil, nil, nil, "iem", "iej")
		end
		add_normal_decl(base, stem,
			"ý", "á", "é", "á", "éj", {}, "é", "á",
			"ého", "é", "ú", "ých",
			"ému", "éj", "ýma", "ým",
			"ú",
			"ém", "éj", "ú", "ých",
			"ým", "ú", "ýma", "ými"
		)
		return
		end
	end
	if base.comp then
	stem, suffix = rmatch(base.lemma, "(.*)(í)$")
	if stem then
		add_normal_decl(base, stem,
			"í", "ši", "še", "še", "še", "še", "še", "še",
			"šě", "šě", nil, nil,
			"šu", "ši", nil, nil,
			"šu",
			"ši", "ši", nil, nil,
			"šem", "šú")
		return
	end
	
	else
	-- soft in -í
	stem, suffix = rmatch(base.lemma, "^(.*)(í)$")
	if stem then
		add_normal_decl(base, stem,
			"í", {}, {}, {}, "í", "í", {}, {},
			{}, {}, {}, "ích",
			{}, "í", "íma", "ím",
			{},
			{}, "í", {}, "ích",
			"ím", {}, "íma", "ími"
		)
		add_normal_decl(base, com.convert_paired_plain_to_palatal(stem, ending), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "ú", nil, nil, nil, nil, nil, "ú", nil, nil, "ú", nil, nil, "ú")
		if rfind(base.lemma, ".*lí$") then
		add_normal_decl(base, stem, nil, "é", "é", "é", "éj", nil, "é", "é", "ého", "é", nil, nil, "ému", "éj", nil, nil, nil, "ém", "éj")
		else
		add_normal_decl(base, stem, nil, "ie", "ie", "ie", "iej", nil, "ie", "ie", "ieho", "ie", nil, nil, "iemu", "iej", nil, nil, nil, "iem", "iej")
		end

		return
	end
	
	-- possessive in -óv
	stem, suffix = rmatch(base.lemma, "^(.*)(óv)$")
	if stem then
		add_normal_decl(base, stem,
			"óv", "ova", "ovo", "ova", "ově", "ovi", "ovy", "ova",
			"ova", "ovy", "ovú", "ových",
			"ovu", "ově", "ovýma", "ovým",
			"ovu",
			{"ově", "ovu"}, "ově", "ovú", "ových",
			"ovým", "ovú", "ovýma", "ovými"
		)
		return
	end

	-- possessive in -in
	stem, suffix = rmatch(base.lemma, "^(.*)(in)$")
	if stem then
		add_normal_decl(base, stem,
			"in", "ina", "ino", "ina", "ině", "ini", "iny", "ina",
			"ina", "iny", "inú", "iných",
			"inu", "ině", "inýma", "iným",
			"inu",
			{"ině", "inu"}, "ině", "inú", "iných",
			"iným", "inú", "inýma", "inými"
		)
		return
	end


	if base.num then
	stem, suffix = rmatch(base.lemma, "^(.*)" .. com.inherently_soft_c)
	if not stem then
		add_normal_decl(base, base.lemma, "")
		add_normal_decl(base, base.red == true and com.reduce(base.lemma) or base.lemma,
			nil, "a", "o", nil, nil, {}, "y", "a",
			"a", "y", nil, "ých",
			"u", {}, nil, "ým",
			"u",
			{}, {}, nil, "ých",
			"em", "ú", nil, "ými"
		)
		add_normal_decl(base, base.red == true and com.reduce( com.apply_second_palatalization(base.lemma, "is adj")) or com.apply_second_palatalization(base.lemma, "is adj"), nil, nil, nil, nil, nil, "i", nil, nil, nil, nil, nil, nil, nil, "ě", nil, nil, nil, "ě", "ě")
		return
	end
	
	stem, suffix = rmatch(base.lemma, "^(.*)(ój)$")
	if stem then
		add_normal_decl(base, stem,
			"ój", "ojě", {"oje", "é"}, "ojě", "oji", "oji", "ojě", "ojě",
			{"ojeho", "ého"}, "ojie", "ojú", {"ojich", "ých"},
			{"ojemu", "ému"}, "ojí", {"ojima", "ýma"}, {"ojim", "ým"},
			"oju",
			{"ojem", "ém"}, "ojí", "ojú", {"ojim", "ých"},
			{"ojím", "ým"}, "ojú", {"ojima", "ýma"}, {"ojimi", "ými"}
		)
		return
	end
	end 

	-- short soft
	stem, suffix = rmatch(base.lemma, "^(.*)" .. com.inherently_soft_c .. "$")
	if stem then
		add_normal_decl(base, base.lemma,
			"", {}, "e", {}, {}, {}, {}, {},
			{}, {}, nil, nil,
			"u", {}, nil, nil,
			"u",
			nil, nil, nil, nil,
			nil, nil, nil, nil
		)
		add_normal_decl(base, com.convert_paired_palatal_to_plain(base.lemma, ending), nil, "ě", nil, "ě", "i", "i", "ě", "ě", "ě", "ě", nil, nil, nil, "i", nil, nil, nil, nil, nil, nil, nil, nil, nil)
		return
	end
	
	-- short hard
	stem, suffix = rmatch(base.lemma, "^(.*)" .. com.cons_c)
	if stem then
		add_normal_decl(base, base.lemma, "")
		add_normal_decl(base, base.red == true and com.reduce(base.lemma) or base.lemma,
			nil, "a", "o", "a", {}, {}, "y", "a",
			"a", "y", nil, nil,
			"u", {}, nil, nil,
			"u",
			{}, {}, nil, nil,
			"em", "ú", nil, nil
		)
		add_normal_decl(base, base.red == true and com.reduce( com.apply_second_palatalization(base.lemma, "is adj")) or com.apply_second_palatalization(base.lemma, "is adj"), nil, nil, nil, nil, "ě", "i", nil, nil, nil, nil, nil, nil, nil, "ě", nil, nil, nil, "ě", "ě")
		return
	end
	end
		error("Unrecognized adjective lemma, should end in '-ý', '-í', '-ův' or '-in': '" .. base.lemma .. "'")
end

decls["irreg"] = function(base)
	local stem, suffix

	-- determiner like mój
	stem, suffix = rmatch(base.lemma, "^(.*)(ój)$")
	if stem then
		add_normal_decl(base, stem,
			"ój", {"á", "ojě"}, {"é", "oje"}, "á", "oji", "oji", {"é", "ojě"}, {"á", "ojě"},
			"ého", "é", {"ú", "ojú"}, "ých",
			"ému", {"ej", "éj"}, "ýma", "ým",
			{"ú", "oju"},
			"ém", {"ej", "éj"}, {"ú", "ojú"}, "ých",
			"ým", "ú", "ýma", "ými"
		)
		return
	end

	if base.lemma == "veš" then
		add_normal_decl(base, "",
			"veš", "všě", "vše", nil, nil, "vši", "všě", "všě",
			"všeho", "všie", nil, "všěch",
			"všemu", {"vší", "všiej"}, nil, "všěm",
			"všu",
			"všem", {"vší", "všiej"}, nil, "všěch",
			"všiem", "všú", nil, "všěmi"
		)
		return
	end

	if base.lemma == "sen" then
		add_normal_decl(base, "",
			"sen", "sie", "se", nil, nil, "si", nil, nil,
			"seho", "sie", nil, "sich",
			"semu", {"sí", "siej"}, "sima", "sim",
			"śú",
			"sem", {"sí", "siej"}, nil, "sich",
			"sím", "śú", "sima", "simi"
		)
		return
	end

	-- determiner like [[ten]], [[tento]], [[onen]], [[jeden]]
	stem, suffix = rmatch(base.lemma, "^(.*)(en)$")
	if stem then
		local nom_stem = stem .. suffix
		if nom_stem == "jeden" then
			stem = "jedn"
		end
		add_normal_decl(base, nom_stem, "")
		add_normal_decl(base, stem,
			nil, "a", "o", "a", "ě", "i", "y", "a",
			"oho", "é", "ú", "ěch",
			"omu", {"ej", "éj"}, "ěma", "ěm",
			"u",
			"om", {"ej", "éj"}, "ú", "ěch",
			"iem", "ú", "ěma", "ěmi"
		)
		return
	end

	-- [[náš]], [[váš]]
	stem, suffix = rmatch(base.lemma, "^(.*)(áš)$")
	if stem then
		local nom_stem = stem .. suffix
		stem = stem .. "aš"
		add_normal_decl(base, nom_stem,
			"", "ě", "e", "ě", "i", "i", "ě", "ě",
			"eho", "ie", "ú", "ich",
			"emu", {"í", "iej"}, "ima", "im",
			"u",
			"em", {"í", "iej"}, "ú", "ich",
			"ím", "ú", "ima", "imi"
)
		add_normal_decl(base, stem,
			nil, "ě", "e", "ě", "i", "i", "ě", "ě",
			"eho", "ie", "ú", "ich",
			"emu", {"í", "iej"}, "ima", "im",
			"u",
			"em", {"í", "iej"}, "ú", "ich",
			"ím", "ú", "ima", "imi"
		)
		return
	end
	

	if base.lemma == "jenž" then
		local preposition_footnote = "the leading letter ''j-'' is changed to ''n-'' when the pronoun is preceded by a preposition, e.g. {{m|cs|[[s]] [[nímž]]}}, {{m|cs|[[k]] [[němuž]]}}, {{m|cs|[[bez]] [[ňúž]]}}"
		preposition_footnote = "[" .. mw.getCurrentFrame():preprocess(preposition_footnote) .. "]"
		-- Add the non-prepositional forms.
		add_normal_decl(base, "",
			"jenž", "jěž", "jež", "jěž", "již", "již", "jěž", "jěž",
			"jehož", "jiež", "júž", "jichž",
			"jemuž", {"jíž", "jiejž"}, "jimaž", "jimž",
			"juž",
			nil, nil, nil, nil,
			"jímž", "júž", "jimaž", "jimiž"
		)
		-- Add the prepositional forms. (FIXME: Maybe should go in a separate column in a special table.)
		add_normal_decl(base, "",
			nil, nil, nil, nil, nil, nil, nil, nil,
			"ňehož", "niež", "ňúž", "nichž",
			"ňemuž", {"níž", "niejž"}, "nimaž", "nimž",
			"ňuž",
			"ňemž", {"níž", "niejž"}, "ňúž", "nichž",
			"nímž", "ňúž", "nimaž", "nimiž",
			preposition_footnote
		)
		add(base, "acc_m_an", "", {"jejž", "jenž", "již", "jehož"})
		add(base, "acc_m_an", "", {"ňejž", "ňenž", "niž", "něhož"}, preposition_footnote)
		return
	end

	if base.lemma == "sám" then
		-- This mixes long and short endings.
		add_normal_decl(base, "sám", "")
		add_normal_decl(base, "sam",
			nil, "a", "o", "a", "ě", "i", "y", "a",
			{"oho", "a"}, "é", "ú", "ěch",
			{"omu", "u"}, {"ej", "éj"}, "ěma", "ěm",
			"u",
			"om", {"ej", "éj"}, "ú", "ěch",
			"iem", "ú", "ěma", "ěmi"
		)
		return
	end

	error("Unrecognized irregular lemma '" .. base.lemma .. "'")
end


local function fetch_footnotes(separated_group)
	local footnotes
	for j = 2, #separated_group - 1, 2 do
		if separated_group[j + 1] ~= "" then
			error("Extraneous text after bracketed footnotes: '" .. table.concat(separated_group) .. "'")
		end
		if not footnotes then
			footnotes = {}
		end
		table.insert(footnotes, separated_group[j])
	end
	return footnotes
end


local function parse_indicator_spec(angle_bracket_spec)
	local inside = rmatch(angle_bracket_spec, "^<(.*)>$")
	assert(inside)
	local base = {forms = {}}
	if inside ~= "" then
		local parts = rsplit(inside, ".", true)
		for _, part in ipairs(parts) do
			if part == "irreg" then
				base.irreg = true
			elseif part == "*" then
				base.red = true
			elseif part == "comp" then
				base.comp = true
			elseif part == "num" then
				base.num = true
			else
				error("Unrecognized indicator '" .. part .. "': '" .. inside .. "'")
			end
		end
	end
	return base
end


local function normalize_all_lemmas(alternant_multiword_spec, pagename)
	iut.map_word_specs(alternant_multiword_spec, function(base)
		if base.lemma == "" then
			base.lemma = pagename
		end
		base.orig_lemma = base.lemma
		base.orig_lemma_no_links = m_links.remove_links(base.lemma)
		base.lemma = base.orig_lemma_no_links
	end)
end


local function detect_indicator_spec(base)
	if base.irreg then
		base.decl = "irreg"
	else
		base.decl = "normal"
	end
end


local function detect_all_indicator_specs(alternant_multiword_spec)
	iut.map_word_specs(alternant_multiword_spec, function(base)
		detect_indicator_spec(base)
	end)
end


local function decline_adjective(base)
	if not decls[base.decl] then
		error("Internal error: Unrecognized declension type '" .. base.decl .. "'")
	end
	decls[base.decl](base)
	-- handle_derived_slots_and_overrides(base)
end


-- Process override for the arguments in `args`, storing the results into `forms`. If `do_acc`, only do accusative
-- slots; otherwise, don't do accusative slots.
local function process_overrides(forms, args, do_acc)
	for slot, _ in pairs(input_adjective_slots) do
		if args[slot] and not not do_acc == not not slot:find("^acc") then
			forms[slot] = nil
			if args[slot] ~= "-" and args[slot] ~= "—" then
				local segments = iut.parse_balanced_segment_run(args[slot], "[", "]")
				local comma_separated_groups = iut.split_alternating_runs(segments, "%s*,%s*")
				for _, comma_separated_group in ipairs(comma_separated_groups) do
					local formobj = {
						form = comma_separated_group[1],
						footnotes = fetch_footnotes(comma_separated_group),
					}
					iut.insert_form(forms, slot, formobj)
				end
			end
		end
	end
end


local function check_allowed_overrides(alternant_multiword_spec, args)
	local special = alternant_multiword_spec.special or alternant_multiword_spec.surname and "surname" or ""
	for slot, types in pairs(input_adjective_slots) do
		if args[slot] then
			local allowed = false
			for _, typ in ipairs(types) do
				if typ == special then
					allowed = true
					break
				end
			end
			if not allowed then
				error(("Override %s= not allowed for %s"):format(slot, special == "" and "regular declension" or
					"special=" .. special))
			end
		end
	end
end


local function set_accusative(alternant_multiword_spec)
	local forms = alternant_multiword_spec.forms
	local function copy_if(from_slot, to_slot)
		if not forms[to_slot] then
			iut.insert_forms(forms, to_slot, forms[from_slot])
		end
	end

	copy_if("nom_n", "acc_n")
	copy_if("gen_mn", "acc_m_an")
	copy_if("nom_m", "acc_m_in")
	copy_if("nom_fp", "acc_mfp")
	copy_if("nom_np", "acc_np")
	copy_if("nom_md", "acc_md")
	copy_if("nom_fnd", "acc_fnd")
end


local function add_categories(alternant_multiword_spec)
	local cats = {}
	local plpos = m_string_utilities.pluralize(alternant_multiword_spec.pos or "adjective")
	local function insert(cattype)
		m_table.insertIfNot(cats, "Old Czech " .. cattype .. " " .. plpos)
	end
	if not alternant_multiword_spec.manual then
		iut.map_word_specs(alternant_multiword_spec, function(base)
			if base.decl == "irreg" then
				insert("irregular")
			elseif rfind(base.lemma, "ý$") then
				insert("hard")
			elseif rfind(base.lemma, "í$") then
				insert("soft")
			elseif base.num then
				insert("number")
			elseif rfind(base.lemma, "óv$") then
				insert("possessive")
			elseif rfind(base.lemma, "in$") then
				insert("possessive")
			elseif rfind(base.lemma, "^(.*)" .. com.inherently_soft_c .. "$") then
				insert("short soft")
			elseif rfind(base.lemma, "^(.*)" .. com.cons_c .. "$") then
				insert("short hard")
			end
		end)
	end
	alternant_multiword_spec.categories = cats
end


local function show_forms(alternant_multiword_spec)
	local lemmas = {}
	local lemmaform = alternant_multiword_spec.forms.nom_m or alternant_multiword_spec.forms.nom_mp
	if lemmaform then
		for _, form in ipairs(lemmaform) do
			table.insert(lemmas, form.form)
		end
	end
	local props = {
		lemmas = lemmas,
		slot_table = get_output_adjective_slots(alternant_multiword_spec),
		lang = lang,
	}
	iut.show_forms(alternant_multiword_spec.forms, props)
end


local function make_table(alternant_multiword_spec)
	local forms = alternant_multiword_spec.forms

	local function template_prelude(min_width)
		return rsub([===[
<div>
<div class="NavFrame" style="max-width: MINWIDTHem">
<div class="NavHead" style="background:#eff7ff">{title}{annotation}</div>
<div class="NavContent" style="overflow:auto">
{\op}| border="1px solid #000000" style="border-collapse:collapse;background:#F9F9F9;text-align:center; min-width:MINWIDTHem" class="inflection-table"
|-
]===], "MINWIDTH", min_width)
	end

	local function template_postlude()
		return [=[
|{\cl}{notes_clause}</div></div></div>]=]
	end

	local table_spec_sg = [=[
! style="background:#d9ebff" colspan=5 | singular
|-
! style="background:#d9ebff" |
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | neuter
|-
! style="background:#eff7ff" | nominative
| {nom_m}
| {nom_f}
| {nom_n}
|-
! style="background:#eff7ff" | genitive
| {gen_mn}
| {gen_f}
| {gen_mn}
|-
! style="background:#eff7ff" | dative
| {dat_mn}
| {dat_f}
| {dat_mn}
|-
! style="background:#eff7ff" | accusative
| {acc_m_an}, {acc_m_in}
| {acc_f}
| {acc_n}
|-
! style="background:#eff7ff" | locative
| {loc_mn}
| {loc_f}
| {loc_mn}
|-
! style="background:#eff7ff" | instrumental
| {ins_mn}
| {ins_f}
| {ins_mn}
]=]

	local table_spec_du = [=[
! style="background:#d9ebff" colspan=5 | dual
|-
! style="background:#d9ebff" | 
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | neuter
|-
! style="background:#eff7ff" | nominative
| {nom_md}
| colspan=2 | {nom_fnd}
|-
! style="background:#eff7ff" | genitive
| colspan=3 | {gen_d}
|-
! style="background:#eff7ff" | dative
| colspan=3 | {dat_d}
|-
! style="background:#eff7ff" | accusative
| {acc_md}
| colspan=2 | {acc_fnd}
|-
! style="background:#eff7ff" | locative
| colspan=3 | {loc_d}
|-
! style="background:#eff7ff" | instrumental
| colspan=3 | {ins_d}
]=]

	local table_spec_pl = [=[
! style="background:#d9ebff" colspan=5 | plural
|-
! style="background:#d9ebff" | 
! style="background:#d9ebff" | masculine
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | neuter
|-
! style="background:#eff7ff" | nominative
| {nom_mp}
| {nom_fp}
| {nom_np}
|-
! style="background:#eff7ff" | genitive
| colspan=3 | {gen_p}
|-
! style="background:#eff7ff" | dative
| colspan=3 | {dat_p}
|-
! style="background:#eff7ff" | accusative
| colspan=2 | {acc_mfp}
| {acc_np}
|-
! style="background:#eff7ff" | locative
| colspan=3 | {loc_p}
|-
! style="background:#eff7ff" | instrumental
| colspan=3 | {ins_p}
]=]

	local table_spec = template_prelude("55") .. table_spec_sg .. "|-\n" .. table_spec_du .. "|-\n" .. table_spec_pl .. template_postlude()

	local table_spec_plonly = template_prelude("55") .. table_spec_pl .. template_postlude()
	
	local table_spec_nodual = template_prelude("55") .. table_spec_sg  .. "|-\n" .. table_spec_pl .. template_postlude()

	local table_spec_duonly = template_prelude("40") .. [=[
! style="width:40%;background:#eff7ff" colspan="2" | 
! style="background:#eff7ff" colspan="2" | dual
|-
! style="width:40%;background:#eff7ff" colspan="2" | 
! style="background:#eff7ff" | masculine
! style="background:#eff7ff" | feminine/neuter
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_md}
| {nom_fnd}
|-
! style="background:#eff7ff" colspan="2" | genitive
| colspan="2" | {gen_d} 
|-
! style="background:#eff7ff" colspan="2" | dative
| colspan="2" | {dat_d} 
|-
! style="background:#eff7ff" colspan="2" | accusative
| {acc_md}
| {acc_fnd}
|-
! style="background:#eff7ff" colspan="2" | locative
| colspan="2" | {loc_d} 
|-
! style="background:#eff7ff" colspan="2" | instrumental
| colspan="2" | {ins_d} 
]=] .. template_postlude()

	local notes_template = [===[
<div style="width:100%;text-align:left;background:#eff7ff">
<div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em">
{footnote}
</div></div>
]===]

	if alternant_multiword_spec.title then
		forms.title = alternant_multiword_spec.title
	else
		forms.title = 'Declension of <i lang="zlw-ocs">' .. forms.lemma .. '</i>'
	end

	if alternant_multiword_spec.manual then
		forms.annotation = ""
	else
		local ann_parts = {}
		local decls = {}
		iut.map_word_specs(alternant_multiword_spec, function(base)	
			if base.decl == "irreg" then
				m_table.insertIfNot(decls,"irregular")
			elseif rfind(base.lemma, "ý$") then
				m_table.insertIfNot(decls, "hard")
			elseif base.comp then
				m_table.insertIfNot(decls, "short comparative")
			elseif rfind(base.lemma, "í$") then
				m_table.insertIfNot(decls, "soft")
			elseif base.num then
				m_table.insertIfNot(decls, "number")
			elseif rfind(base.lemma, "óv$") then
				m_table.insertIfNot(decls, "possessive")
			elseif rfind(base.lemma, "in$") then
				m_table.insertIfNot(decls, "possessive")
			elseif rfind(base.lemma, "^(.*)" .. com.inherently_soft_c .. "$") then
				m_table.insertIfNot(decls, "short soft")
			elseif rfind(base.lemma, "^(.*)" .. com.cons_c .. "$") then
				m_table.insertIfNot(decls, "short hard")
			end
		end)
		table.insert(ann_parts, table.concat(decls, " // "))
		forms.annotation = " (" .. table.concat(ann_parts, ", ") .. ")"
	end

	forms.notes_clause = forms.footnote ~= "" and
		m_string_utilities.format(notes_template, forms) or ""
	return m_string_utilities.format(
		alternant_multiword_spec.special == "plonly" and table_spec_plonly or
		alternant_multiword_spec.special == "duonly" and table_spec_duonly or
		alternant_multiword_spec.special == "nodual" and table_spec_nodual or
		table_spec, forms
	)
end

-- Externally callable function to parse and decline an adjective given
-- user-specified arguments. Return value is WORD_SPEC, an object where the
-- declined forms are in `WORD_SPEC.forms` for each slot. If there are no values
-- for a slot, the slot key will be missing. The value for a given slot is a
-- list of objects {form=FORM, footnotes=FOOTNOTES}.
function export.do_generate_forms(parent_args, pos, from_headword, def)
	local params = {
		[1] = {},
		vowel_alt = {},
		pos = {},
		json = {type = "boolean"}, -- for use with bots
		title = {},
		pagename = {},
	}
	for slot, _ in pairs(input_adjective_slots) do
		params[slot] = {}
	end

	-- Only default param 1 when displaying the template.
	local args = require("Module:parameters").process(parent_args, params)
	local SUBPAGE = mw.title.getCurrentTitle().subpageText
	local pagename = args.pagename or SUBPAGE
	vowel_alt = args.vowel_alt
	if not args[1] then
		if SUBPAGE == "zlw-ocs-adecl" then
			args[1] = "měkký"
		else
			args[1] = pagename
		end
	end		
	local parse_props = {
		parse_indicator_spec = parse_indicator_spec,
		allow_default_indicator = true,
		allow_blank_lemma = true,
	}
	local alternant_multiword_spec = iut.parse_inflected_text(args[1], parse_props)
	alternant_multiword_spec.pos = args.pos
	alternant_multiword_spec.title = args.title
	alternant_multiword_spec.forms = {}
	normalize_all_lemmas(alternant_multiword_spec, pagename)
	detect_all_indicator_specs(alternant_multiword_spec)
	check_allowed_overrides(alternant_multiword_spec, args)
	local inflect_props = {
		slot_table = get_output_adjective_slots(alternant_multiword_spec),
		inflect_word_spec = decline_adjective,
	}
	iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
	-- Do non-accusative overrides so they get copied to the accusative forms appropriately.
	process_overrides(alternant_multiword_spec.forms, args)
	set_accusative(alternant_multiword_spec)
	-- Do accusative overrides after copying the accusative forms.
	process_overrides(alternant_multiword_spec.forms, args, "do acc")
	add_categories(alternant_multiword_spec)
	if args.json and not from_headword then
		return require("Module:JSON").toJSON(alternant_multiword_spec)
	end
	return alternant_multiword_spec
end


-- Externally callable function to parse and decline an adjective where all
-- forms are given manually. Return value is WORD_SPEC, an object where the
-- declined forms are in `WORD_SPEC.forms` for each slot. If there are no values
-- for a slot, the slot key will be missing. The value for a given slot is a
-- list of objects {form=FORM, footnotes=FOOTNOTES}.
function export.do_generate_forms_manual(parent_args, pos, from_headword, def)
	local params = {
		pos = {},
		special = {},
		json = {type = "boolean"}, -- for use with bots
		title = {},
	}
	for slot, _ in pairs(input_adjective_slots) do
		params[slot] = {}
	end

	local args = require("Module:parameters").process(parent_args, params)
	local alternant_multiword_spec = {
		pos = args.pos,
		special = args.special,
		title = args.title, 
		forms = {},
		manual = true,
	}
	check_allowed_overrides(alternant_multiword_spec, args)
	-- Do non-accusative overrides so they get copied to the accusative forms appropriately.
	process_overrides(alternant_multiword_spec.forms, args)
	set_accusative(alternant_multiword_spec)
	-- Do accusative overrides after copying the accusative forms.
	process_overrides(alternant_multiword_spec.forms, args, "do acc")
	add_categories(alternant_multiword_spec)
	if args.json and not from_headword then
		return require("Module:JSON").toJSON(alternant_multiword_spec)
	end
	return alternant_multiword_spec
end


-- Entry point for {{zlw-ocs-adecl}}. Template-callable function to parse and decline 
-- an adjective given user-specified arguments and generate a displayable table
-- of the declined forms.
function export.show(frame)
	local parent_args = frame:getParent().args
	local alternant_multiword_spec = export.do_generate_forms(parent_args)
	show_forms(alternant_multiword_spec)
	return make_table(alternant_multiword_spec) .. require("Module:utilities").format_categories(alternant_multiword_spec.categories, lang)
end


-- Entry point for {{zlw-ocs-adecl-manual}}. Template-callable function to parse and
-- decline an adjective given manually-specified inflections and generate a
-- displayable table of the declined forms.
function export.show_manual(frame)
	local parent_args = frame:getParent().args
	local alternant_multiword_spec = export.do_generate_forms_manual(parent_args)
	show_forms(alternant_multiword_spec)
	return make_table(alternant_multiword_spec) .. require("Module:utilities").format_categories(alternant_multiword_spec.categories, lang)
end


return export