Module:pl-adjective

From Wiktionary, the free dictionary
Jump to navigation Jump to search

This module implements {{pl-adecl}}.


local export = {}


--[=[

Authorship: Ben Wing <benwing2>

]=]

--[=[

TERMINOLOGY:

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

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

-- "lemma" = The dictionary form of a given Polish 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("pl")
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:pl-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" = regular, "plonly" = indicator 'plonly', "dwa" = [[dwa]] and [[oba]].
local input_adjective_slots = {
	nom_m = {"regular"},
	nom_f = {"regular"},
	nom_n = {"regular"},
	nom_mp_pers = {"regular", "dwa", "plonly"},
	nom_p_not_mp_pers = {"regular", "plonly"},
	nom_mp_npers = {"dwa"},
	nom_fp = {"dwa"},
	nom_np = {"dwa"},
	gen_mn = {"regular"},
	gen_f = {"regular"},
	gen_p = {"regular", "plonly", "dwa"},
	dat_mn = {"regular"},
	dat_f = {"regular"},
	dat_p = {"regular", "plonly", "dwa"},
	acc_m_an = {"regular"},
	acc_m_in = {"regular"},
	acc_f = {"regular"},
	acc_n = {"regular"},
	acc_mp_pers = {"regular", "dwa", "plonly"},
	acc_p_not_mp_pers = {"regular", "plonly"},
	acc_mp_npers = {"dwa"},
	acc_fp = {"dwa"},
	acc_np = {"dwa"},
	ins_mn = {"regular"},
	ins_f = {"regular"},
	ins_p = {"regular", "plonly"},
	ins_mnp = {"dwa"},
	ins_fp = {"dwa"},
	loc_mn = {"regular"},
	loc_f = {"regular"},
	loc_p = {"regular", "plonly", "dwa"},
	short = {"regular"},
}


local output_adjective_slots = {
	nom_m = "nom|m|s",
	nom_m_linked = "nom|m|s", -- used in [[Module:pl-noun]]?
	nom_f = "nom|f|s",
	nom_n = "nom|n|s",
	nom_mp_pers = "pr|nom|m|p",
	nom_mp_pers_linked = "pr|nom|m|p", -- used in [[Module:pl-noun]]?
	nom_p_not_mp_pers = "np|nom|p",
	nom_mp_npers = "np|nom|m|p",
	nom_mp_npers_linked = "pr|nom|m|p", -- used in [[Module:pl-noun]]?
	nom_fp = "nom|f|p",
	nom_np = "nom|n|p",
	gen_mn = "gen|m//n|s",
	gen_f = "gen|f|s",
	gen_p = "gen|p",
	dat_mn = "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",
	acc_mp_pers = "pr|acc|m|p",
	acc_p_not_mp_pers = "np|acc|p",
	acc_mp_npers = "np|acc|m|p",
	acc_fp = "acc|f|p",
	acc_np = "acc|n|p",
	ins_mn = "ins|m//n|s",
	ins_f = "ins|f|s",
	ins_p = "ins|p",
	ins_mnp = "ins|m//n|p",
	ins_fp = "ins|f|p",
	loc_mn = "loc|m//n|s",
	loc_f = "loc|f|s",
	loc_p = "loc|p",
	short = "short",
}


local potential_lemma_slots = {"nom_m", "nom_mp_npers", "nom_mp_pers"}


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 "?"
	elseif stem:find("j$") and ending:find("^i") then
		return stem:gsub("j$", "") .. ending
	else
		return stem .. ending
	end
end


-- Basic function to combine stem and ending(s) and insert the result into the appropriate slot. `stem` is either a
-- string or a form object (containing fields `form` and `footnotes`). `endings` is either a string specifying a single
-- ending or a list of endings. If `endings` is nil, no forms are inserted. If an ending is "-", the value of `stem` is
-- ignored and the lemma is used instead as the stem; this is important in case the user used `decllemma:` to specify a
-- declension lemma different from the actual lemma.
local function add(base, slot, stem, endings, footnotes)
	if not endings then
		return
	end
	local stem_footnotes = type(stem) == "table" and stem.footnotes or nil
	footnotes = iut.combine_footnotes(iut.combine_footnotes(base.footnotes, stem_footnotes), footnotes)
	if type(endings) == "string" then
		endings = {endings}
	end
	for _, ending in ipairs(endings) do
		local this_stem
		-- Compute the stem. If ending is "-", use the lemma regardless.
		if ending == "-" then
			this_stem = base.actual_lemma
			ending = ""
		elseif type(stem) == "string" then
			this_stem = stem
		else
			this_stem = stem.form
		end
		ending = iut.combine_form_and_footnotes(ending, footnotes)
		iut.add_forms(base.forms, slot, this_stem, ending, combine_stem_ending)
	end
end


-- Following the old [[Module:pl-adj]], a "normal" declension has syncretism in the gen/dat/loc fem sg, in the acc/ins
-- fem sg, in the ins/loc masc/neut sg, and in the gen/loc pl.
local function add_normal_decl(base, stem,
	nom_f, nom_n, nom_mp_pers, nom_p_not_mp_pers,
	gen_mn, gen_f, gen_p,
	dat_mn, dat_p,
	acc_f,
	ins_mn, ins_p,
	arch_dat_mn,
	footnote)
	if stem then
		stem = iut.combine_form_and_footnotes(stem, footnote)
	end
	add(base, "nom_m", stem, "-")
	add(base, "nom_f", stem, nom_f)
	add(base, "nom_n", stem, nom_n)
	add(base, "nom_mp_pers", stem, nom_mp_pers)
	add(base, "nom_p_not_mp_pers", stem, nom_p_not_mp_pers)
	add(base, "gen_mn", stem, gen_mn)
	add(base, "gen_f", stem, gen_f)
	add(base, "gen_p", stem, gen_p)
	add(base, "dat_mn", stem, dat_mn)
	if arch_dat_mn and base.archdat then
		add(base, "dat_mn", stem, arch_dat_mn, "[archaic]")
	end
	add(base, "dat_f", stem, gen_f)
	add(base, "dat_p", stem, dat_p)
	add(base, "acc_f", stem, acc_f)
	add(base, "ins_mn", stem, ins_mn)
	add(base, "ins_f", stem, acc_f)
	add(base, "ins_p", stem, ins_p)
	add(base, "loc_mn", stem, ins_mn)
	add(base, "loc_f", stem, gen_f)
	add(base, "loc_p", stem, gen_p)
end

local decls = {}

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

	-- soft in -l or -j ([[żmii]]; combine_stem_ending() will drop -j before i-)
	stem, suffix = rmatch(base.lemma, "^(.*[lj])(i)$")
	if stem then
		add_normal_decl(base, stem,
			"a", "e", "i", "e",
			"ego", "ej", "ich",
			"emu", "im",
			"ą",
			"im", "imi"
		)
		return
	end

	-- soft in -c
	stem, suffix = rmatch(base.lemma, "^(.*c)(i)$")
	if stem then
		add_normal_decl(base, stem,
			"ia", "ie", "i", "ie",
			"iego", "iej", "ich",
			"iemu", "im",
			"ią",
			"im", "imi"
		)
		return
	end

	-- velar
	stem, suffix = rmatch(base.lemma, "^(.*[kg])(i)$")
	if stem then
		add_normal_decl(base, stem,
			"a", "ie", nil, "ie",
			"iego", "iej", "ich",
			"iemu", "im",
			"ą",
			"im", "imi",
			"u"
		)
		-- Do the nominative masculine personal plural separately since it may have a different stem (with the second
		-- palatalization and Lechitic softening applied).
		add_normal_decl(base, com.soften_adj_masc_pers_pl(base.lemma), nil, nil, "")
		return
	end

	-- other soft in -i
	stem, suffix = rmatch(base.lemma, "^(.*)(i)$")
	if stem then
		add_normal_decl(base, stem,
			"ia", "ie", "i", "ie",
			"iego", "iej", "ich",
			"iemu", "im",
			"ią",
			"im", "imi"
		)
		return
	end

	-- hard in -y
	stem, suffix = rmatch(base.lemma, "^(.*)(y)$")
	if stem then
		add_normal_decl(base, stem,
			"a", "e", nil, "e",
			"ego", "ej", "ych",
			"emu", "ym",
			"ą",
			"ym", "ymi",
			"u"
		)
		-- Do the nominative masculine personal plural separately since it may have a different stem (with the second
		-- palatalization and Lechitic softening applied).
		if stem:find("zielon$") then
			add_normal_decl(base, stem, nil, nil, "i")
			add_normal_decl(base, rsub(stem, "on$", "en"), nil, nil, "i")
		elseif stem:find("czerwon$") or stem:find("słon$") then
			add_normal_decl(base, stem, nil, nil, "i")
		elseif stem:find("on$") then
			add_normal_decl(base, rsub(stem, "on$", "en"), nil, nil, "i")
		else
			add_normal_decl(base, com.soften_adj_masc_pers_pl(base.lemma), nil, nil, "")
		end
		return
	end

	error("Unrecognized adjective lemma, should end in '-y' or '-i': '" .. base.lemma .. "'")
end


decls["irreg"] = function(base)
	local lemma = base.lemma

	if lemma == "dwa" or lemma == "oba" then
		for _, slot_value in ipairs {
			{"nom_mp_pers", "aj"},
			{"nom_mp_npers", "a"},
			{"nom_fp", "ie"},
			{"nom_np", "a"},
			{"gen_p", {"u", "óch"}},
			{"dat_p", {"u", "om", "óm"}},
			{"ins_mnp", {"oma", "u"}},
			{"ins_fp", {"iema", "oma", "u"}},
			{"loc_p", {"u", "óch"}},
		} do
			local slot, value = unpack(slot_value)
			add(base, slot, lemma == "dwa" and "dw" or "ob", value)
		end
		return
	end

	local stem = lemma:match("^(.*[st]w)ój$")
	if not stem then
		stem = lemma:match("^(.*m)ój$")
	end
	if stem then
		add_normal_decl(base, stem .. "oj",
			"a", "e", "i", "e", -- combine_stem_ending() will drop -j before i-
			"ego", "ej", "ich",
			"emu", "im",
			"ą",
			"im", "imi"
		)
		add_normal_decl(base, stem,
			"a", "e", nil, "e",
			"ego", "ej", "ych",
			"emu", "ym",
			"ą",
			"ym", "ymi",
			nil,
			"[literary or archaic]"
		)
		return
	end

	stem = lemma:match("^(.*)ten$")
	if stem then
		add_normal_decl(base, stem,
			"ta", "to", "ci", "te",
			"tego", "tej", "tych",
			"temu", "tym",
			nil,
			"tym", "tymi"
		)
		add(base, "acc_f", stem, "tę")
		add(base, "acc_f", stem, "tą", "[proscribed but much more common]")
		add(base, "ins_f", stem, "tą")
		return
	end

	stem = lemma:match("^(.*czyj)$")
	if stem then
		add_normal_decl(base, stem,
			"a", "e", "i", "e",
			"ego", "ej", "ich",
			"emu", "im",
			"ą",
			"im", "imi"
		)
		return
	end

	stem = lemma:match("^(.*sam)$")
	if not stem then
		stem = lemma:match("^(.*jed)en$")
		if stem then
			stem = stem .. "n"
		end
	end
	if not stem then
		stem = lemma:match("^(on)$")
	end
	if not stem then
		stem = lemma:match("^(ów)$")
		if stem then
			stem = stem:gsub("ów$", "ow")
		end
	end
	if not stem then
		stem = lemma:match("^(ow)y$")
	end
	if stem then
		add_normal_decl(base, stem,
			"a", "o", "i", "e",
			"ego", "ej", "ych",
			"emu", "ym",
			"ą",
			"ym", "ymi"
		)
		return
	end

	stem = lemma:match("^(.*il)e$")
	if stem then
		add_normal_decl(base, stem,
			nil, nil, "u", "e",
			nil, nil, "u",
			nil, "u",
			nil,
			nil, "u"
		)
		add(base, "ins_p", stem, "oma", "[rare]")
		return
	end

	-- [[wszystek]]
	stem, suffix = rmatch(base.lemma, "^(.*wszys)tek$")
	if stem then
		add_normal_decl(base, stem .. "tk",
			"a", "o", nil, "ie",
			"iego", "iej", "ich",
			"iemu", "im",
			"ą",
			"im", "imi",
			"u"
		)
		add_normal_decl(base, stem .. "c", nil, nil, "y")
		return
	end

	error(("Unrecognized irregular lemma '%s'"):format(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


--[=[
Parse a single override spec (e.g. 'ins_mn:autodráhou:autodrahou[rare]' or 'gen_p+loc_p:čobotyx') and return two values:
the slot(s) the override applies to, and an object describing the override spec. The input is actually a list where the
footnotes have been separated out; for example, given the spec 'ins_p:čobotami:čobotámi[rare]:čobitmi[archaic]', the
input will be a list {"ins_p:čobotami:čobotámi", "[rare]", ":čobitmi", "[archaic]", ""}. The object returned for
'ins_mn:autodráhou:autodrahou[rare]' looks like this:

{
  values = {
	{
	  form = "autodráhou"
	},
	{
	  form = "autodrahou",
	  footnotes = {"[rare]"}
	}
  }
}
]=]
local function parse_override(segments)
	local retval = {values = {}}
	local slots = {}
	local colon_separated_groups = iut.split_alternating_runs_and_strip_spaces(segments, ":")
	for i, colon_separated_group in ipairs(colon_separated_groups) do
		if i == 1 then
			if #colon_separated_group > 1 then
				error(("Footnotes not allowed after slot name: '%s'"):format(table.concat(segments)))
			end
			local slotspec = colon_separated_group[1]
			for _, slot in ipairs(rsplit(slotspec, "%+")) do
				if not input_adjective_slots[slot] then
					error(("Unrecognized slot '%s': '%s'"):format(slot, table.concat(segments)))
				end
				table.insert(slots, slot)
			end
		else
			local value = {}
			local form = colon_separated_group[1]
			if form == "" then
				error(("Use - to indicate an empty ending for slot%s '%s': '%s'"):format(#slots > 1 and "s" or "", table.concat(slots), table.concat(segments)))
			elseif form == "-" then
				value.form = ""
			else
				value.form = form
			end
			value.footnotes = fetch_footnotes(colon_separated_group)
			table.insert(retval.values, value)
		end
	end
	return slots, retval
end


local function parse_indicator_spec(angle_bracket_spec)
	local inside = rmatch(angle_bracket_spec, "^<(.*)>$")
	assert(inside)
	local base = {overrides = {}, forms = {}}
	if inside ~= "" then
		local segments = iut.parse_balanced_segment_run(inside, "[", "]")
		local dot_separated_groups = iut.split_alternating_runs_and_strip_spaces(segments, "%.")
		for i, dot_separated_group in ipairs(dot_separated_groups) do
			local part = dot_separated_group[1]
			if rfind(part, "^decllemma:") then
				if base.decllemma then
					error("Can't specify 'decllemma:' twice: '" .. inside .. "'")
				end
				base.decllemma = rsub(part, "^decllemma:", "")
			elseif rfind(part, ":") then
				local slots, override = parse_override(dot_separated_group)
				for _, slot in ipairs(slots) do
					if base.overrides[slot] then
						error(("Two overrides specified for slot '%s'"):format(slot))
					else
						base.overrides[slot] = {override}
					end
				end
			elseif part == "irreg" or part == "plonly" or part == "archdat" then
				if #dot_separated_group > 1 then
					error("Footnotes only allowed with slot overrides or by themselves: '" ..
						table.concat(dot_separated_group) .. "'")
				end
				if base[part] then
					error("Can't specify '" .. part .. "' twice: '" .. inside .. "'")
				end
				base[part] = true
			elseif part == "" then
				if #dot_separated_group == 1 then
					error("Blank indicator: '" .. inside .. "'")
				end
				base.footnotes = fetch_footnotes(dot_separated_group)
			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.actual_lemma = base.lemma
		base.lemma = base.decllemma or base.orig_lemma_no_links
	end)
end


local function detect_indicator_spec(base)
	if base.irreg then
		base.decl = "irreg"
		if base.lemma == "dwa" or base.lemma == "oba" then
			base.dwa = true
		elseif base.lemma:find("ile$") then
			base.plonly = true
		end
	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)
		local special = base.plonly and "plonly" or base.dwa and "dwa" or "regular"
		if alternant_multiword_spec.special == nil then
			alternant_multiword_spec.special = special
		elseif alternant_multiword_spec.special ~= special then
			-- We do this because we have a special table with its own slots for each of these special variants.
			if special == "dwa" then
				error("If some alternants are irregular [[dwa]] or [[oba]], all must be")
			elseif special == "plonly" then
				error("If some alternants use indicator 'plonly', all must")
			else
				error("Can't mix regular declension with 'plonly' indicator or with irregular [[dwa]] or [[oba]]")
			end
		end
	end)
end


local function process_slot_overrides(base, do_slot)
	for slot, overrides in pairs(base.overrides) do
		local special = base.plonly and "plonly" or base.dwa and "dwa" or "regular"
		local allowed_specials = input_adjective_slots[slot]
		if not allowed_specials then
			error(("Internal error: Encountered unrecognized slot '%s' not caught during parse_indicator_spec()"):format(
				slot))
		end
		if not m_table.contains(allowed_specials, special) then
			error(("Override specified for slot '%s' not compatible with table type '%s'"):format(slot, special))
		end
		if do_slot(slot) then
			base.slot_overridden[slot] = true
			base.forms[slot] = nil
			for _, override in ipairs(overrides) do
				for _, value in ipairs(override.values) do
					local form = value.form
					local combined_notes = iut.combine_footnotes(base.footnotes, value.footnotes)
					iut.insert_form(base.forms, slot, {form = form, footnotes = combined_notes})
				end
			end
		end
	end
end


local function handle_derived_slots_and_overrides(base)
	local function is_derived_slot(slot)
		return slot:find("^acc")
	end

	local function is_non_derived_slot(slot)
		return not is_derived_slot(slot)
	end

	base.slot_overridden = {}
	-- Handle overrides for the non-derived slots. Do this before generating the derived
	-- slots so overrides of the source slots (e.g. nom_p) propagate to the derived slots.
	process_slot_overrides(base, is_non_derived_slot)

	local function copy_if(from_slot, to_slot)
		if not base.forms[to_slot] then
			iut.insert_forms(base.forms, to_slot, base.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("gen_p", "acc_mp_pers")
	copy_if("nom_p_not_mp_pers", "acc_p_not_mp_pers")
	copy_if("nom_mp_npers", "acc_mp_npers")
	copy_if("nom_fp", "acc_fp")
	copy_if("nom_np", "acc_np")

	-- Handle overrides for derived slots, to allow them to be overridden.
	process_slot_overrides(base, is_derived_slot)

	-- Compute linked versions of potential lemma slots, for use in {{cs-noun}}.
	-- We substitute the original lemma (before removing links) for forms that
	-- are the same as the lemma, if the original lemma has links.
	for _, slot in ipairs(potential_lemma_slots) do
		iut.insert_forms(base.forms, slot .. "_linked", iut.map_forms(base.forms[slot], function(form)
			if form == base.orig_lemma_no_links and rfind(base.orig_lemma, "%[%[") then
				return base.orig_lemma
			else
				return form
			end
		end))
	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


local function get_decl_from_lemma(base)
	if base.decl == "irreg" then
		return "irregular"
	elseif rfind(base.lemma, "y$") then
		return "hard"
	elseif rfind(base.lemma, "[kg]i$") then
		return "velar"
	else
		return "soft"
	end
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, "Polish " .. cattype .. " " .. plpos)
	end
	iut.map_word_specs(alternant_multiword_spec, function(base)
		insert(get_decl_from_lemma(base))
		if base.overrides.short then
			table.insert(cats, "Polish " .. plpos .. " with short forms")
		end
		if base.decllemma then
			table.insert(cats, "Polish " .. plpos .. " with irregular stem")
		end
	end)
	alternant_multiword_spec.categories = cats
end


local function show_forms(alternant_multiword_spec)
	local lemmas = {}
	for _, slot in ipairs(potential_lemma_slots) do
		if alternant_multiword_spec.forms[slot] then
			for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do
				-- FIXME, now can support footnotes as qualifiers in headwords?
				table.insert(lemmas, formobj.form)
			end
			break
		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; width:100%" class="inflection-table"
|-
]===], "MINWIDTH", min_width)
	end

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

	local table_spec_both = template_prelude("75") .. [=[
! style="background:#d9ebff" colspan=5 | singular
! style="background:#d9ebff" colspan=2 | plural
|-
! style="background:#d9ebff" |
! style="background:#d9ebff" | masculine animate
! style="background:#d9ebff" | masculine inanimate
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | neuter
! style="background:#d9ebff" | virile (= masculine personal)
! style="background:#d9ebff" | non-virile
|-
! style="background:#eff7ff" | nominative
| colspan=2 | {nom_m}
| {nom_f}
| {nom_n}
| {nom_mp_pers}
| {nom_p_not_mp_pers}
|-
! style="background:#eff7ff" | genitive
| colspan=2 | {gen_mn}
| {gen_f}
| {gen_mn}
| colspan=2 | {gen_p}
|-
! style="background:#eff7ff" | dative
| colspan=2 | {dat_mn}
| {dat_f}
| {dat_mn}
| colspan=2 | {dat_p}
|-
! style="background:#eff7ff" | accusative
| {acc_m_an}
| {acc_m_in}
| {acc_f}
| {acc_n}
| {acc_mp_pers}
| {acc_p_not_mp_pers}
|-
! style="background:#eff7ff" | instrumental
| colspan=2 | {ins_mn}
| {ins_f}
| {ins_mn}
| colspan=2 | {ins_p}
|-
! style="background:#eff7ff" | locative
| colspan=2 | {loc_mn}
| {loc_f}
| {loc_mn}
| colspan=2 | {loc_p}{short_clause}
]=] .. template_postlude()

	local table_spec_plonly = template_prelude("40") .. [=[
! style="background:#d9ebff" colspan=5 | plural
|-
! style="background:#d9ebff" | 
! style="background:#d9ebff" | virile (= masculine personal)
! style="background:#d9ebff" colspan=3 | non-virile
|-
! style="background:#eff7ff" | nominative
| {nom_mp_pers}
| colspan=3 | {nom_p_not_mp_pers}
|-
! style="background:#eff7ff" | genitive
| colspan=4 | {gen_p}
|-
! style="background:#eff7ff" | dative
| colspan=4 | {dat_p}
|-
! style="background:#eff7ff" | accusative
| {acc_mp_pers}
| colspan=3 | {acc_p_not_mp_pers}
|-
! style="background:#eff7ff" | instrumental
| colspan=4 | {ins_p}
|-
! style="background:#eff7ff" | locative
| colspan=4 | {loc_p}
]=] .. template_postlude()

	local table_spec_dwa = template_prelude("55") .. [=[
! style="width:40%;background:#d9ebff" colspan="2" | 
! style="background:#d9ebff" colspan="4" | plural
|-
! style="width:40%;background:#d9ebff" colspan="2" | 
! style="background:#d9ebff" | masculine personal
! style="background:#d9ebff" | masculine nonpersonal
! style="background:#d9ebff" | feminine
! style="background:#d9ebff" | neuter
|-
! style="background:#eff7ff" colspan="2" | nominative
| {nom_mp_pers}
| {nom_mp_npers}
| {nom_fp}
| {nom_np}
|-
! style="background:#eff7ff" colspan="2" | genitive
| colspan="4" | {gen_p} 
|-
! style="background:#eff7ff" colspan="2" | dative
| colspan="4" | {dat_p} 
|-
! style="background:#eff7ff" colspan="2" | accusative
| {acc_mp_pers}
| {acc_mp_npers}
| {acc_fp}
| {acc_np}
|-
! style="background:#eff7ff" colspan="2" | instrumental
| colspan="2" | {ins_mnp} 
| {ins_fp} 
| {ins_mnp} 
|-
! style="background:#eff7ff" colspan="2" | locative
| colspan="4" | {loc_p} 
]=] .. template_postlude()

	local short_template = [=[

|-
! style="background:#eff7ff" | short
| colspan="2" | {short}
]=]

	local notes_template = [===[
<div style="width:100%;text-align:left;background:#d9ebff">
<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="pl">' .. forms.lemma .. '</i>'
	end

	local ann_parts = {}
	local decls = {}
	iut.map_word_specs(alternant_multiword_spec, function(base)
		m_table.insertIfNot(decls, get_decl_from_lemma(base))
	end)
	table.insert(ann_parts, table.concat(decls, " // "))
	forms.annotation = " (" .. table.concat(ann_parts, ", ") .. ")"

	forms.notes_clause = forms.footnote ~= "" and
		m_string_utilities.format(notes_template, forms) or ""
	forms.short_clause = forms.short and forms.short ~= "—" and
		m_string_utilities.format(short_template, forms) or ""
	return m_string_utilities.format(
		alternant_multiword_spec.special == "plonly" and table_spec_plonly or
		alternant_multiword_spec.special == "dwa" and table_spec_dwa or
		table_spec_both, forms
	)
end

-- Externally callable function to parse and decline an adjective given user-specified arguments. Return value is
-- ALTERNANT_MULTIWORD_SPEC, an object where the declined forms are in `ALTERNANT_MULTIWORD_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)
	local params = {
		-- Only default param 1 when displaying the template.
		[1] = {template_default = "biały<>"},
		pos = {},
		json = {type = "boolean"}, -- for use with bots
		title = {},
		pagename = {},
	}
	for slot, _ in pairs(input_adjective_slots) do
		params[slot] = {}
	end

	local args = require("Module:parameters").process(parent_args, params)
	local pagename = args.pagename or require("Module:headword/data").pagename
	local args1 = args[1] or pagename
	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(args1, 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)
	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)
	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 {{pl-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


return export