Module:Pl-noun

local export = {}

local m_links = require('Module:links')
local m_adj = require('Module:pl-adj')
local m_g = require('Module:gender and number')
local lang = require("Module:languages").getByCode("pl")

-- local consonants = "[bcćdfghjklłmnńpqrsśtvwxzżź]";

-- case information
local cases = {
	{ key = "nom"; en = "nominative"; pl = "mianownik (kto? co?)" },
	{ key = "gen"; en = "genitive"; pl = "dopełniacz (kogo? czego?)" },
	{ key = "dat"; en = "dative"; pl = "celownik (komu? czemu?)" },
	{ key = "acc"; en = "accusative"; pl = "biernik (kogo? co?)" },
	{ key = "ins"; en = "instrumental"; pl = "narzędnik (kim? czym?)" },
	{ key = "loc"; en = "locative"; pl = "miejscownik (o kim? o czym?)" },
	{ key = "voc"; en = "vocative"; pl = "wołacz (o!)" },
}

-- columns for normal nouns
local noun_cols = {
	{ key = "s"; title = "singular" },
	{ key = "p"; title = "plural" },
}

-- columns for Old Polish nouns
local noun_dual_cols = {
	{ key = "s"; title = "singular" },
	{ key = "d"; title = "dual" },
	{ key = "p"; title = "plural" },
}

-- columns for pronouns
local pronoun_cols = {
	{ key = "sm"; title = m_g.format_list({"m"}, lang) },
	{ key = "sf"; title = m_g.format_list({"f"}, lang) },
	{ key = "sn"; title = m_g.format_list({"n"}, lang) },
	{ key = "pm"; title = m_g.format_list({"m-pr-p"}, lang) },
	{ key = "po"; title = m_g.format_list({"np-p"}, lang) },
}

local altsep = "/"

function empty_item(text)
	return (not text) or text == "" or text == "-" or text == "–" or text == "—"
end

-- add link markers, with links separated by any of splitchars
-- exported for use in testcases
-- normally the separator is altsep
function export.make_links(text, splitchars, title)
	if not title then
		title = mw.title.getCurrentTitle().fullText
	end
	if empty_item(text) then
		return "—"
	elseif not splitchars or splitchars == "" then
		return ("[[%s#Pols|%s]]"):format(text, text)
	else
		items = {}
		for word in mw.ustring.gmatch(text, "[^" .. splitchars .. "]+") do
			if word == title then
				table.insert(items, ("[[%s]]"):format(word))
			else
				table.insert(items, ("[[%s#Pols|%s]]"):format(word, word))
			end
		end
		return table.concat(items, "/")
	end
end

local function linkify_info(declinfo, splitchars, nolinks)
	local linked = {}
	local title = mw.title.getCurrentTitle().fullText
	for k, v in pairs(declinfo) do
		if v == title then
			linked[k] = "[[" .. v .. "]]"
		elseif nolinks then
			linked[k] = v
		else
			linked[k] = export.make_links(v, splitchars, title)
		end
	end
	return linked
end

local function nowiki_info(declinfo)
	local nowikied = {}
	for k, v in pairs(declinfo) do
		nowikied[k] = mw.text.nowiki(v)
	end
	return nowikied
end

local function override_col_titles(heads, cols)
	if not heads then
		return cols or {}
	end
	local new_cols = {}
	local index = 1
	for word in mw.ustring.gmatch(heads, "[^,]+") do
		if cols[index] then
			table.insert(new_cols, { key = cols[index].key; title = word } )
		else
			table.insert(new_cols, { key = tostring(index); title = word } )
		end
		index = index + 1
	end
	for ci, col in ipairs(cols) do
		if ci >= index then
			table.insert(new_cols, cols[index])
		end
	end
	return new_cols
end

local function normalize_tantum(pargs)
	-- support "num" as fallback for "tantum" to match Latin templates
	local tantum = pargs.tantum or pargs.num
	if not tantum then
		return nil
	end
	if tantum == "sg" then
		tantum = "s"
	elseif tantum == "pl" then
		tantum = "p"
	end
	return tantum
end

local function guess_width(declinfo, cols)
	local maxl = 0
	for k, v in pairs(declinfo) do
		local l = mw.ustring.len(mw.text.trim(v))
		if maxl < l then
			maxl = l
		end
	end
	local width = math.floor(maxl * 0.78) -- number obtained by anecdotal evidence
	width = (width < 10) and 10 or width
	width = 9 + (width * #cols)
	return width
end

-- generate the HTML code of an inflection table
-- each entry in heads must have "key" and "title"
local function make_table(declinfo, cols, preproc, width, title, tantum, nolinks)
	local result = {}
	if not cols or not cols[1] then
		error("make_table: invalid cols parameter")
	end

	local lemma_key = cases[1].key .. cols[1].key
	local lemma = m_links.remove_links(declinfo[lemma_key])
	title = title or ('declension of <span class="Latn mention" lang="pl" xml:lang="pl">%s</span>'):format(lemma)

	local emwidth = width or guess_width(declinfo, cols)

	if preproc == "nowiki" then
		declinfo = nowiki_info(declinfo)
	elseif preproc == "linkify" then
		declinfo = linkify_info(declinfo, altsep, nolinks)
	end

	if cols and (#cols > 0) then
		table.insert(result, '|-\n! style="width: 8em;" |\n')
		for i, col in ipairs(cols) do
			table.insert(result, ('! scope="col" | %s\n'):format(col.title))
		end
	end

	local maxl = 0
	for i, case in ipairs(cases) do
		table.insert(result, ('|-\n! title="%s" scope="row" | %s\n'):format(case.pl, case.en))
		for _, col in ipairs(cols) do
			local declkey = case.key .. col.key
			local item = mw.text.trim(declinfo[declkey])
			if empty_item(item) then
				table.insert(result, '| —\n')
			else
				table.insert(result, ('| <span class="Latn" lang="pl" xml:lang="pl">%s</span>\n'):format(item))
			end
		end
	end

	local outtext = ([=[<div class="NavFrame inflection-table-noun" style="width: %uem">
<div class="NavHead">%s</div>
<div class="NavContent">
{| style="width: %uem; margin: 0;" class="wikitable inflection-table"
]=]):format(emwidth, title, emwidth) .. table.concat(result, "") .. "|}</div></div>"

	if tantum == "s" then
		outtext = outtext .. "[[Category:Polish singularia tantum]]"
	elseif tantum == "p" then
		outtext = outtext .. "[[Category:Polish pluralia tantum]]"
	end
	return outtext
end

local function get_mode()
	local frame = mw.getCurrentFrame()
	if mw.isSubsting() then
		return 'subst'
	elseif frame:getParent():getTitle() == mw.title.getCurrentTitle().fullText then
		return 'demo'
	else
		return 'xclude'
	end
end

local function make_table_from_pargs(pargs, cols, tantum)
	local width = pargs.width and tonumber(pargs.width)
	
	local declinfo = {}
	if get_mode() == 'demo' then
		if not cols then
			cols = {
				{ key = "1"; title = "column 1" },
				{ key = "2"; title = "column 2" },
				{ key = "3"; title = "column 3" },
			}
		end
		for i = 1, 7 do
			for j, col in ipairs(cols) do
				local case_key = cases[i].key .. col.key
				local argn = (i-1) * #cols + j
				declinfo[case_key] = '{{{' .. case_key .. '| {{{' .. argn .. '}}} }}}'
			end
		end
		return make_table(declinfo, cols, "nowiki", width, nil, nil, nil)
	else
		cols = override_col_titles(pargs.heads, cols or {})
		for i = 1, 7 do
			for j, col in ipairs(cols) do
				local case_key = cases[i].key .. col.key
				local argn = ((i-1) * #cols) + j
				declinfo[case_key] = m_links.remove_links(mw.text.trim(pargs[case_key] or pargs[argn] or "-"))
			end
		end
		return make_table(declinfo, cols, "linkify", pargs.width, pargs.title, normalize_tantum(pargs), pargs.nolinks)
	end
end

-- Generate declension table for a singulare tantum with all forms passed explicitly as parameters
function export.template_decl_noun_sg(frame)
	local pargs = frame:getParent().args
	local cols = { noun_cols[1] }
	return make_table_from_pargs(pargs, cols, "s")
end

-- Generate declension table for a plurale tantum with all forms passed explicitly as parameters
function export.template_decl_noun_pl(frame)
	local pargs = frame:getParent().args
	local cols = { noun_cols[2] }
	return make_table_from_pargs(pargs, cols, "p")
end

-- Generate declension table for a regular noun with all forms passed explicitly as parameters
function export.template_decl_noun(frame)
	local pargs = frame:getParent().args
	return make_table_from_pargs(pargs, noun_cols, nil)
end

function export.template_decl_pronoun(frame)
	local pargs = frame:getParent().args
	return make_table_from_pargs(pargs, pronoun_cols, nil)
end

-- this probably belongs in a module for Old Polish (zlw-opl).
-- which reminds me: someone should write [[WT:AZLW-OPL]].
-- Generate declension table for a noun with dual number with all forms passed explicitly as parameters
function export.template_decl_noun_dual(frame)
	local pargs = frame:getParent().args
	return make_table_from_pargs(pargs, noun_dual_cols, nil)
end

function export.template_decl_generic(frame)
	local pargs = frame:getParent().args
	local heads = pargs.heads
	if not heads then
		if get_mode() == "demo" then
			heads = "column 1,column 2"
		else
			error("No column headings defined!")
		end
	end
	local cols = override_col_titles(heads, {})
	return make_table_from_pargs(pargs, cols, nil)
end


-- -----------------------------------------------------------------------------
-- -----------------------------------------------------------------------------
-- ------------- Semi-automatic generation of inflected forms ------------------
-- -----------------------------------------------------------------------------
-- -----------------------------------------------------------------------------

local function nonempty(str)
	if not str or str == "" then
		return nil
	else
		return str
	end
end

-- Generate a table contains true for each space-separated word in str
local function make_lookup_table(str)
	local ret = {}
	for i in mw.ustring.gmatch(str, "%a+") do
		ret[i] = true
	end
	return ret
end

-- table that converts nominative soft endings to their genitive form
-- required for proper functioning of masc_common
local soft_ending_lookup = {
	["ć"] = "ci";
	["dź"] = "dzi";
	["ń"] = "ni";
	["ś"] = "si";
	["ź"] = "zi";
}

-- Given a word and a lookup table of accepted endings,
-- split the word into a stem and an ending.
-- Ending is returned in genitive form, i.e. soft consonants
-- and digraphs such as ć, ś, dź are converted to midword
-- i-forms ci, si, dzi.
local function split_stem(word, lookup)
	-- match the longest possible ending
	local last, last_gen
	local limit = math.min(mw.ustring.len(word), 5)
	for i = -limit, -1 do
		if not last_gen then
			last = mw.ustring.sub(word, i)
			if lookup[last] or soft_ending_lookup[last] then
				last_gen = soft_ending_lookup[last] or last
			end
		end
	end

	if last_gen then
		local stem = mw.ustring.sub(word, 1, -mw.ustring.len(last)-1)
		return stem, last_gen
	else
		return nil, nil
	end
end

local function check_split(stem, last)
	if not stem then
		error("nil stem encountered in declension pattern")
	end
	if not last then
		error("nil ending encountered in declension pattern")
	end
end

local function handle_overrides(pargs, declinfo, ovinfo)
	-- process cases in order
	local ret = {}
	local singpl = { "s", "p" }
	for i = 1, 14 do
		local caseno = math.floor((i+1)/2)
		local sp = 2 - (i % 2)
		local case_key = cases[caseno].key .. singpl[sp]
		if pargs[case_key] then
			ret[case_key] = pargs[case_key]
			if ovinfo[case_key] then
				for dummy, v in pairs(ovinfo[case_key]) do
					ret[v] = pargs[case_key]
				end
			end
		elseif not ret[case_key] then
			ret[case_key] = declinfo[case_key]
		end
	end
	return ret
end

-- This table will hold patterns
local patterns = {}


-- -----------------------------------------------------------------------------
-- -------------------------- Masculine declension -----------------------------
-- -----------------------------------------------------------------------------

-- accepted masculine endings in genitive singular form
local masc_endings = make_lookup_table(
	"acz b bi c ch ci ciec ciel cz d dz dzi dziec ek el eł f g giel h iec ieł ik j k kiel l ł m n ni niec p pi r rz rzeł " ..
	"s si seł siec sł sm sn st sz t unek w wi x z zi ż zd zeł ziec zł zm zn")

-- common function for masculine declensions
local function masc_common(pattern, stem, last, gens_ending, noms_form, nomp_form, altgenp, explicit_stem)
	-- verify that the word ending is supported
	if not masc_endings[last] then
		-- suppress module error on the template page
		if not last or not mw.ustring.match(last, "^{{{") then
			error("Unsupported word ending: " .. (last or "nil"))
		end
	end
	local initial_last = last

	-- nominative singular
	local noms_lookup = { bi = "b"; ci = "ć"; dzi = "dź"; ni = "ń"; ["pi"] = "p"; si = "ś"; wi = "w"; zi = "ź"; }
	if not noms_form then
		noms_form = stem .. (noms_lookup[last] or last)
	end

	-- fix the only exceptions to -iec vowel elision
	if last == "iec" and (stem == "w" or stem == "p") then
		if not gens_ending and stem == "p" then
			gens_ending = "a"
		end
		stem = stem .. "ie"
		last = "c"
	end

	-- limited guessing of masculine inanimate endings here
	-- personal and animate nouns (except for ''wół'') always have "a"
	local gens_a = make_lookup_table("acz ciec ciel dzi dziec ek el eł iec ieł ik kiel giel ni niec seł siec rz rzeł zeł")
	if not gens_ending then
		if gens_a[last] then
			gens_ending = "a"
		else
			gens_ending = "u"
		end
	end

	-- handle the following things here:
	--   regular vowel elisions
	--   overlong endings used only for genitive singular guessing, e.g. acz, unek
	--   x becoming ks in inflected forms
	--   stem-final ó becoming o in inflected forms
	local last_lookup = {
		acz = "cz";
		ciec = "c"; ciel = "l";
		dziec = "c";
		ek = "k"; el = "l"; ["eł"] = "ł";
		iec = "c"; ["ieł"] = "ł"; ik = "k";
		kiel = "l";
		giel = "l";
		niec = "c";
		["rzeł"] = "ł";
		["seł"] = "sł"; siec = "c";
		unek = "k";
		x = "s";
		["zeł"] = "zł"; ziec = "c";
	}
	local stem_add = {
		acz = "a";
		ciec = "ć"; ciel = "cie";
		dziec = "dź";
		ik = "i";
		kiel = "k";
		giel = "g";
		niec = "ń";
		["rzeł"] = "r";
		siec = "ś";
		unek = "un"; 
		x = "k";
		ziec = "ź";
	}
	stem = stem .. (stem_add[last] or "")
	last = last_lookup[last] or last

	if mw.ustring.match(stem, "ó$") and not explicit_stem then
		stem = mw.ustring.sub(stem, 1, -2) .. "o"
	end

	-- genitive singular
	local gens_form = stem .. last .. gens_ending

	-- accusative singular
	local accs_form = (pattern == "m-in") and noms_form or gens_form

	-- instrumental singular
	local inss_form
	if (last == "g") or (last == "k") then
		inss_form = stem .. last .. "iem"
	else
		inss_form = stem .. last .. "em"
	end

	-- locative singular
	local locs_lookup = {
		b = "bie"; bi = "biu";
		c = "cu"; ch = "chu"; ci = "ciu"; cz = "czu";
		d = "dzie"; dz = "dzu"; dzi = "dziu";
		f = "fie";
		g = "gu";
		h = "hu";
		j = "ju";
		k = "ku";
		l = "lu"; ["ł"] = "le";
		m = "mie";
		n = "nie"; ni = "niu";
		p = "pie"; ["pi"] = "piu";
		r = "rze"; rz = "rzu";
		s = "sie"; si = "siu"; ["sł"] = "śle"; sm = "śmie"; sn = "śnie";
			st = "ście"; sz = "szu";
		t = "cie";
		w = "wie"; wi = "wiu";
		z = "zie"; zd = "ździe"; zi = "ziu"; ["zł"] = "źle"; zm = "zmie";
			zn = "źnie"; ["ż"] = "żu";
		-- the -zm ending should not be palatalized, since it normally occurs
		-- in borrowed nouns such as "marazm", "komunizm", "faszyzm"
	}
	local locs_form = stem .. (locs_lookup[last] or last)

	-- vocative singular is the same as locative singular, with the exception of -iec
	local vocs_form = locs_form
	if pattern == "m-pr" and mw.ustring.match(initial_last, "iec$") then
		vocs_form = stem .. "cze"
	end

	-- nominative plural
	-- accusative and vocative plural are the same
	if not nomp_form then
		local nomp_e_ending = make_lookup_table("bi c ci cz dz dzi j l ni pi rz si sz wi zi ż")
		if (last == "g") or (last == "k") then
			nomp_form = stem .. last .. "i"
		elseif nomp_e_ending[last] then
			nomp_form = stem ..last .. "e"
		else
			nomp_form = stem .. last .. "y"
		end
	end

	-- genitive plural
	local genp_form
	if mw.ustring.match(last, "i$") and (last ~= "pi") then
		genp_form = stem .. last
	elseif (last == "l") then
		genp_form = stem .. last .. "i"
	elseif (last == "j") then
		genp_form = stem .. last .. "ów"
		if not altgenp or (altgenp == "") then
			altgenp = stem .. "i"
		end
	elseif (last == "cz") or (last == "rz") or (last == "sz") or (last == "ż") then
		genp_form = stem .. last .. "y"
	else
		genp_form = stem .. last .. "ów"
	end
	if nonempty(altgenp) then
		genp_form = genp_form .. "/" .. altgenp
	end

	-- accusative plural - same as genitive or nominative depending on animacy
	local accp_form = (pattern == "m-pr") and genp_form or nomp_form

	return {
		noms = noms_form;             nomp = nomp_form;
		gens = gens_form;             genp = genp_form;
		dats = stem .. last .. "owi"; datp = stem .. last .. "om";
		accs = accs_form;             accp = accp_form;
		inss = inss_form;             insp = stem .. last .. "ami";
		locs = locs_form;             locp = stem .. last .. "ach";
		vocs = vocs_form;             vocp = nomp_form;
	}
end

-- masculine inanimate nouns, e.g. bruk, beton, słup, szczaw, kurnik
-- default genitive singular ending is "u", but "a" endings are common as well
patterns["m-in"] = function (pargs, word)
	local stem, last = split_stem(word, masc_endings)
	local explicit_stem = false
	if nonempty(pargs[2]) then
		stem = pargs[1] or ""
		last = pargs[2]
		explicit_stem = true
	end
	local gens_ending = pargs[3] or gens_ending
	local noms_form = nonempty(pargs[4])
	local altgenp = nonempty(pargs[5])

	local declinfo = masc_common("m-in", stem, last, gens_ending, noms_form,
		nil, altgenp, explicit_stem)
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs"}; locs = {"vocs"}; nomp = {"accp", "vocp"} })
end

-- masculine animate nouns, e.g. bocian, dzik, jeleń
patterns["m-an"] = function (pargs, word)
	local stem, last = split_stem(word, masc_endings)
	local explicit_stem = false
	if nonempty(pargs[2]) then
		stem = pargs[1] or ""
		last = pargs[2]
		explicit_stem = true
	end
	local noms_form = nonempty(pargs[3])
	local altgenp = nonempty(pargs[4])
	
	local declinfo =  masc_common("m-an", stem, last, "a", noms_form,
		nil, altgenp, explicit_stem)
	return handle_overrides(pargs, declinfo,
		{ gens = {"accs"}; locs = {"vocs"}; nomp = {"accp", "vocp"} })
end

-- masculine personal nouns, e.g. policjant, sknerus, polityk
patterns["m-pr"] = function (pargs, word)
	if not pargs[1] and not pargs[2] then
		if mw.ustring.match(word, "a$") then
			return patterns["m-pr-a"](pargs, word)
		elseif mw.ustring.match(word, "[yi]$") then
			return patterns["m-pr-adj"](pargs, word)
		elseif mw.ustring.match(word, "nin$") then
			return patterns["m-pr-nin"](pargs, word)
		end
	end
	local stem, last = split_stem(word, masc_endings)
	local explicit_stem = false
	if nonempty(pargs[2]) then
		stem = pargs[1] or ""
		last = pargs[2] or ""
		explicit_stem = true
	end
	local nomp_form = nonempty(pargs[3]) or nonempty(pargs.nomp)
	local noms_form = nonempty(pargs[4]) or nonempty(pargs.noms)
	local altgenp = nonempty(pargs[5])

	-- note: this is only a default, and will need to be overridden often
	if not nomp_form then
		nomp_lookup = {
			acz = "acze"; -- palacz
			ch = "chowie"; ci = "cie"; ciec = "ćcy"; cz = "cze";
				ciel = "ciele"; -- eunuch, cieć, ?, ?, nauczyciel
			d = "dzi", dziec = "dźcy"; -- kloszard, jeździec
			ek = "kowie"; el = "ele"; -- świadek, ?
			f = "fowie"; -- szeryf
			g = "dzy"; -- szpieg
			h = "howie"; -- druh
			iec = "cy"; ik = "icy"; -- pogrobowiec, czytelnik
			j = "je"; -- lokaj
			k = "cy"; -- alkoholik
			l = "le"; ["ł"] = "łowie"; -- ?, apostoł
			m = "mi"; -- pielgrzym
			n = "ni"; ni = "nie"; niec = "ńcy"; -- kompan, leń, jeniec
			p = "pi"; -- chłop
			r = "rzy"; rz = "rze"; -- konduktor, murarz
			s = "si"; si = "sie"; siec = "ścy"; sz = "sze"; -- ordynans, rabuś, ?, jarosz
			t = "ci"; -- policjant
			z = "zi"; zi = "zie"; ziec = "źcy"; ["ż"] = "żowie"; -- intruz, kniaź, ?, mąż
		}
		if not nomp_lookup[last] then
			error("Unsupported word ending: " .. last ..
				  "; please define the nominative plural form")
		end
		if last == "g" and mw.ustring.match(stem, "lo$") then
			nomp_form = stem .. "dzy/" .. stem .. "gowie"
		else
			nomp_form = stem .. nomp_lookup[last]
		end
	end

	local declinfo = masc_common("m-pr", stem, last, "a", noms_form,
		nomp_form, altgenp, explicit_stem)
	return handle_overrides(pargs, declinfo,
		{ gens = {"accs"}; locs = {"vocs"}; nomp = {"vocp"}; genp = {"accp"} })
end

-- masculine personal nouns with adjectival declension
-- e.g. radny, łowczy, salowy
patterns["m-pr-adj"] = function (pargs, title)
	local word = pargs[1] or title
	if mw.ustring.match(word, "^{{{") then
		word = "przykładowy"
	end
	local decl = m_adj.autoinflect(word)
	local declinfo = {
		noms = decl[1];  nomp = decl[4];
		gens = decl[6];  genp = decl[8];
		dats = decl[9];  datp = decl[10];
		accs = decl[6];  accp = decl[8];
		inss = decl[12]; insp = decl[13];
		locs = decl[12]; locp = decl[8];
		vocs = decl[1];  vocp = decl[4];
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"vocs"}; gens = {"accs"}; nomp = {"vocp"}; genp = {"accp", "locp"} })
end

-- masculine personal nouns that end in -a
-- e.g. stażysta, dawca, banita
patterns["m-pr-a"] = function (pargs, word)
	local word_no_a = mw.ustring.match(word, "^(.*)a$") or ""
	local endings = make_lookup_table("b bi c ch d g j p r st t zd zn ż")
	local stem, last = split_stem(word_no_a, endings)
	stem = nonempty(pargs[1]) or stem
	last = nonempty(pargs[2]) or last

	if not endings[last] then
		-- suppress module error on the template page
		if not mw.ustring.match(last, "^{{{") then
			error("Unsupported word ending: " .. last)
		end
	end

	local gens_form = stem .. last .. "y"
	if last == "j" then
		gens_form = stem .. "i"
    elseif last == "g" then
	    gens_form = stem .. "gi"
	end
	local dats_lookup = {
		b = "bie"; -- Barnaba
		bi = "bi";
		c = "cy"; -- zdrajca
		ch = "sze"; -- monarcha
		d = "dzie"; -- nomada (?)
		g = "dze"; -- sługa
		j = "i"; -- kaznodzieja
		p = "pie"; -- satrapa
		r = "rze"; -- sknera
		st = "ście"; -- starosta
		t = "cie"; -- idiota
		zd = "ździe"; -- gazda
		zn = "źnie"; -- mężczyzna
		["ż"] = "ży"; -- doża
	}
	local dats_form = stem .. (dats_lookup[last] or last)

	local nomp_lookup = {
		b = "bowie";
		bi = "biowie";
		c = "cy";
		ch = "chowie";
		d = "dzi";
		g = "dzy";
		j = "je";
		p = "powie";
		r = "rzy";
		st = "ści";
		t = "ci";
		zd = "zdowie";
		zn = "źni";
		["ż"] = "żowie";
	}
	local nomp_form = stem .. (nomp_lookup[last] or (last .. "i"))
	local genp_form = stem .. last .. "ów"
	if last == "zn" then
		genp_form = stem .. last
	end

	-- TODO: alternative adjectival declension for -bia
	local declinfo = {
		noms = stem .. last .. "a";  nomp = nomp_form;
		gens = gens_form;            genp = genp_form;
		dats = dats_form;            datp = stem .. last .. "om";
		accs = stem .. last .. "ę";  accp = genp_form;
		inss = stem .. last .. "ą";  insp = stem .. last .. "ami";
		locs = dats_form;            locp = stem .. last .. "ach";
		vocs = stem .. last .. "o";  vocp = nomp_form;
	}
	return handle_overrides(pargs, declinfo,
		{ dats = {"locs"}; nomp = {"vocp"}; genp = {"accp"} })
end

-- masculine nouns ending in -log, both personal and inanimate
-- inanimate form is chosen when the first parameter is empty
patterns["log"] = function (pargs, title)
	local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)log$") or ""
	local nomp_form = nonempty(pargs[2])
	local inanimate = not nonempty(pargs[1])

	if not nomp_form then
		if inanimate then
			nomp_form = stem .. "logi"
		else
			nomp_form = stem .. "lodzy/" .. stem .. "logowie"
		end
	end

	local gens_form = stem .. "loga"
	if inanimate then
		gens_form = stem .. "logu"
	end

	local accs_form = stem .. "loga"
	if inanimate then
		accs_form = stem .. "log"
	end

	local declinfo = {
		noms = stem .. "log";     nomp = nomp_form;
		gens = gens_form;         genp = stem .. "logów";
		dats = stem .. "logowi";  datp = stem .. "logom";
		accs = accs_form;         accp = stem .. "logów";
		inss = stem .. "logiem";  insp = stem .. "logami";
		locs = stem .. "logu";    locp = stem .. "logach";
		vocs = stem .. "logu";    vocp = nomp_form;
	}
	return handle_overrides(pargs, declinfo,
		{ locs = {"vocs"}; nomp = {"vocp"}; genp = {"accp"} })
end

-- masculine personal nouns ending with -nin, e.g. Rosjanin, łodzianin
-- popular for demonyms
patterns["m-pr-nin"] = function (pargs, word)
	local stem = nonempty(pargs[1]) or mw.ustring.match(word, "^(.*)nin$")
	local genp_ending = pargs[2] or "n"
	local declinfo = {
		noms = stem .. "nin";     nomp = stem .. "nie";
		gens = stem .. "nina";    genp = stem .. genp_ending;
		dats = stem .. "ninowi";  datp = stem .. "nom";
		accs = stem .. "nina";    accp = stem .. genp_ending;
		inss = stem .. "ninem";   insp = stem .. "nami";
		locs = stem .. "ninie";   locp = stem .. "nach";
		vocs = stem .. "ninie";   vocp = stem .. "nie";
	}
	return handle_overrides(pargs, declinfo,
		{ gens = {"accs"}; locs = {"vocs"}; nomp = {"vocp"}; genp = {"accp"} })
end

-- masculine inanimate adjectives in noun phrases
patterns["adj-m-in"] = function (pargs, word)
	word = nonempty(pargs[1]) or word
	if mw.ustring.match(word, "^{{{") then
		word = "przykładowy"
	end
	local decl = m_adj.autoinflect(word)
	local declinfo = {
		noms = decl[1];  nomp = decl[5];
		gens = decl[6];  genp = decl[8];
		dats = decl[9];  datp = decl[10];
		accs = decl[1];  accp = decl[5];
		inss = decl[12]; insp = decl[13];
		locs = decl[12]; locp = decl[8];
		vocs = decl[1];  vocp = decl[5];
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs", "vocs"}; inss = {"locs"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end

-- masculine animate adjectives in noun phrases
patterns["adj-m-an"] = function (pargs, word)
	word = nonempty(pargs[1]) or word
	if mw.ustring.match(word, "^{{{") then
		word = "przykładowy"
	end
	local decl = m_adj.autoinflect(word)
	local declinfo = {
		noms = decl[1];  nomp = decl[5];
		gens = decl[6];  genp = decl[8];
		dats = decl[9];  datp = decl[10];
		accs = decl[6];  accp = decl[5];
		inss = decl[12]; insp = decl[13];
		locs = decl[12]; locp = decl[8];
		vocs = decl[1];  vocp = decl[5];
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"vocs"}; gens = {"accs"}; inss = {"locs"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end

-- masculine personal adjectives in noun phrases are identical
-- to masculine personal adjectival declension


-- -----------------------------------------------------------------------------
-- -------------------------- Feminine declension ------------------------------
-- -----------------------------------------------------------------------------

local fem_endings = make_lookup_table(
	"b bi c ch ci cz d dz dzi dż f g h j k l ł m n ni p pi r rz " ..
	"s si sł sm sn st sz t w wi z zd zi zł zm zn ż")

-- most feminine nouns ending in -a
-- note that some nouns ending in -ia have this declension and others
-- follow the "f-ia" pattern - it's impossible to tell from the word alone.
patterns["f"] = function (pargs, word)
	-- to handle pluralia tantum as well
	local word_no_a = mw.ustring.match(word, "^(.*)[aeiy]$")
	local pars3 = pargs[1] and pargs[2] and pargs[3]
	local nopars = not pargs[1] and not pargs[2] and not pargs[3]

	-- 2 positional parameters given OR a at the and -> use a-final declension
	-- 3+ positional parameters given OR consonant at the end -> use f-softcons
	if nopars then
		if mw.ustring.match(word, "ia$") then
			native_ia = mw.ustring.match(word, "[bcdjklłrśtwzź]nia$")
			native_ia = native_ia or mw.ustring.match(word, "[csz]ia$")
			if not native_ia then
				return patterns["f-ia"](pargs, word)
			end
		elseif mw.ustring.match(word, "ni$") then
			return patterns["f-ni"](pargs, word)
		end
	end
	if pars3 or not word_no_a then
		return patterns["f-softcons"](pargs, word)
	end

	word_no_a = word_no_a or word
	local stem, last = split_stem(word_no_a, fem_endings)
	stem = nonempty(pargs[1]) or stem
	last = nonempty(pargs[2]) or last

	if not fem_endings[last] then
		-- suppress module error on the template page
		if not mw.ustring.match(last, "^{{{") then
			error("Unsupported word ending: " .. last)
		end
	end

	local soft_endings = make_lookup_table("bi ci dzi ni pi si wi zi")

	local gens_form = stem .. last .. "y"
	if soft_endings[last] then
		gens_form = stem .. last
	elseif last == "j" then
		if mw.ustring.match(stem, "[aąeęioóuy]$") then
			gens_form = stem .. "i"
		else
			gens_form = stem .. last .. "i"
		end
	elseif (last == "g") or (last == "k") or (last == "l") then
		gens_form = stem .. last .. "i"
	end

	local dats_lookup = {
		b = "bie"; bi = "bi";
		c = "cy"; ch = "sze"; ci = "ci"; cz = "czy";
		d = "dzie"; dz = "dzy"; dzi = "dzi"; ["dż"] = "dży";
		f = "fie";
		g = "dze";
		h = "że";
		j = "ji";
		k = "ce";
		l = "li"; ["ł"] = "le";
		m = "mie";
		n = "nie"; ni = "ni";
		p = "pie"; ["pi"] = "pi";
		r = "rze"; rz = "rzy";
		s = "sie"; si = "si"; ["sł"] = "śle"; sm = "śmie"; sn = "śnie";
			st = "ście"; sz = "szy";
		t = "cie";
		w = "wie"; wi = "wi";
		z = "zie"; zd = "ździe"; zi = "zi"; ["zł"] = "źle"; zm = "zmie";
			zn = "źnie"; ["ż"] = "ży";
		-- -zm should not be palatalized
	}
	local dats_form = stem .. (dats_lookup[last] or last)
	
	if last == "j" and mw.ustring.match(stem, "[aąeęioóuy]$") then
		dats_form = stem .. "i"
	end

	local nomp_e_ending = make_lookup_table("bi c ci cz dz dzi dż j l ni pi rz si sz wi zi ż")
	
	local nomp_form = stem .. last .. "y"

	if (last == "g") or (last == "k") then
		nomp_form = stem .. last .. "i"
	elseif nomp_e_ending[last] then
		nomp_form = stem .. last .. "e"
	end

	local genp_form = stem .. last
	if last == "j" then
		if mw.ustring.match(stem, "[aąeęioóuy]$") then
			-- zgraja, breja, żmija, koja, szuja, chryja ->
			-- zgraj, brej, żmij, koj, szuj, chryj
			genp_form = stem .. last
		else
			-- e.g. gracja, torsja
			genp_form = stem .. "ji/" .. stem .. "yj"
		end
	elseif last == "l" then
		if mw.ustring.match(stem, "[aąeęioóuy]$") then
			-- hala, tabela, mila, rola, kula
			genp_form = stem .. "l"
		else
			-- hodowla, grobla, bernikla
			genp_form = stem .. "li"
		end
	elseif last == "k" then
		local kstem, klast = split_stem(stem, soft_ending_lookup)
		if kstem then
			kstem = kstem .. klast
		else
			kstem = stem
		end
		if mw.ustring.match(stem, "[aąeęioóuy]$") then
			genp_form = kstem .. "k"
		else
			genp_form = kstem .. "ek"
		end
	end

	local declinfo = {
		noms = stem .. last .. "a";  nomp = nomp_form;
		gens = gens_form;            genp = genp_form;
		dats = dats_form;            datp = stem .. last .. "om";
		accs = stem .. last .. "ę";  accp = nomp_form;
		inss = stem .. last .. "ą";  insp = stem .. last .. "ami";
		locs = dats_form;            locp = stem .. last .. "ach";
		vocs = stem .. last .. "o";  vocp = nomp_form;
	}
	return handle_overrides(pargs, declinfo,
		{ dats = {"locs"}; nomp = {"accp", "vocp"} })
end

-- feminine nouns ending with a consonant, e.g. stal, sól, brew
patterns["f-softcons"] = function (pargs, title)
	local endings = make_lookup_table("c ć cz dz dź ew iew j l ń rz ś sz w z ż ź")
	local accepted_lasts = make_lookup_table("c ci cz dz dzi j l ni rz si sz wi zi ż")
	local stem, last = split_stem(title, endings)
	local explicit_stem = false
	stem = nonempty(pargs[1]) or stem
	last = nonempty(pargs[2]) or last
	if nonempty(pargs[2]) then
		stem = pargs[1] or ""
		last = pargs[2] or ""
		explicit_stem = true
	end
	local nomp_ending = pargs[3] -- may be empty
	local noms_form = nonempty(pargs[4])

	-- Narew, brukiew, żagiew, brew - elide -e- or -ie-
	if last == "iew" or last == "ew" then
		if not noms_form then
			noms_form = stem .. last
		end
		last = "wi"
	end

	if last == "w" or last == "z" then
		last = last .. "i"
	end
	if not accepted_lasts[last] then
		-- suppress module error on the template page
		if not last or not mw.ustring.match(last, "^{{{") then
			error("Unsupported word ending: " .. (last or "nil"))
		end
	end

	-- nominative singular
	if not noms_form or (noms_form == "") then
		local noms_lookup = {
			ci = "ć"; dzi = "dź"; ni = "ń"; si = "ś"; wi = "w"; zi = "ź"; }
		noms_form = stem .. (noms_lookup[last] or last)
	end
	
	-- Stem-final ó becoming o in inflected forms
	if mw.ustring.match(stem, "ó$") and not explicit_stem then
		stem = mw.ustring.sub(stem, 1, -2) .. "o"
	end

	-- genitive singular
	local gens_form = stem .. last
	local gens_y_ending = make_lookup_table("c cz dz sz rz ż")
	if last == "j" then
		gens_form = stem .. "i"
	elseif last == "l" then
		gens_form = gens_form .. "i"
	elseif gens_y_ending[last] then
		gens_form = gens_form .. "y"
	end

	-- nominative plural
	if not nomp_ending then
		if last == "ci" then
			-- some words have -e, but this is a lot more common
			nomp_ending = ""
		else
			nomp_ending = "e"
		end
	end
	-- this is trivial, but used in 3 places in the table
	local nomp_form = stem .. last .. nomp_ending;

	local declinfo = {
		noms = noms_form;            nomp = nomp_form;
		gens = gens_form;            genp = gens_form;
		dats = gens_form;            datp = stem .. last .. "om";
		accs = noms_form;            accp = nomp_form;
		inss = stem .. last .. "ą";  insp = stem .. last .. "ami";
		locs = gens_form;            locp = stem .. last .. "ach";
		vocs = gens_form;            vocp = nomp_form;
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs"}; gens = {"dats", "locs", "vocs", "genp"}; nomp = {"accp", "vocp"} })
end

-- feminine nouns with adjectival declension
patterns["f-adj"] = function (pargs, title)
	local word = nonempty(pargs[1]) or title
	if mw.ustring.match(word, "^{{{") then
		word = "przykładowa"
	end
	local decl = m_adj.autoinflect(word)
	local declinfo = {
		noms = decl[2];   nomp = decl[5];
		gens = decl[7];   genp = decl[8];
		dats = decl[7];   datp = decl[10];
		accs = decl[11];  accp = decl[5];
		inss = decl[11];  insp = decl[13];
		locs = decl[7];   locp = decl[8];
		vocs = decl[2];   vocp = decl[5];
	}
	return handle_overrides(pargs, declinfo,
		{ gens = {"dats", "locs"}; accs = {"inss"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
		-- "vocs" may not always be equal to "noms" (e.g. "teściowa")
end

-- feminine nouns ending with -ja, e.g. fuzja, anomizja, animacja
-- obsolete - "f" handles them as well
patterns["f-ja"] = function (pargs, title)
	local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ja$")
	local yj = "yj"
	if pargs[2] and (pargs[2] ~= "") then
		yj = "ij"	
	end
	genp_form = stem .. "ji/" .. stem .. yj;

	local declinfo = {
		noms = stem .. "ja";  nomp = stem .. "je";
		gens = stem .. "ji";  genp = genp_form;
		dats = stem .. "ji";  datp = stem .. "jom";
		accs = stem .. "ję";  accp = stem .. "je";
		inss = stem .. "ją";  insp = stem .. "jami";
		locs = stem .. "ji";  locp = stem .. "jach";
		vocs = stem .. "jo";  vocp = stem .. "je";
	}
	return handle_overrides(pargs, declinfo,
		{ gens = {"dats", "locs"}; nomp = {"accp", "vocp"} })
end

-- most feminine nouns ending with -ia, e.g. mafia, kopia, balia
-- note that some nouns follow the "f" pattern instead, e.g. konopia, głębia
patterns["f-ia"] = function (pargs, title)
	local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ia$")
	local oldgenp = stem .. "ij"
	if mw.ustring.match(stem, "[dtr]$") then
		oldgenp = stem .. "yj"
	end
	local genp_form = stem .. "ii/" .. oldgenp;
	
	local declinfo = {
		noms = stem .. "ia";  nomp = stem .. "ie";
		gens = stem .. "ii";  genp = genp_form;
		dats = stem .. "ii";  datp = stem .. "iom";
		accs = stem .. "ię";  accp = stem .. "ie";
		inss = stem .. "ią";  insp = stem .. "iami";
		locs = stem .. "ii";  locp = stem .. "iach";
		vocs = stem .. "io";  vocp = stem .. "ie";
	}
	return handle_overrides(pargs, declinfo,
		{ gens = {"dats", "locs"}; nomp = {"accp", "vocp"} })
end

-- feminine nouns ending in -ni, e.g. mistrzyni, mędrczyni
patterns["f-ni"] = function (pargs, title)
	local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ni$")
	local declinfo = {
		noms = stem .. "ni";  nomp = stem .. "nie";
		gens = stem .. "ni";  genp = stem .. "ń";
		dats = stem .. "ni";  datp = stem .. "niom";
		accs = stem .. "nię"; accp = stem .. "nie";
		inss = stem .. "nią"; insp = stem .. "niami";
		locs = stem .. "ni";  locp = stem .. "niach";
		vocs = stem .. "ni";  vocp = stem .. "nie";
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"gens", "dats", "locs", "vocs"}; nomp = {"accp", "vocp"} })
end

-- feminine adjectives in noun phrases
patterns["adj-f"] = function (pargs, title)
	local word = nonempty(pargs[1]) or title
	if mw.ustring.match(word, "^{{{") then
		word = "przykładowa"
	end
	local decl = m_adj.autoinflect(word)
	local declinfo = {
		noms = decl[2];  nomp = decl[5];
		gens = decl[7];  genp = decl[8];
		dats = decl[7];  datp = decl[10];
		accs = decl[11]; accp = decl[5];
		inss = decl[11]; insp = decl[13];
		locs = decl[7];  locp = decl[8];
		vocs = decl[2];  vocp = decl[5];
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"vocs"}; gens = {"dats", "locs"}; accs = {"inss"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end


-- -----------------------------------------------------------------------------
-- -------------------------- Neuter declension -----------------------------
-- -----------------------------------------------------------------------------

-- neuter nouns ending in -o, e.g. jajko, jarzmo, gusło, cudo
patterns["n"] = function (pargs, word)
	if pargs[1] and mw.ustring.match(pargs[1], "^{{{") then
		word = "jajko"
	end

	-- forward to other patterns depending on the last letter
	if not nonempty(pargs[1]) and not nonempty(pargs[2]) then
		if mw.ustring.match(word, "e$") then
			return patterns["n-e"](pargs, word)
		elseif mw.ustring.match(word, "ę$") then
			return patterns["n-ę"](pargs, word)
		elseif mw.ustring.match(word, "um$") then
			return patterns["n-um"](pargs, word)
		end
	end

	local word_no_o = mw.ustring.match(word, "^(.*)o$")
	local endings = make_lookup_table(
		"b bn c ch d f g gn j k kn l ł m mn n nd p r rz rzm " ..
		"s sk sł sm sn st t tt tw w wn z zd zł zm zn zz")
	local stem, last
	if word_no_o then
		stem, last = split_stem(word_no_o, endings)
	end
	stem = nonempty(pargs[1]) or stem
	last = nonempty(pargs[2]) or last
	local genp_form = nonempty(pargs[3])

	if not endings[last] then
		-- suppress module error on the template page
		if not mw.ustring.match(last, "^{{{") then
			error("Unsupported word ending: " .. last)
		end
	end

	local inss_form = stem .. last .. "em"
	if last == "g" or last == "k" then
		inss_form = stem .. last .. "iem"
	end

	local locs_lookup = {
		b = "bie"; bn = "bnie";
		c = "cu"; ch = "chu";
		d = "dzie";
		f = "fie";
		g = "gu"; gn = "gnie";
		j = "ju";
		k = "ku"; kn = "knie";
		l = "lu"; ["ł"] = "le";
		m = "mie"; mn = "mnie";
		n = "nie"; nd = "ndzie";
		p = "pie";
		r = "rze"; rz = "rzu"; rzm = "rzmie";  -- piórze, scherzu, jarzmie
		s = "sie"; sk = "sku"; ["sł"] = "śle"; sm = "śmie"; sn = "śnie"; st = "ście";
		t = "cie"; tt = "tcie"; tw = "twie";
		w = "wie"; wn = "wnie";
		z = "zie"; zd = "ździe"; ["zł"] = "źle"; zm = "źmie"; zn = "źnie"; zz = "zzu";
	}

	locs_form = stem .. (locs_lookup[last] or (last .. "u"))

	if not genp_form then
		if last == "sn" or last == "zn" then
			genp_form = stem .. mw.ustring.sub(last, 1, 1) .. "en"  -- e.g. krosno -> krosen
		elseif mw.ustring.match(last, "^.n$") then
			genp_form = stem .. mw.ustring.sub(last, 1, 1) .. "ien"  -- e.g. bagno -> bagien
		elseif last == "tw" or last == "sk" or mw.ustring.match(stem, "[aąeęioóuy]$") then
			genp_form = stem .. last  -- e.g. bogactwo -> bogactw, dyktando -> dyktand (nd treated as digraph)
		else
			if mw.ustring.match(stem, "[gk]$") then
				genp_form = stem .. "ie" .. last  -- e.g. szkło -> szkieł
			else	
				genp_form = stem .. "e" .. last  -- e.g. jajko -> jajek
			end	
		end
	end
	
	local declinfo = {
		noms = stem .. last .. "o";  nomp = stem .. last .. "a";
		gens = stem .. last .. "a";  genp = genp_form;
		dats = stem .. last .. "u";  datp = stem .. last .. "om";
		accs = stem .. last .. "o";  accp = stem .. last .. "a";
		inss = inss_form;            insp = stem .. last .. "ami";
		locs = locs_form;            locp = stem .. last .. "ach";
		vocs = stem .. last .. "o";  vocp = stem .. last .. "a";
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs", "vocs"}; gens = {"nomp", "accp", "vocp"}; nomp = {"accp", "vocp"} })
end

-- neuter nouns ending in -e
patterns["n-e"] = function (pargs, word)
	local word_no_e = mw.ustring.match(word, "^(.*)e$")
	local endings = make_lookup_table("bi c ci cz dz dzi fi j l mi ni pi rz si sz wi zi ż")
	local stem, last
	if word_no_e then
		stem, last = split_stem(word_no_e, endings)
	end
	if nonempty(pargs[1]) then
		stem = pargs[1]
		last = ""
	end
	
	local genp_lookup = { ci = "ć"; cz = "czy"; ni = "ń"; rz = "rzy"; sz = "szy"; ["ż"] = "ży"; }
	local genp_form = pargs[3] or (stem .. (genp_lookup[last] or last))
	local sl = stem .. last

	local declinfo = {
		noms = sl .. "e";   nomp = sl .. "a";
		gens = sl .. "a";   genp = genp_form;
		dats = sl .. "u";   datp = sl .. "om";
		accs = sl .. "e";   accp = sl .. "a";
		inss = sl .. "em";  insp = sl .. "ami";
		locs = sl .. "u";   locp = sl .. "ach";
		vocs = sl .. "e";   vocp = sl .. "a";
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs", "vocs"}; gens = {"nomp", "accp", "vocp"}; dats = {"locs"}; nomp = {"accp", "vocp"} })
end

-- neuter nouns ending in -ę but not in -mię, e.g. kocię, szczenię, dziecię
patterns["n-ę"] = function (pargs, title)
	local mstem = mw.ustring.match(title, "^(.*)mię$")
	if mstem then
		pargs[1] = mstem
		return patterns["n-mię"](pargs, title)
	end
	local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ę$")
	local declinfo = {
		noms = stem .. "ę";     nomp = stem .. "ęta";
		gens = stem .. "ęcia";  genp = stem .. "ąt";
		dats = stem .. "ęciu";  datp = stem .. "ętom";
		accs = stem .. "ę";     accp = stem .. "ęta";
		inss = stem .. "ęciem"; insp = stem .. "ętami";
		locs = stem .. "ęciu";  locp = stem .. "ętach";
		vocs = stem .. "ę";     vocp = stem .. "ęta";
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs", "vocs"}; dats = {"locs"}; nomp = {"accp", "vocp"} })
end

-- neuter nouns ending in -mię, e.g. wymię, znamię, plemię
patterns["n-mię"] = function (pargs, title)
	local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)mię$")
	local declinfo = {
		noms = stem .. "mię";      nomp = stem .. "miona";
		gens = stem .. "mienia";   genp = stem .. "mion";
		dats = stem .. "mieniu";   datp = stem .. "mionom";
		accs = stem .. "mię";      accp = stem .. "miona";
		inss = stem .. "mieniem";  insp = stem .. "mionami";
		locs = stem .. "mieniu";   locp = stem .. "mionach";
		vocs = stem .. "mię";      vocp = stem .. "miona";
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs", "vocs"}; dats = {"locs"}; nomp = {"accp", "vocp"} })
end

-- neuter nouns with adjectival declension, e.g. bykowe
patterns["n-adj"] = function (pargs, title)
	local word = nonempty(pargs[1]) or title
	if mw.ustring.match(word, "^{{{") then
		word = "przykładowe"
	end

	local decl = m_adj.autoinflect(word)
	local declinfo = {
		noms = decl[3];   nomp = decl[5];
		gens = decl[6];   genp = decl[8];
		dats = decl[9];   datp = decl[10];
		accs = decl[3];   accp = decl[5];
		inss = decl[12];  insp = decl[13];
		locs = decl[12];  locp = decl[8];
		vocs = decl[3];   vocp = decl[5];
	}
	return handle_overrides(pargs, declinfo,
		{ noms = {"accs", "vocs"}; inss = {"locs"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end

-- neuter nouns ending in -um, e.g. liceum, gimnazjum, kryterium
-- mostly nouns borrow from ancient Greek
patterns["n-um"] = function (pargs, title)
	local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)um$")
	local declinfo = {
		noms = stem .. "um";  nomp = stem .. "a";
		gens = stem .. "um";  genp = stem .. "ów";
		dats = stem .. "um";  datp = stem .. "om";
		accs = stem .. "um";  accp = stem .. "a";
		inss = stem .. "um";  insp = stem .. "ami";
		locs = stem .. "um";  locp = stem .. "ach";
		vocs = stem .. "um";  vocp = stem .. "a";
	}
	return handle_overrides(pargs, declinfo,
		{ nomp = {"accp", "vocp"} })
end

-- highly irregular nouns
patterns["irreg"] = function (pargs, title)
	
	local word = make_lookup_table("brat dziecko ksiądz książę rok")
	
	if not word[title] then
		if not mw.ustring.match(title, "^{{{") then
			error("Unsupported word")
		end
	end
	
	-- brat	
	if title == "brat" then
		noms_form = "brat"
		gens_form = "brata"
		dats_form = "bratu"
		accs_form = "brata"
		inss_form = "bratem"
		locs_form = "bracie"
		vocs_form = "bracie"
		nomp_form = "bracia"
		genp_form = "braci"
		datp_form = "braciom"
		accp_form = "braci"
		insp_form = "braćmi"
		locp_form = "braciach"
		vocp_form = "bracia"
	end
	
	-- dziecko
	if title == "dziecko" then
		noms_form = "dziecko"
		gens_form = "dziecka"
		dats_form = "dziecku"
		accs_form = "dziecko"
		inss_form = "dzieckiem"
		locs_form = "dziecku"
		vocs_form = "dziecko"
		nomp_form = "dzieci"
		genp_form = "dzieci"
		datp_form = "dzieciom"
		accp_form = "dzieci"
		insp_form = "dziećmi"
		locp_form = "dzieciach"
		vocp_form = "dzieci"
	end
	
	--ksiądz
	if title == "ksiądz" then
		noms_form = "ksiądz"
		gens_form = "księdza"
		dats_form = "księdzu"
		accs_form = "księdza"
		inss_form = "księdzem"
		locs_form = "księdzu"
		vocs_form = "księże"
		nomp_form = "księża"
		genp_form = "księży"
		datp_form = "księżom"
		accp_form = "księży"
		insp_form = "księżmi"
		locp_form = "księżach"
		vocp_form = "księża"
	end
	
	--książę
	if title == "książę" then
		noms_form = "książę"
		gens_form = "księcia"
		dats_form = "księciu"
		accs_form = "księcia"
		inss_form = "księciem"
		locs_form = "księciu"
		vocs_form = "książę"
		nomp_form = "książęta"
		genp_form = "książąt"
		datp_form = "książętom"
		accp_form = "książąt"
		insp_form = "książętami"
		locp_form = "książętach"
		vocp_form = "książęta"
	end
	
	-- rok
	if title == "rok" then
		noms_form = "rok"
		gens_form = "roku"
		dats_form = "rokowi"
		accs_form = "rok"
		inss_form = "rokiem"
		locs_form = "roku"
		vocs_form = "roku"
		nomp_form = "lata"
		genp_form = "lat"
		datp_form = "latom"
		accp_form = "lata"
		insp_form = "latami"
		locp_form = "latach"
		vocp_form = "lata"
	end	
	
	local declinfo = {
		noms = noms_form;            nomp = nomp_form;
		gens = gens_form;            genp = genp_form;
		dats = dats_form;            datp = datp_form;
		accs = accs_form;            accp = accp_form;
		inss = inss_form;            insp = insp_form;
		locs = locs_form;            locp = locp_form;
		vocs = vocs_form;            vocp = vocp_form;
	}
	return handle_overrides(pargs, declinfo, {})
end	

-- indeclinable pattern, i.e. same word for all cases
patterns["indec"] = function (pargs, title)
	local word = nonempty(pargs[1]) or title
	local declinfo = {}
	for i = 1, 7 do
		for _, col in ipairs(noun_cols) do
			declinfo[cases[i].key .. col.key] = word
		end
	end
	return handle_overrides(pargs, declinfo, {})
end

-- shorthands
patterns["nin"] = patterns["m-pr-nin"]
patterns["ia"] = patterns["fem-ia"]

-- aliases for adjectives in phrases
patterns["adj-m-pr"] = patterns["m-pr-adj"]
patterns["adj-n"] = patterns["n-adj"]

-- used for autodetection of adjective genders in phrases
local function pattern_gender(pattern)
	if pattern == "m-in" or pattern == "adj-m-in" then
		return "m-in"
	elseif pattern == "m-an" or pattern == "adj-m-an" then
		return "m-an"
	elseif string.match(pattern, "^m-pr") or pattern == "adj-m-pr" then
		return "m-pr"
	elseif string.match(pattern, "^f") or pattern == "adj-f" then
		return "f"
	elseif string.match(pattern, "^n") or pattern == "adj-n" then
		return "n"
	end
	return nil
end

-- generate inflected forms given pattern, parameters, and full word
-- returned words do not contain links
function export.autoinflect(pattern, pargs, word)
	if not pattern then
		error("Declension pattern not specified")
	end
	if mw.ustring.match(pattern, "^{{{") then
		pattern = "m-in"
	end
	if not patterns[pattern] then
		error("Invalid declension pattern: " .. pattern)
	end
	return patterns[pattern](pargs, pargs["lemma"] or word)
end

local function make_substitutable_table(pargs, declinfo, preproc)
	local tantum = normalize_tantum(pargs)
	if mw.isSubsting() then
		if tantum == "s" or tantum == "p" then
			local tname = "sing"
			if tantum == "p" then
				tname = "pl"
			end
			local rows = {}
			for i = 1, 7 do
				local case_key = cases[i].key .. tantum
				table.insert(rows, ("| <!-- %s --> %s"):format(case_key, m_links.remove_links(declinfo[case_key])))
			end
			return "{{pl-decl-noun-" .. tname .. "\n" .. table.concat(rows, "\n") .. "\n}}"
		end

		local rows = {}
		for i = 1, 7 do
			local case_skey = cases[i].key .. "s"
			local case_pkey = cases[i].key .. "p"
			table.insert(rows, ("| <!-- %s --> %s"):format(case_skey, m_links.remove_links(declinfo[case_skey])))
			table.insert(rows, ("| <!-- %s --> %s"):format(case_pkey, m_links.remove_links(declinfo[case_pkey])))
		end
		return "{{pl-decl-noun\n" .. table.concat(rows, "\n") .. "\n}}"
	else
		local cols = noun_cols
		if tantum == "s" then
			cols = { noun_cols[1] }
		elseif tantum == "p" then
			cols = { noun_cols[2] }
		end
		cols = override_col_titles(pargs.heads, cols)

		if pargs.nocat then
			tantum = nil
		end
		return make_table(declinfo, cols, preproc, pargs.width, pargs.title, tantum, pargs.nolinks)
	end
end

-- Generate declension table for a specified declension pattern
function export.template_decl_pattern(frame)
	local args = frame.args
	local pargs = frame:getParent().args
	local pagetitle = mw.title.getCurrentTitle().fullText

	-- support "num" as fallback for "tantum" to match Latin templates
	local tantum = pargs.tantum or pargs.num
	if tantum == "sg" then
		tantum = "s"
	elseif tantum == "pl" then
		tantum = "p"
	end

	if get_mode() == 'demo' then
		pargs = { "{{{1}}}", "{{{2}}}", "{{{3}}}", "{{{4}}}", "{{{5}}}" }
	end

	-- if args is empty, use pargs[1] as the pattern name and shift pargs
	local offset = 0
	local pattern = args[1]
	if not pattern then
		-- extreme brokenness: table.remove(pargs, 1) has absolutely no effect!
		-- is this because pargs is somehow immutable?
		pattern = pargs[1]
		offset = -1
	end

	local fixed_pargs = {}
	for key, parg in pairs(pargs) do
		if type(key) == "number" then
			local i = key + offset
			if i >= 1 and parg then
				fixed_pargs[i] = m_links.remove_links(parg)
			end
		else
			fixed_pargs[key] = m_links.remove_links(parg)
		end
	end

	declinfo = export.autoinflect(pattern, fixed_pargs, pagetitle)
	return make_substitutable_table(pargs, declinfo, "linkify")
end

local function push_down_indices(tbl)
	local ret = {}
	for index, decl in ipairs(tbl) do
		for k, v in pairs(decl) do
			ret[k] = ret[k] or {}
			table.insert(ret[k], v)
		end
	end
	return ret
end

-- Generate declension table for a noun phrase
function export.template_decl_phrase(frame)
	local args = frame.args
	local pargs = frame:getParent().args
	local page_title = mw.title.getCurrentTitle().fullText
	local lemma = pargs["lemma"] or page_title
	local words = {}

	if get_mode() == "demo" then
		pargs = { "adj", "n", "adj" }
		lemma = "przykładowe wyrażenie rzeczownikowe"
	end

	-- split title into words
	for t in mw.ustring.gmatch(lemma, "[^ ]+") do
		table.insert(words, t)
	end

	local word_args = {}
	for index, word in ipairs(words) do
		-- parse arguments to declension patterns
		word_args[index] = {}
		local numkey = 1
		local arg = pargs[index] or ""
		for i in mw.ustring.gmatch(arg, "[^!]+") do
			local param = mw.text.trim(i)
			local k, v = mw.ustring.match(param, "^([^:]*):(.*)$")
			if k and v then
				word_args[index][k] = v
			else
				word_args[index][numkey] = param
				numkey = numkey + 1
			end
		end
	end

	local adj_gender = nil
	for index, word in ipairs(words) do
		-- find the first gendered pattern and use it to match adjectives
		if not adj_gender then
			adj_gender = pattern_gender(word_args[index][1])
		end
	end

	local result = {}
	local result_raw = {}

	for index, word in ipairs(words) do
		local word_result = {}
		local pattern = table.remove(word_args[index], 1)
		if pattern == "adj" then
			if adj_gender then
				pattern = "adj-" .. adj_gender
			else
				error("Unable to guess adjective gender")
			end
		end
		if not patterns[pattern] then
			-- indeclinable
			pattern = "indec"
		end
		local nolinks = word_args[index].nolinks or pargs.linkfull
		result_raw[index] = export.autoinflect(pattern, word_args[index], word)
		result[index] = linkify_info(result_raw[index], altsep, nolinks)
	end

	-- rearrange the table so that word indices are at the lower level
	result_raw = push_down_indices(result_raw)
	result = push_down_indices(result)

	-- make table entries
	for case, v in pairs(result_raw) do
		local phrase = table.concat(v, " ")
		if phrase == page_title then
			-- bold self-link if the phrase is equal to the page title
			result[case] = "[[" .. page_title .. "]]"
		else
			result[case] = table.concat(result[case], " ")
			if pargs.linkfull then
				-- do not split if linking full phrase declensions
				result[case] = export.make_links(result[case], nil)
			end
		end
	end

	-- width determination
	if not pargs.width then
		local result_concat = {}
		for case, v in pairs(result_raw) do
			result_concat[case] = table.concat(v, " ")
		end
		pargs.width = guess_width(result_concat, noun_cols)
	end

	-- disable linkifying, since we already made per-word links
	return make_substitutable_table(pargs, result, nil)
end

return export

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.