Module:fi-nominals

Hello, you have come here looking for the meaning of the word Module:fi-nominals. In DICTIOUS you will not only get to know all the dictionary meanings for the word Module:fi-nominals, but we will also tell you about its etymology, its characteristics and you will know how to say Module:fi-nominals in singular and plural. Everything you need to know about the word Module:fi-nominals you have here. The definition of the word Module:fi-nominals will help you to be more precise and correct when speaking or writing your texts. Knowing the definition ofModule:fi-nominals, as well as those of other words, enriches your vocabulary and provides you with more and better linguistic resources.

This module is used for the various Finnish nominal inflection tables.


local export = {}

local lang = require("Module:languages").getByCode("fi")

-- Functions that do the actual inflecting by creating the forms of a basic term.
local inflections = {}

local kotus_grad_type = {
	 = "A",
	 = "B",
	 = "C",
	 = "D",
	 = "E",
	 = "F",
	 = "G",
	 = "H",
	 = "I",
	 = "J",
	 = "K",
	 = "L",
	 = "M"
}

local m_bit32 -- loaded later if needed

local RARE = '<span class="narrow-space"> </span><sup>rare</sup>'

local function normalize_apostrophes(term, link_target)
	if link_target then
		if term and mw.ustring.find(term, "’") then
			term = mw.ustring.gsub(term, "’", "'")
		end
	else
		if term and mw.ustring.find(term, "'") then
			term = mw.ustring.gsub(term, "'", "’")
		end
	end
	return term
end


-- Creates a link to a form.
local function make_link(term, accel_form)
	-- do not link inflected forms of suffixes
	if term:match("^-") then
		if term == mw.title.getCurrentTitle().fullText then
			return '<span class="Latn" lang="fi"><strong class="selflink">' .. term .. '</strong></span>'
		end

		return '<span class="Latn" lang="fi">' .. term .. '</span>'
	end
	
	-- if there is something difficult, use full module.
	if term:find(":") or term:find("<") then
		if term:find(":") then term = mw.ustring.gsub(term, ":", "\\:") end
		
		return require("Module:links").full_link({
			lang = lang,
			term = term,
			accel = accel_form and ({ form = accel_form }) or nil
		})
	end
	
	-- otherwise, we can save a ton of memory by doing this manually.
	local target = normalize_apostrophes(term, true)
	
	if target == mw.title.getCurrentTitle().fullText then
		return '<span class="Latn" lang="fi"><strong class="selflink">' .. term .. '</strong></span>'
	end
	
	if not accel_form then
		return '<span class="Latn" lang="fi">]</span>'
	end
	
	return '<span class="Latn form-of lang-fi ' .. accel_form .. '-form-of" lang="fi">]</span>'
end

local function tag_term(term)
	-- return require("Module:script utilities").tag_text(term, lang, nil, "term")
	return '<i class="Latn mention" lang="fi">' .. term .. '</i>'
end

local function do_inflection_internal(data, argobj)
	local args = argobj.args
	argobj.pos = 1

	data.words = {}
	data.num = 1
	data.forms = nil
	data.categories = {}
	
	for num, infl_type in ipairs(data.infl_types) do
		-- initialize data for single word
		local word_data = {forms = {}, title = nil, categories = {}}
		
		-- word index
		word_data.num = num
		data.num = num
		
		-- Generate the forms
		inflections(argobj, word_data)
		postprocess_word(argobj, word_data, data, num == #data.infl_types)
		word_data.class = infl_type
		data.words = word_data
	end

	if #data.words > 1 then
		-- join the inflected word components
		export.join_words(data, function (n) return args or args or " " end)
	else
		data.vh = data.words.vh
		data.forms = data.words.forms
		data.title = data.words.title
		data.categories = data.words.categories
	end
	
	-- Postprocess
	postprocess(args, data)
end

-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
	local infl_type = frame.args or error("Inflection type has not been specified. Please pass parameter 1 to the module invocation")
	local args = frame:getParent().args
	local infl_types = {infl_type}
	
	infl_types = mw.text.split(infl_type, "%-")
	
	for _, type in ipairs(infl_types) do
		if not inflections then
			error("Unknown inflection type '" .. infl_type .. "'")
		end
	end
	
	local pos = args; if not pos or pos == "" then pos = "noun" end
	local allow_possessive = (pos == "noun" or pos == "adj") and not args
	
	-- initialize data for full inflection process
	local data = {
		pagename = mw.title.getCurrentTitle().text,
		infl_types = infl_types,
		poss = args
	}
	local argobj = {args = args}

	if args and mw.title.getCurrentTitle().namespace > 0 then
		data.pagename = args.title
	elseif data.pagename:find("^Unsupported titles/") then
		data.pagename = data.pagename:gsub("^Unsupported titles/", "")
	end
	
	data.pagename = normalize_apostrophes(data.pagename)

	do_inflection_internal(data, argobj)

	--if args then table.insert(data.categories, "fi-decl with type") end
	--if args then table.insert(data.categories, "fi-decl with nocheck") end
	--if args then table.insert(data.categories, "fi-decl with nosg") end
	--if args then table.insert(data.categories, "fi-decl with nopl") end
	
	local function get_poss_forms(poss)
		data.poss = poss
		do_inflection_internal(data, argobj)
		return data
	end

	local categories
	if args then
		categories = ""
	else
		categories = require("Module:utilities").format_categories(data.categories, lang)
	end

	return make_table(data, true)
	    .. (allow_possessive and make_possessive_table(data.pagename, args, pos, data.title, get_poss_forms) or "")
	    .. categories
		.. require("Module:TemplateStyles")("Module:fi-nominals/style.css")
end

local function args_get_required(args, i, purpose)
	local v = args
	if not v then
		error(purpose .. " (parameter " .. i .. ") may not be omitted.")
	end
	return v
end

local function args_get_vowel_harmony(args, i)
	local v = args
	if not v or not mw.ustring.match(v, "^$") then
		error("Vowel harmony (parameter " .. i .. ") must be \"a\" or \"ä\".")
	end
	return v
end

function get_params(argobj, num, invert_grades)
	local params = {}
	local args = argobj.args
	local pos = argobj.pos

	params.base = normalize_apostrophes(args)
	if num >= 2 then
		if num >= 4 then
			params.strong = normalize_apostrophes(args_get_required(args, pos + 1, "Nominative grade"))
			params.weak = normalize_apostrophes(args_get_required(args, pos + 2, "Genitive grade"))
	
			-- Swap the grades
			if invert_grades then
				params.strong, params.weak = params.weak, params.strong
			end
		end

		if num >= 5 then
			params.final = args_get_required(args, pos + 3, "Final letter(s)")
		end

		params.a = args_get_vowel_harmony(args, pos + num - 1)
	else
		params.base = params.base or ""
	end
	
	if params.a then
		params.o = params.a == "ä" and "ö" or "o"
		params.u = params.a == "ä" and "y" or "u"
	end
	
	argobj.pos = argobj.pos + num
	return params	
end

function get_extra_arg(argobj, wdata, name, fallback)
	return argobj.args or argobj.args
end

--[=[
	Inflection functions
]=]--

local stem_endings = {}

stem_endings = {
	 = "",
}

stem_endings = {
	 = "na",
}

stem_endings = {
	 = "n",
	 = "ssa",
	 = "sta",
	 = "lla",
	 = "lta",
	 = "lle",
	 = "ksi",
	 = "n",
	 = "tta",
	 = "t",
}

stem_endings = {
	 = "a",
}

stem_endings = {
	 = "Vn",
}

stem_endings = {
	 = "na",
	 = "ne",
}

stem_endings = {
	 = "ssa",
	 = "sta",
	 = "lla",
	 = "lta",
	 = "lle",
	 = "ksi",
	 = "n",
	 = "tta",
}

stem_endings = {
	 = "a",
}

stem_endings = {
	 = "en",
}

stem_endings = {
	 = "Vn",
}

-- Make a copy of the endings, with front vowels
stem_endings = { = stem_endings,  = mw.clone(stem_endings)}

for stem_key, endings in pairs(stem_endings) do
	for key, ending in pairs(endings) do
		endings = mw.ustring.gsub(endings, "()", { = "ä",  = "ö",  = "y"})
	end
end

-- data for generating possessive forms
-- suffixes per person
local poss_forms = { = "ni",  = "si",  = "nsa",  = "mme",  = "nne"}
local poss_alt = {-- = false,  = false,  = false,  = false,  = false,
					 = true, -- shorter form -Vn 
					}
-- which forms allow -nsa > -Vn?
local forms_alt_ok = {
	 = false,  = false,
	 = false,  = true,
	 = true,  = true,
	 = true,  = true,
	 = false,  = false,
	 = true,  = true,
	 = true,  = true,
	 = true,  = true,
	 = true,  = true,
	 = true,  = true,
	 = false,  = false,
	 = true,  = true,
	 = true,  = true,
}
-- which forms end in -n?
-- (in which case it is dropped before the possessive suffix)
local forms_gen_ok = {
	 = true,  = true,
	 = true,  = true,
	 = true,  = true,
}

local function feed_list(outputs, inputs)
	for key, values in pairs(inputs) do
		outputs = outputs or {}
		for _, value in ipairs(values) do
			table.insert(outputs, value)
		end
	end
end

local function process_stems(data, stems, vh)
	-- Create any stems that were not given
	stems = stems or mw.clone(stems)
	stems = stems or mw.clone(stems)
	stems = stems or mw.clone(stems)
	stems = stems or mw.clone(stems)
	
	if not stems and stems then
		stems = {}
		
		for _, stem in ipairs(stems) do
			-- If the stem ends in a long vowel or diphthong, then add -h
			if mw.ustring.find(stem, "()%1$") or mw.ustring.find(stem, "$") then
				table.insert(stems, stem .. "h")
			else
				table.insert(stems, stem)
			end
		end
	end
	
	if not stems and stems then
		stems = {}
		
		for _, stem in ipairs(stems) do
			-- If the partitive plural stem ends in -it, then replace the t with d or tt
			if mw.ustring.find(stem, "it$") then
				table.insert(stems, (mw.ustring.gsub(stem, "t$", "d")))
				table.insert(stems, stem .. "t")
			else
				table.insert(stems, stem)
			end
		end
	end

	-- Create forms based on each stem, by adding endings to it
	stems = stems or mw.clone(stems)
	stems = stems or mw.clone(stems)
	
	-- Go through each of the stems given
	for stem_key, substems in pairs(stems) do
		for _, stem in ipairs(substems) do
			-- Attach the endings to the stem
			for form_key, ending in pairs(stem_endings) do
				if not data.forms then
					data.forms = {}
				end
				
				-- "V" is a copy of the last vowel in the stem
				if mw.ustring.find(ending, "V") then
					local vowel = mw.ustring.match(stem, "()*$")
					ending = mw.ustring.gsub(ending, "V", vowel or "?")
				end
				
				table.insert(data.forms, stem .. ending)
			end
		end
	end
	
	data = stems
	data = vh
end


local function make_kotus_title_number(type_number)
	return "] type " .. type_number
end


local function make_kotus_title_word(reference_word)
	return '/<span lang="fi" class="Latn">]</span>'
end


local function make_kotus_title(number, reference_word)
	return make_kotus_title_number(number) .. make_kotus_title_word(reference_word)
end


local function inflection_type_is(data, number, reference_word, strong, weak)
	local title = make_kotus_title_number(number)
	local has_gradation = strong and strong ~= weak

	if has_gradation then
		local letter = kotus_grad_type
		if letter then
			title = title .. "*" .. letter
		else
			title = title .. "*"
		end
	end

	title = title .. make_kotus_title_word(reference_word)

	if has_gradation then
		local EMPTY = "<small>∅</small>"
		local function format(grade)
			if grade == "" then
				return EMPTY
			else
				return "''" .. grade .. "''"
			end
		end
		title = title .. ", " .. format(strong) .. "-" .. format(weak) .. " gradation"
	else
		title = title .. ", no gradation"
	end

	data.title = title

	table.insert(data.categories, "Finnish " .. reference_word .. "-type nominals")
end


--[=[
	Inflection types
]=]--


inflections = function(args, data)
	local params = get_params(args, 5)
	
	local wk_sg = params.weak
	local wk_pl = params.weak
	
	if mw.ustring.sub(params.base, -1) == params.final then
		if wk_sg == "" and (mw.ustring.find(params.base, "$") or mw.ustring.find(params.base, "$")) then
			wk_sg = "’"
		end
		
		if wk_pl == "" then
			wk_pl = "’"
		end
	end
	
	local stems = {}
	stems      = {params.base .. params.strong .. params.final}
	stems = {params.base .. wk_sg .. params.final}
	stems      = {params.base .. params.strong .. params.final .. "i"}
	stems = {params.base .. wk_pl .. params.final .. "i"}
	stems  = {params.base .. params.strong .. params.final .. "j"}
	stems  = {params.base .. params.strong .. params.final .. "ih"}
	
	inflection_type_is(data, 1, "valo", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems      = {params.base}
	stems      = {params.base .. "i"}
	stems  = {params.base .. "j", params.base .. "it"}
	stems  = {params.base .. "ih"}
	
	inflection_type_is(data, 2, "palvelu")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	
	local stems = {}
	stems      = {params.base}
	stems  = {params.base .. "t"}
	stems      = {params.base .. "i"}
	stems  = {params.base .. "it"}
	stems  = {params.base .. "ih"}
	
	inflection_type_is(data, 3, "valtio")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 5, false, "kk", "k", "o")
	
	local stems = {}
	stems      = {params.base .. params.strong .. params.final}
	stems = {params.base .. params.weak .. params.final}
	stems      = {params.base .. params.strong .. params.final .. "i"}
	stems = {params.base .. params.weak .. params.final .. "i"}
	stems  = {params.base .. params.strong .. params.final .. "j", params.base .. params.weak .. params.final .. "it"}
	stems  = {params.base .. params.strong .. params.final .. "ih", params.base .. params.weak .. params.final .. "ih"}
	
	inflection_type_is(data, 4, "laatikko", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 4)
	local i = get_extra_arg(args, data, "i"); if i == "0" then i = "" else i = "i" end

	local stems = {}
	stems  = {params.base .. params.strong .. i}
	stems      = {params.base .. params.strong .. "i"}
	stems = {params.base .. params.weak .. "i"}
	stems      = {params.base .. params.strong .. "ei"}
	stems = {params.base .. params.weak .. "ei"}
	stems  = {params.base .. params.strong .. "i"}
	stems  = {params.base .. params.strong .. "ej"}
	stems  = {params.base .. params.strong .. "eih"}
	
	inflection_type_is(data, 5, "risti", params.strong, params.weak)	
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local i = get_extra_arg(args, data, "i"); if i == "0" then i = "" else i = "i" end
	
	local stems = {}
	stems  = {params.base .. i}
	stems      = {params.base .. "i"}
	stems      = {params.base .. "ei"}
	stems  = {params.base .. "eit", params.base .. "ej"}
	stems  = {params.base .. "i", params.base .. "eid", params.base .. "eitt"}
	stems  = {params.base .. "eih"}
	
	inflection_type_is(data, 6, "paperi")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 4)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local wk_pl = params.weak
	if mw.ustring.sub(params.base, -1) == "i" and params.strong == "k" and params.weak == "" then
		wk_pl = "’"
	end

	local stems = {}
	stems  = {nom_sg or params.base .. params.strong .. "i"}
	stems      = {params.base .. params.strong .. "e"}
	stems = {params.base .. params.weak .. "e"}
	stems      = {params.base .. params.strong .. "i"}
	stems = {params.base .. wk_pl .. "i"}
	
	inflection_type_is(data, 7, "ovi", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 4)
	
	local stems = {}
	stems      = {params.base .. params.strong .. "e"}
	stems = {params.base .. params.weak .. "e"}
	stems      = {params.base .. params.strong .. "ei"}
	stems = {params.base .. params.weak .. "ei"}
	stems  = {params.base .. params.strong .. "ej"}
	stems  = {params.base .. params.strong .. "eih"}
	
	inflection_type_is(data, 8, "nalle", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.strong .. "ein"}
end

inflections = function(args, data)
	local params = get_params(args, 4)
	local ain = get_extra_arg(args, data, "ain"); if ain == "" then ain = nil end
	
	local wk_sg = params.weak
	
	if wk_sg == "" and mw.ustring.sub(params.base, -2) == params.a .. params.a then
		wk_sg = "’"
	end
	
	local stems = {}
	stems      = {params.base .. params.strong .. params.a}
	stems = {params.base .. wk_sg .. params.a}
	stems      = {params.base .. params.strong .. params.o .. "i"}
	stems = {params.base .. params.weak .. params.o .. "i"}
	stems  = {params.base .. params.strong .. params.o .. "j"}
	stems  = {params.base .. params.strong .. params.o .. "ih"}
	
	inflection_type_is(data, 9, "kala", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	local ain_form = params.base .. params.strong .. params.a .. "in"
	if ain == "2" then
		table.insert(data.forms, 1, ain_form)
	elseif ain == "1" then
		table.insert(data.forms, ain_form)
	else
		data.forms.rare = {ain_form}
	end
end

inflections = function(args, data)
	local params = get_params(args, 4)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	local apo_pl = get_extra_arg(args, data, "apo_pl"); if apo_pl == "" then apo_pl = nil end
	local ain = get_extra_arg(args, data, "ain"); if ain == "" then ain = nil end
	
	local wk_sg = params.weak
	local wk_pl = params.weak
	
	if wk_sg == "" and mw.ustring.sub(params.base, -2) == params.a .. params.a then
		wk_sg = "’"
	end
	
	if wk_pl == "" and mw.ustring.sub(params.base, -1) == "i" then
		wk_pl = "’"
	end
	
	local stems = {}
	stems  = {nom_sg or params.base .. params.strong .. params.a}
	stems      = {params.base .. params.strong .. params.a}
	stems = {params.base .. wk_sg .. params.a}
	stems      = {params.base .. params.strong .. (apo_pl and "'" or "") .. "i"}
	stems = {params.base .. wk_pl .. (apo_pl and "'" or "") .. "i"}
	
	inflection_type_is(data, 10, "koira", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	local ain_form = params.base .. params.strong .. params.a .. "in"
	if ain == "2" then
		table.insert(data.forms, 1, ain_form)
	elseif ain == "1" then
		table.insert(data.forms, ain_form)
	else
		data.forms.rare = {ain_form}
	end
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems      = {params.base .. params.a}
	stems      = {params.base .. params.o .. "i", params.base .. "i"}
	stems  = {params.base .. "i", params.base .. params.o .. "it"}
	stems  = {params.base .. "i", params.base .. params.o .. "ih"}
	
	inflection_type_is(data, 11, "omena")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.o .. "jen", params.base .. params.a .. "in"}
	data.forms.rare = {params.base .. params.o .. "j" .. params.a}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems      = {params.base .. params.a}
	stems      = {params.base .. params.o .. "i"}
	stems  = {params.base .. params.o .. "it"}
	stems  = {params.base .. params.o .. "ih"}
	
	inflection_type_is(data, 12, "kulkija")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems      = {params.base .. params.a}
	stems      = {params.base .. params.o .. "i"}
	stems  = {params.base .. params.o .. "it", params.base .. params.o .. "j"}
	stems  = {params.base .. params.o .. "ih"}
	
	inflection_type_is(data, 13, "katiska")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 4)
	
	local stems = {}
	stems      = {params.base .. params.strong .. params.a}
	stems = {params.base .. params.weak .. params.a}
	stems      = {params.base .. params.strong .. params.o .. "i"}
	stems = {params.base .. params.weak .. params.o .. "i"}
	stems  = {params.base .. params.weak .. params.o .. "it", params.base .. params.strong .. params.o .. "j"}
	stems  = {params.base .. params.weak .. params.o .. "ih", params.base .. params.strong .. params.o .. "ih"}
	
	inflection_type_is(data, 14, "solakka", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.strong .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	
	local stems = {}
	stems      = {params.base .. params.a}
	stems  = {params.base .. params.a, params.base .. params.a .. "t"}
	stems      = {params.base .. "i"}
	stems  = {params.base .. "it"}
	stems  = {params.base .. "isi", params.base .. "ih"}
	
	inflection_type_is(data, 15, "korkea")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "mpi"}
	stems      = {params.base .. "mp" .. params.a}
	stems = {params.base .. "mm" .. params.a}
	stems      = {params.base .. "mpi"}
	stems = {params.base .. "mmi"}
	
	inflection_type_is(data, 16, "vanhempi", "mp", "mm")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "mp" .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	
	local stems = {}
	stems      = {params.base .. final}
	stems  = {params.base .. final .. "t"}
	stems  = {params.base .. final .. "se"}
	stems      = {params.base .. "i"}
	stems  = {params.base .. "it"}
	stems  = {params.base .. "isi"}
	
	inflection_type_is(data, 17, "vapaa")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "ihin"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local pl_stem = mw.ustring.sub(params.base, 1, -2)
	
	local stems = {}
	stems      = {params.base}
	stems  = {params.base .. "t"}
	stems      = {pl_stem .. "i"}
	stems  = {pl_stem .. "it"}
	stems  = {pl_stem .. "ih"}
	
	inflection_type_is(data, 18, "maa")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local final = mw.ustring.sub(params.base, -1)
	local stem = mw.ustring.sub(params.base, 1, -3)
	local plural
	if mw.ustring.sub(stem, -1) == final then
		plural = stem .. "-" .. final
	elseif mw.ustring.find(stem, "-$") and mw.ustring.sub(stem, -1) ~= final then
		plural = mw.ustring.sub(stem, 1, -2) .. final
	else
		plural = stem .. final
	end
	
	local stems = {}
	stems      = {params.base}
	stems  = {params.base .. "t"}
	stems  = {params.base .. "h"}
	stems      = {plural .. "i"}
	stems  = {plural .. "it"}
	stems  = {plural .. "ih"}
	
	inflection_type_is(data, 19, "suo")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	local pl_stem = mw.ustring.sub(params.base, 1, -2)
	
	local stems = {}
	stems  = {nom_sg or params.base}
	stems      = {params.base}
	stems  = {params.base .. "t"}
	stems  = {params.base .. "h", params.base .. "se"}
	stems      = {pl_stem .. "i"}
	stems  = {pl_stem .. "it"}
	stems  = {pl_stem .. "ih", pl_stem .. "isi"}
	
	inflection_type_is(data, 20, "filee")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local ill_sg_vowel = get_extra_arg(args, data, "ill_sg_vowel"); if ill_sg_vowel == "" then error("Parameter \"ill_sg_vowel=\" cannot be empty.") end
	local ill_sg_vowel2 = get_extra_arg(args, data, "ill_sg_vowel2"); if ill_sg_vowel2 == "" then error("Parameter \"ill_sg_vowel2=\" cannot be empty.") end
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems  = {nom_sg or params.base}
	stems      = {params.base}
	stems  = {params.base .. "t"}
	stems  = {params.base .. "h"}
	stems      = {params.base .. "i"}
	stems  = {params.base .. "it"}
	stems  = {params.base .. "ih"}
	
	inflection_type_is(data, 21, "rosé")
	process_stems(data, stems, params.a)
	
	if ill_sg_vowel then
		data.forms = {params.base .. "h" .. ill_sg_vowel .. "n"}
	end
	
	if ill_sg_vowel2 then
		table.insert(data.forms, params.base .. "h" .. ill_sg_vowel2 .. "n")
	end
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local ill_sg_vowel = get_extra_arg(args, data, "ill_sg_vowel"); if ill_sg_vowel == "" then error("Parameter \"ill_sg_vowel=\" is missing.") end
	local ill_sg_vowel2 = get_extra_arg(args, data, "ill_sg_vowel2"); if ill_sg_vowel2 == "" then error("Parameter \"ill_sg_vowel2=\" cannot be empty.") end
	
	local stems = {}
	stems  = {params.base}
	stems      = {params.base .. "’"}
	stems  = {params.base .. "’t"}
	stems      = {params.base .. "’i"}
	stems  = {params.base .. "’it"}
	stems  = {params.base .. "’ih"}
	
	inflection_type_is(data, 22, "parfait")
	process_stems(data, stems, params.a)
	
	data.forms = {params.base .. "’h" .. ill_sg_vowel .. "n"}
	
	if ill_sg_vowel2 then
		table.insert(data.forms, params.base .. "h" .. ill_sg_vowel2 .. "n")
	end
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "i"}
	stems      = {params.base .. "e"}
	stems  = {params.base .. "t"}
	stems      = {params.base .. "i"}
	
	inflection_type_is(data, 23, "tiili")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local par_sg_a = get_extra_arg(args, data, "par_sg_a"); if par_sg_a and par_sg_a ~= "a" and par_sg_a ~= "ä" then error("Parameter \"par_sg_a=\" must be \"a\" or \"ä\".") end
	
	local stems = {}
	stems  = {params.base .. "i"}
	stems      = {params.base .. "e"}
	stems  = {params.base .. "t"}
	stems      = {params.base .. "i"}
	stems  = {params.base .. "i", params.base .. "t"}
	
	inflection_type_is(data, 24, "uni")
	process_stems(data, stems, params.a)
	
	if par_sg_a then
		data.forms = {}
		
		for _, stem in ipairs(stems) do
			table.insert(data.forms, stem .. par_sg_a)
		end
	end
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "mi"}
	stems      = {params.base .. "me"}
	stems  = {params.base .. "nt", params.base .. "me"}
	stems      = {params.base .. "mi"}
	stems  = {params.base .. "mi", params.base .. "nt"}
	
	inflection_type_is(data, 25, "toimi")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local par_sg_a = get_extra_arg(args, data, "par_sg_a"); if par_sg_a and par_sg_a ~= "a" and par_sg_a ~= "ä" then error("Parameter \"par_sg_a=\" must be \"a\" or \"ä\".") end
	
	local stems = {}
	stems  = {params.base .. "i"}
	stems      = {params.base .. "e"}
	stems  = {params.base .. "t"}
	stems      = {params.base .. "i"}
	stems  = {params.base .. "t", params.base .. "i"}
	
	inflection_type_is(data, 26, "pieni")
	process_stems(data, stems, params.a)
	
	if par_sg_a then
		data.forms = {}
		
		for _, stem in ipairs(stems) do
			table.insert(data.forms, stem .. par_sg_a)
		end
	end
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "si"}
	stems      = {params.base .. "te"}
	stems = {params.base .. "de"}
	stems  = {params.base .. "tt"}
	stems      = {params.base .. "si"}
	
	inflection_type_is(data, 27, "käsi", "t", "d")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "tten"}
end

inflections = function(args, data)
	local params = get_params(args, 2, false, "n")
	local cons = mw.ustring.match(params.base, "$")
	
	if not cons then error("Stem must end in \"l\", \"n\" or \"r\".") end
	
	local stems = {}
	stems  = {params.base .. "si"}
	stems      = {params.base .. "te"}
	stems = {params.base .. cons .. "e"}
	stems  = {params.base .. "tt"}
	stems      = {params.base .. "si"}
	
	inflection_type_is(data, 28, "kynsi", cons .. "t", cons .. cons)
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "tten"}
end

inflections = function(args, data)
	local params = get_params(args, 2, false, "p")
	if not mw.ustring.match(params.base, "$") then error("Stem must end in \"k\" or \"p\".") end
	local syncopated_stem = mw.ustring.sub(params.base, 1, -2)
	
	local stems = {}
	stems  = {params.base .. "si"}
	stems      = {params.base .. "se"}
	stems  = {syncopated_stem .. "st"}
	stems      = {params.base .. "si"}
	stems  = {params.base .. "si", syncopated_stem .. "st"}
	
	inflection_type_is(data, 29, "lapsi")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "tsi"}
	stems      = {params.base .. "tse"}
	stems  = {params.base .. "st"}
	stems      = {params.base .. "tsi"}
	
	inflection_type_is(data, 30, "veitsi")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "sten"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "ksi"}
	stems      = {params.base .. "hte"}
	stems = {params.base .. "hde"}
	stems  = {params.base .. "ht"}
	stems      = {params.base .. "ksi"}
	
	inflection_type_is(data, 31, "kaksi", "t", "d")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 5, true)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems  = {nom_sg or params.base .. params.weak .. params.final}
	stems      = {params.base .. params.strong .. params.final .. "e"}
	stems  = {params.base .. params.weak .. params.final .. "t"}
	stems      = {params.base .. params.strong .. params.final .. "i"}
	stems  = {params.base .. params.strong .. params.final .. "i", params.base .. params.weak .. params.final .. "t"}
	
	inflection_type_is(data, 32, "sisar", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 5, true)
	
	local stems = {}
	stems  = {params.base .. params.weak .. params.final .. "n"}
	stems      = {params.base .. params.strong .. params.final .. "me"}
	stems  = {params.base .. params.weak .. params.final .. "nt"}
	stems      = {params.base .. params.strong .. params.final .. "mi"}
	stems  = {params.base .. params.strong .. params.final .. "mi", params.base .. params.weak .. params.final .. "nt"}
	
	inflection_type_is(data, 33, "kytkin", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local no_gradation = get_extra_arg(args, data, "no_tt") == "1"
	local strong
	if no_gradation then
		strong = "t"
	else
		strong = "tt"
	end
	
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "t" .. params.o .. "n"}
	stems      = {params.base .. strong .. params.o .. "m" .. params.a}
	stems  = {params.base .. "t" .. params.o .. "nt"}
	stems      = {params.base .. strong .. params.o .. "mi"}
	
	inflection_type_is(data, 34, "onneton", strong, "t")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "t" .. params.o .. "nten"}
end

inflections = function(args, data)
	local params = get_params(args, 5, true)
	
	local stems = {}
	stems  = {params.base .. params.weak .. params.final .. "n"}
	stems      = {params.base .. params.strong .. params.final .. "m" .. params.a}
	stems  = {params.base .. params.weak .. params.final .. "nt"}
	stems      = {params.base .. params.strong .. params.final .. "mi"}
	
	inflection_type_is(data, 35, "lämmin", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.strong .. params.final .. "m" .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "in"}
	stems      = {params.base .. "imp" .. params.a}
	stems = {params.base .. "imm" .. params.a}
	stems  = {params.base .. "int"}
	stems      = {params.base .. "impi"}
	stems = {params.base .. "immi"}
	stems  = {params.base .. "impi", params.base .. "int"}
	
	inflection_type_is(data, 36, "sisin", "mp", "mm")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "imp" .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 1)
	params.base = params.base .. "vase"
	params.a = "a"
	
	local stems = {}
	stems  = {params.base .. "n"}
	stems      = {params.base .. "mp" .. params.a}
	stems = {params.base .. "mm" .. params.a}
	stems  = {params.base .. "nt", params.base .. "mp" .. params.a}
	stems      = {params.base .. "mpi"}
	stems = {params.base .. "mmi"}
	stems  = {params.base .. "mpi", params.base .. "nt"}
	
	inflection_type_is(data, 37, "vasen", "mp", "mm")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "mp" .. params.a .. "in"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "nen"}
	stems      = {params.base .. "se"}
	stems  = {params.base .. "st"}
	stems      = {params.base .. "si"}
	stems  = {params.base .. "st", params.base .. "si"}
	
	inflection_type_is(data, 38, "nainen")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems  = {nom_sg or params.base .. "s"}
	stems      = {params.base .. "kse"}
	stems  = {params.base .. "st"}
	stems      = {params.base .. "ksi"}
	stems  = {params.base .. "st", params.base .. "ksi"}
	
	inflection_type_is(data, 39, "vastaus")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "s"}
	stems      = {params.base .. "te"}
	stems = {params.base .. "de"}
	stems  = {params.base .. "tt"}
	stems      = {params.base .. "ksi"}
	
	inflection_type_is(data, 40, "kalleus", "t", "d")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 5, true)
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems  = {nom_sg or (params.base .. params.weak .. params.final .. "s")}
	stems      = {params.base .. params.strong .. params.final .. params.final}
	stems  = {params.base .. params.weak .. params.final .. "st"}
	stems  = {params.base .. params.strong .. params.final .. params.final .. "se"}
	stems      = {params.base .. params.strong .. params.final .. "i"}
	stems  = {params.base .. params.strong .. params.final .. "it"}
	stems  = {params.base .. params.strong .. params.final .. "isi"}
	
	inflection_type_is(data, 41, "vieras", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.weak .. params.final .. "sten"}
	data.forms.rare = {params.base .. params.strong .. params.final .. "ihin"}
end

inflections = function(args, data)
	local params = get_params(args, 1)
	local cap = get_extra_arg(args, data, "cap"); if cap == "" then nom_sg = nil end
	params.base = params.base .. (cap and "Mie" or "mie")
	params.a = "ä"
	
	local stems = {}
	stems  = {params.base .. "s"}
	stems      = {params.base .. "he"}
	stems  = {params.base .. "st"}
	stems      = {params.base .. "hi"}
	stems  = {params.base .. "st", params.base .. "hi"}
	
	inflection_type_is(data, 42, "mies")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 5, true)
	
	local stems = {}
	stems  = {params.base .. params.weak .. params.final .. "t"}
	stems      = {params.base .. params.strong .. params.final .. "e"}
	stems  = {params.base .. params.weak .. params.final .. "tt"}
	stems      = {params.base .. params.strong .. params.final .. "i"}
	stems  = {params.base .. params.strong .. params.final .. "it"}
	stems  = {params.base .. params.strong .. params.final .. "isi", params.base .. params.strong .. params.final .. "ih"}
	
	inflection_type_is(data, 43, "ohut", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 4, true)
	local vowel = params.a
	
	local stems = {}
	stems  = {params.base .. params.weak .. vowel .. "t"}
	stems      = {params.base .. params.strong .. vowel .. vowel}
	stems  = {params.base .. params.weak .. vowel .. "tt"}
	stems  = {params.base .. params.strong .. vowel .. vowel .. "se"}
	stems      = {params.base .. params.strong .. vowel .. "i"}
	stems  = {params.base .. params.strong .. vowel .. "it"}
	stems  = {params.base .. params.strong .. vowel .. "isi"}
	
	inflection_type_is(data, 44, "kevät", params.strong, params.weak)
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. params.strong .. vowel .. "ihin"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "s"}
	stems      = {params.base .. "nte"}
	stems = {params.base .. "nne"}
	stems  = {params.base .. "tt"}
	stems      = {params.base .. "nsi"}
	
	inflection_type_is(data, 45, "kahdeksas", "nt", "nn")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. "t"}
	stems      = {params.base .. "nte"}
	stems = {params.base .. "nne"}
	stems  = {params.base .. "tt"}
	stems      = {params.base .. "nsi"}

	inflection_type_is(data, 46, "tuhat", "nt", "nn")
	process_stems(data, stems, params.a)
	
	data.forms.rare = {params.base .. "nten"}
end

inflections = function(args, data)
	local params = get_params(args, 2)
	
	local stems = {}
	stems  = {params.base .. params.u .. "t"}
	stems      = {params.base .. "ee"}
	stems  = {params.base .. params.u .. "tt"}
	stems  = {params.base .. "eese"}
	stems      = {params.base .. "ei"}
	stems  = {params.base .. "eit"}
	stems  = {params.base .. "eisi", params.base .. "eih"}
	
	inflection_type_is(data, 47, "kuollut")
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 4, true)
	local stem_vowel = get_extra_arg(args, data, "stem_vowel"); if stem_vowel == "" then stem_vowel = nil end
	stem_vowel = stem_vowel or "e"
	local nom_sg = get_extra_arg(args, data, "nom_sg"); if nom_sg == "" then nom_sg = nil end
	
	local stems = {}
	stems  = {nom_sg or (params.base .. params.weak .. stem_vowel)}
	stems      = {params.base .. params.strong .. stem_vowel .. stem_vowel}
	stems  = {params.base .. params.weak .. stem_vowel .. "tt"}
	stems  = {params.base .. params.strong .. stem_vowel .. stem_vowel .. "se"}
	stems      = {params.base .. params.strong .. stem_vowel .. "i"}
	stems  = {params.base .. params.strong .. stem_vowel .. "it"}
	stems  = {params.base .. params.strong .. stem_vowel .. "isi", params.base .. params.strong .. stem_vowel .. "ih"}
	
	inflection_type_is(data, 48, "hame", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

inflections = function(args, data)
	local params = get_params(args, 5, true)
	local prefer_hame = get_extra_arg(args, data, "e") == "1"
	
	local stems_sisar = {}
	stems_sisar      = {params.base .. params.strong .. params.final .. "e"}
	stems_sisar  = {params.base .. params.weak .. params.final}
	stems_sisar  = {params.base .. params.weak .. params.final .. "t"}
	stems_sisar  = {params.base .. params.strong .. params.final .. "e"}
	stems_sisar      = {params.base .. params.strong .. params.final .. "i"}
	stems_sisar  = {params.base .. params.strong .. params.final .. "i", params.base .. params.weak .. params.final .. "t"}
	stems_sisar  = {params.base .. params.strong .. params.final .. "i"}
	stems_sisar  = {params.base .. params.strong .. params.final .. "i"}

	local stems_hame = {}
	stems_hame      = {params.base .. params.strong .. params.final .. "ee"}
	stems_hame  = {params.base .. params.strong .. params.final .. "e"}
	stems_hame  = {params.base .. params.strong .. params.final .. "ett"}
	stems_hame  = {params.base .. params.strong .. params.final .. "eese"}
	stems_hame      = {params.base .. params.strong .. params.final .. "ei"}
	stems_hame  = {params.base .. params.strong .. params.final .. "eid", params.base .. params.strong .. params.final .. "eitt"}
	stems_hame  = {params.base .. params.strong .. params.final .. "eit"}
	stems_hame  = {params.base .. params.strong .. params.final .. "eisi", params.base .. params.strong .. params.final .. "eih"}

	local stems = {}
	if prefer_hame then
		feed_list(stems, stems_hame)
		feed_list(stems, stems_sisar)
	else
		feed_list(stems, stems_sisar)
		feed_list(stems, stems_hame)
	end

	inflection_type_is(data, 49, "askel", params.strong, params.weak)
	process_stems(data, stems, params.a)
end

-- Helper functions

-- joins data.words.forms to data.forms
function export.join_words(data, sep_supplier)
	local reorganized = {}
	local classes = {}
	
	-- reorganize from words.forms(.rare) to forms,words(.rare)
	for windex, word in ipairs(data.words) do
		table.insert(classes, word.class)
		for case, forms in pairs(word.forms) do
			if reorganized == nil then
				reorganized = {}
			end
			reorganized = {}
			for _, form in ipairs(forms) do
				table.insert(reorganized, form)
			end
			if word.forms.rare then
				reorganized.rare = {}
				for _, form in ipairs(word.forms.rare) do
					table.insert(reorganized.rare, form)
				end
			end
		end
	end
	
	-- merge the forms with a Cartesian product to produce all possible combinations
	data.forms = {}
	for case, words in pairs(reorganized) do
		data.forms = forms_cart_product(words, case, sep_supplier, classes)
	end

	if #data.words <= 1 then
		-- use title and categories of the sole word if there is only one
		data.title = data.words.title
		data.categories = data.words.categories
	else
		-- if there are multiple words, force nuoripari type
		data.title = make_kotus_title(51, "nuoripari")
		data.categories = {"Finnish nuoripari-type nominals"}
	end
	data.words = nil
end

local function cleanup_suffix(suffix)
	if suffix and mw.ustring.find(suffix, "_") then
		return mw.ustring.gsub(suffix, "_", " ")
	else
		return suffix	
	end
end

-- computes the Cartesian product of tables
function cart_product(words, depth)
	depth = depth or 1
	local prod = {}
	
	for _, val in ipairs(words) do
		if depth < #words then
			-- go over the next list
			for _, res in ipairs(cart_product(words, depth + 1)) do
				table.insert(prod, { val, unpack(res) })
			end
		else
			-- end of list, simply return the original
			table.insert(prod, { val })
		end
	end
	
	return prod
end

local function supplied_concat(list, sep_supplier)
	local result = ""
	local n = #list
	if n >= 1 then
		for i = 1, n - 1 do
			local v = mw.ustring.match(list, "^")
			if v and mw.ustring.find(result, v .. "$") then
				result = result .. "-"
			end
			result = result .. list .. sep_supplier(i)
		end
		
		local v = mw.ustring.match(list, "^")
		if v and mw.ustring.find(result, v .. "$") then
			result = result .. "-"
		end
		
		result = result .. list
	end
	return result
end

-- computes the Cartesian product of tables, also concats
function cart_product_concat(words, sep_supplier)
	local res = {}

	for _, combination in ipairs(cart_product(words)) do
		table.insert(res, supplied_concat(combination, sep_supplier))
	end

	return res
end

-- returns a bit mask (!) or nil
function get_rhyming_pattern(word, case, class)
	if class == "askel" then
		return nil
	end
	if case == "gen_pl" then
		if mw.ustring.match(word, "tten$") then
			return 2
		elseif mw.ustring.match(word, "ten$") then
			return 3
		else
			return 1
		end
	elseif case == "ill_sg" then
		if mw.ustring.match(word, "seen$") then
			return 1
		elseif mw.ustring.match(word, "hen$") then
			return 2
		end
	elseif case == "ill_pl" then
		if mw.ustring.match(word, "siin$") then
			return 1
		elseif mw.ustring.match(word, "hin$") then
			return 2
		end
	end
	return nil -- not applicable
end

function is_nonrhyming(form, case, classes)
	local expected = m_bit32.bnot(0) -- -1
	
	for i, word in ipairs(form) do
		local got = get_rhyming_pattern(word, case, classes)
		if got then
			expected = m_bit32.band(expected, got)
		end
		if expected == 0 then
			return true
		end
	end

	return false
end

-- computes the Cartesian product of tables, also concats
-- returns non-rhyming combinations as rare
function cart_product_concat_nonrhyming_rare(words, case, sep_supplier, classes)
	local res = {}
	local rare = {}
	local multichoice = 0
	local allow_pruning = false

	for _, position in ipairs(words) do
		if #position > 1 then
			multichoice = multichoice + 1
		end
	end

	allow_pruning = multichoice > 1

	for _, combination in ipairs(cart_product(words)) do
		local item = supplied_concat(combination, sep_supplier)
		if is_nonrhyming(combination, case, classes) then
			table.insert(rare, item)
		else
			table.insert(res, item)
		end
	end

	if #res < 1 then
		rare.rare = {}
		return rare
	end

	res.rare = rare
	return res
end

-- converts a list of words to extract the rare forms for ipairs
-- the number is interpreted bit-by-bit to decide which combination
-- to choose
function prepare_rare_tables(words, code)
	local result = {}
	
	for _, forms in ipairs(words) do
		-- replace with rare if bit is 1
		if m_bit32.band(code, 1) == 1 then
			table.insert(result, forms.rare or {})
		else
			table.insert(result, forms)
		end
		
		-- shift right to test next bit
		code = m_bit32.rshift(code, 1)
	end
	
	return result
end

-- copies all entries of source and inserts them to target
function merge_table(target, source)
	for _, value in ipairs(source) do
		table.insert(target, value)
	end
end

function merge_table_rare(target, source)
	for _, value in ipairs(source) do
		table.insert(target, value)
	end
	target.rare = source.rare
end

-- the Cartesian product of possible forms
function forms_cart_product(words, case, sep_supplier, classes)
	local result = {}
	result.rare = {}

	m_bit32 = require("bit32")
	
	-- merge possible non-rare forms
	merge_table_rare(result, cart_product_concat_nonrhyming_rare(words, case, sep_supplier, classes))
	
	-- merge possible rare forms
	-- for example, with two words:
	--        1 = rare A, common B
	--        2 = common A, rare B
	--        3 = rare A, rare B
	-- (2 ^ #words) - 1 == m_bit32.lshift(1,#words)-1
	-- (prepare_rare_tables actually takes out the rare forms)
	for i = 1,m_bit32.lshift(1,#words)-1 do
		merge_table(result.rare, cart_product_concat(prepare_rare_tables(words, i), sep_supplier))
	end
	
	-- if no rare forms, remove the table completely
	if #result.rare < 1 then
		result.rare = nil
	end
	return result
end

function make_word_possessive(args, data, poss, always_add)
	local pos = get_extra_arg(args, data, "pos"); if not pos or pos == "" then pos = "noun" end
	local par_nom_sg = get_extra_arg(args, data, "par_nom_sg") == "1"

	-- "no possessive forms exist" sentinel value
	if poss == "-" then
		return data.forms
	end

	if always_add or pos == "noun" then
		-- add possessive suffix
		if poss == "3" or poss == "3p" then
			poss = "3s" -- 3rd person forms are identical
		end
		if not poss_forms then
			error("Invalid poss value: '" .. p .. "'")
		end
		return make_poss_with_suffix(data.forms, data.stems, poss_forms, "", poss_alt, par_nom_sg)
	end
	return data.forms
end

function postprocess_word(args, data, gdata, always_add)
	local pos = get_extra_arg(args, data, "pos"); if not pos or pos == "" then pos = "noun" end
	local alwayspl = get_extra_arg(args, data, "alwayspl"); if alwayspl == "" then alwayspl = nil end
	
	if gdata.poss then
		data.forms = make_word_possessive(args, data, gdata.poss, always_add)
	elseif pos == "noun" and data.forms then
		-- Add the possessive suffix to the comitative plural, if the word is a noun
		for key, subform in ipairs(data.forms) do
			data.forms = subform .. "en"
		end
		gdata.tagged_com_pl = true
	end
	
	if get_extra_arg(args, data, "gen_nom_sg") == "1" then
		data.forms = data.forms
	elseif get_extra_arg(args, data, "par_nom_sg") == "1" then
		data.forms = data.forms
	end
	
	if gdata.poss then
		data.forms = nil
	end

	if alwayspl then -- ]
		for k, v in pairs(data.forms) do
			local k_sg = k:gsub("_pl", "_sg")
			if data.forms then
				data.forms = data.forms
			end
		end
	end
end

function postprocess(args, data)
	local pos = args; if not pos or pos == "" then pos = "noun" end
	local nosg = args; if nosg == "" then nosg = nil end
	local nopl = args; if nopl == "" then nopl = nil end
	local n = args; if n == "" then n = nil end
	local suffix = cleanup_suffix(args); if suffix == "" then suffix = nil end
	local appendix = args; if appendix == "" then appendix = nil end
	local has_ins_sg = args; if has_ins_sg == "" then has_ins_sg = nil end
	local has_no_nom = args; if has_no_nom == "" then has_no_nom = nil end
	
	-- Add the possessive suffix to the comitative plural, if the word is a noun
	-- now done per word; see postprocess_word
	
	if nosg or n == "pl" then
		table.insert(data.categories, "Finnish pluralia tantum")
	end
	
	-- TODO: This says "nouns", but this module is also used for adjectives!
	if nopl or n == "sg" then
		table.insert(data.categories, "Finnish uncountable nouns")
	end
	
	if n == "csg" then          -- "chiefly singular"
		data.rare_plural = true
	end
	
	if not has_ins_sg then
		data.forms = nil
	end
	
	for key, form in pairs(data.forms) do
		-- Add suffix to forms
		for i, subform in ipairs(form) do
			subform = subform .. (suffix or "")
			form = subform
		end
		
		if form.rare then
			for i, subform in ipairs(form.rare) do
				subform = subform .. (suffix or "")
				form.rare = subform
			end
		end
		
		-- Do not show singular or plural forms for nominals that don't have them
		if ((nosg or n == "pl") and key:find("_sg$")) or ((nopl or n == "sg") and key:find("_pl$")) then
			form = nil
		end
		
		data.forms = form
	end
	
	-- Check if the lemma form matches the page name
	local lemma = data.forms
	if not appendix and lemma ~= data.pagename and not data.poss then
		--error("The lemma " .. lemma .. " does not match the page title. Check the parameters!")
		mw.addWarning("<i>Please check the fi-decl-... parameters!</i>")
		table.insert(data.categories, "Finnish entries with inflection not matching pagename")
	end
	
	data.is_appendix = appendix
	
	if has_no_nom then
		data.forms = nil
		data.forms = nil
	end
end

-- Make the table
function make_table(data, preview, title_override, collapse_class_override, accel_class_prefix, extra_classes)
	local rare_plural = data.rare_plural
	local note = rare_plural and "Plural forms of this word are not commonly used, but might be found in figurative uses, in some set phrases or in colloquial language." or nil

	local com_forms

	local function show_form(forms, code, no_rare)
		local form = forms
		if not form then
			return "&mdash;"
		elseif type(form) ~= "table" then
			error("a non-table value was given in the list of inflected forms.")
		end
		
		local ret = {}
		local accel

		if rare_plural and code:find("_pl$") then
			-- plural is marginal
			for key, subform in ipairs(form) do
				table.insert(ret, "(''" .. make_link(subform) .. "'')")
			end
			
			if not no_rare and form.rare then
				for key, subform in ipairs(form.rare) do
					table.insert(ret, "(''" .. make_link(subform) .. "''" .. RARE .. ")")
				end
			end

			return table.concat(ret, "<br/>")
		end

		-- See ].
		if code == "nom_sg" and not data.poss then
			accel = nil
		elseif code == "gen_sg" then
			accel = "gen//acc|s"
		elseif code == "nom_pl" then
			accel = "nom//acc|p"
		else
			accel = code:gsub("%f(%a%a)$", {sg = "s", pl = "p"}):gsub("ins", "ist"):gsub("_", "|")
		end
		
		if accel_class_prefix and accel then
			accel = accel_class_prefix .. accel
		end

		for key, subform in ipairs(form) do
			table.insert(ret, make_link(subform, accel))
		end
		
		if not no_rare and form.rare then
			if accel then
				accel = 'rare-' .. accel
			end

			for key, subform in ipairs(form.rare) do
				table.insert(ret, make_link(subform, accel) .. RARE)
			end
		end
		
		return table.concat(ret, "<br/>")
	end
	
	local function repl(param)
		if param == "title" then
			if title_override then return title_override end
			return "] of " .. (data.is_appendix and make_link(data.pagename) or tag_term(data.pagename)) .. ( data.title and " (" .. data.title .. ")" or "")
		elseif param == "maybenote" then
			if note then
				return [=[
|- class="vsHide"
| colspan="4" class="fi-decl-maybenote" | ]=] .. note .. "\n"
			else
				return ""
			end
		elseif param == "com_forms" then
			return com_forms
		else
			local param2 = mw.ustring.match(param, "^(.-):c$")
			
			if param2 then
				return show_form(data.forms, param2, true)
			else
				return show_form(data.forms, param)
			end
		end
	end

	if not data.poss and data.tagged_com_pl then
		com_forms = 'colspan="2" | \'\'See the possessive forms below.\'\''
	else
		com_forms = mw.ustring.gsub('{{{com_sg}}} || {{{com_pl}}}', "{{{(+)}}}", repl)
	end

	if preview then
		preview = [=[
|- class="vsShow"
! class="case-column" colspan="2" | nominative
| class="number-column" | {{{nom_sg:c}}}
| class="number-column" | {{{nom_pl:c}}}
|- class="vsShow"
! colspan="2" | genitive
| {{{gen_sg:c}}}
| {{{gen_pl:c}}}
|- class="vsShow"
! colspan="2" | partitive
| {{{par_sg:c}}}
| {{{par_pl:c}}}
|- class="vsShow"
! colspan="2" | illative
| {{{ill_sg:c}}}
| {{{ill_pl:c}}}

]=]
	else
		preview = ""
	end
	
	local wikicode = [=[
<div class="noresize">
{| class="inflection-table fi-decl vsSwitcher ]=] .. (extra_classes or "") .. =] .. (collapse_class_override or "declension") .. [=["
|-
! class="vsToggleElement" colspan="4" | {{{title}}}
]=] .. preview .. [=[
|- class="vsHide"
! class="case-column" colspan="2" |
! class="number-column" | singular
! class="number-column" | plural
|- class="vsHide"
! colspan="2" | nominative
| {{{nom_sg}}}
| {{{nom_pl}}}
|- class="vsHide"
! rowspan="2" | accusative
! <abbr title="The nominative accusative is used, for example, as the object of certain passives and imperatives. Click the Inflection link and see the section on accusatives for more information.">nom.</abbr>
| {{{nom_sg}}}
| rowspan="2" | {{{nom_pl}}}
|- class="vsHide"
! gen.
| {{{gen_sg}}}
|- class="vsHide"
! colspan="2" | genitive
| {{{gen_sg}}}
| {{{gen_pl}}}
|- class="vsHide"
! colspan="2" | partitive
| {{{par_sg}}}
| {{{par_pl}}}
|- class="vsHide"
! colspan="2" | inessive
| {{{ine_sg}}}
| {{{ine_pl}}}
|- class="vsHide"
! colspan="2" | elative
| {{{ela_sg}}}
| {{{ela_pl}}}
|- class="vsHide"
! colspan="2" | illative
| {{{ill_sg}}}
| {{{ill_pl}}}
|- class="vsHide"
! colspan="2" | adessive
| {{{ade_sg}}}
| {{{ade_pl}}}
|- class="vsHide"
! colspan="2" | ablative
| {{{abl_sg}}}
| {{{abl_pl}}}
|- class="vsHide"
! colspan="2" | allative
| {{{all_sg}}}
| {{{all_pl}}}
|- class="vsHide"
! colspan="2" | essive
| {{{ess_sg}}}
| {{{ess_pl}}}
|- class="vsHide"
! colspan="2" | translative
| {{{tra_sg}}}
| {{{tra_pl}}}
|- class="vsHide"
! colspan="2" | abessive
| {{{abe_sg}}}
| {{{abe_pl}}}
|- class="vsHide"
! colspan="2" | instructive
| {{{ins_sg}}}
| {{{ins_pl}}}
|- class="vsHide"
! colspan="2" | comitative
| {{{com_forms}}}
{{{maybenote}}}|}
</div>]=]
	return mw.ustring.gsub(wikicode, "{{{(+)}}}", repl)
end

------------------------------------------
-- POSSESSIVE FORM GENERATION & DISPLAY --
------------------------------------------

local function prepare_possessive_list(forms)
	local res = {}
	for _, v in ipairs(forms) do
		table.insert(res, v)
	end
	if forms then
		for _, v in ipairs(forms) do
			table.insert(res, v)
			res = "rare"
		end
	end
	return res
end

local function wrap_rare_forms(forms)
	local newforms = {}
	for case, subforms in pairs(forms) do
		local common = {}
		local rare = {}
		for _, v in ipairs(subforms) do
			if subforms == "rare" then
				table.insert(rare, v)
			else
				table.insert(common, v)
			end
		end
		common.rare = rare
		newforms = common
	end
	return newforms
end

local function make_possessives_from_stems(stems, suffix, extra_suffix)
	local pforms = {}
	for _, stem in pairs(stems) do
		table.insert(pforms, stem .. suffix .. extra_suffix)
	end
	return pforms
end

function make_poss_with_suffix(forms, stems, poss_suffix, extra_suffix, allow_alt, par_nom_sg)
	local result = {}
	local par_sg_a = false
	if poss_suffix:find("a") and mw.ustring.sub(forms, -1) == "ä" then
		poss_suffix = mw.ustring.gsub(poss_suffix, "a", "ä")
	end
	if mw.ustring.sub(forms, -1) ~= mw.ustring.sub(forms, -1) then
		par_sg_a = true
	end

	for k, v in pairs(forms_alt_ok) do
		if forms then
			local suffix = poss_suffix
			if k == "par_sg" and par_sg_a and mw.ustring.find(suffix, "ä$") then
				suffix = mw.ustring.gsub(poss_suffix, "ä", "a")
			end

			result = {}
			if k == "par_sg" and allow_alt then
				-- par_sg is a bit of an exception: it allows
				-- alt form if it doesn't end in two "aa"/"ää"
				local prepared = prepare_possessive_list(forms)
				for _, form in ipairs(prepared) do
					local modform = form
					if mw.ustring.sub(modform, -2, -2) ~= mw.ustring.sub(modform, -1) then
						local final = modform .. mw.ustring.sub(modform, -1) .. "n"
						table.insert(result, final)
						result = prepared
					end
				end
			elseif forms_alt_ok and allow_alt then
				local prepared = prepare_possessive_list(forms)
				for _, form in ipairs(prepared) do
					local modform = form
					if k == "tra_sg" or k == "tra_pl" then
						modform = mw.ustring.sub(form, 1, -2) .. "e"
					end
					local final = modform .. mw.ustring.sub(modform, -1) .. "n"
					table.insert(result, final)
					result = prepared
				end
			end
			
			if k == "gen_sg" or k == "ins_sg" then
				result = make_possessives_from_stems(stems, suffix, extra_suffix)
			elseif k == "ins_pl" then
				result = make_possessives_from_stems(stems, suffix, extra_suffix)
			elseif forms_gen_ok then
				local prepared = prepare_possessive_list(forms)
				for _, form in ipairs(prepared) do
					local tmp = form
					tmp = mw.ustring.sub(tmp, 1, -2)
					local final = tmp .. suffix .. extra_suffix
					table.insert(result, final)
					result = prepared
				end
			else
				local prepared = prepare_possessive_list(forms)
				for _, form in ipairs(prepared) do
					local modform = form
					if k == "tra_sg" or k == "tra_pl" then
						modform = mw.ustring.sub(form, 1, -2) .. "e"
					end
					local final = modform .. suffix .. extra_suffix
					table.insert(result, final)
					result = prepared
				end
			end
		end
	end

	-- nominative forms are (usually) identical to genitive singular
	result = result
	result = result
	return wrap_rare_forms(result)
end

local function serialize_args(args)
	local items = {}
	local entries = {}
	local max_number = 0

	for key, value in pairs(args) do
		if type(key) == "number" and key > 0 and key == math.floor(key) then
			items = value
			max_number = math.max(key, max_number)
		else
			table.insert(entries, key .. "=" .. value)
		end
	end

	for i = 1,max_number,1 do
		items = items or ""
	end
	-- entries before items
	for i, v in ipairs(entries) do
		table.insert(items, i, v)
	end
	return table.concat(items, "|")
end

local poss_headings = {
	 = "first-person singular",  = "second-person singular",
	 = "first-person plural",  = "second-person plural",
	 = "third-person",
}

local poss_accel = {
	 = "1|s",  = "2|s",  = "1|p",  = "2|p",  = "3"
}

function make_possessive_table(pagename, args, pos, infl_title, get_forms)
	local note = nil

	if args then
		return ""
	elseif pos == "adj" then
		note = not (args or args) and "''']'''. Only used with ]s." or ""
	elseif pos ~= "noun" then
		return ""
	end

	return "\n" .. make_possessive_table_internal(pagename, args, infl_title, note, get_forms)
end

function make_possessive_table_internal(pagename, args, infl_title, note, get_forms)
	local function show_subtable(code)
		return make_table(get_forms(code), false, poss_headings .. " possessor", "possessive inflection", poss_accel .. "|poss|form|of|", "fi-decl-poss-subtable")
	end
	
	local function repl(param)
		if param == "lemma" then
			return tag_term(pagename)
		elseif param == "info" then
			return " <small>(" .. infl_title .. ")</small>"
		elseif param == "maybenote" then
			if note then
				return [=[
|- class="vsHide"
| colspan="3" | ]=] .. note .. "\n"
			else
				return ""
			end
		else
			return show_subtable(param)
		end
	end
	
	local wikicode = [=[
<div class="noresize">
{| class="inflection-table vsSwitcher fi-decl-poss" data-toggle-category="possessive inflection" cellspacing="1" cellpadding="2"
|- class="fi-decl-poss-header"
! class="vsToggleElement" colspan="3" | ] of {{{lemma}}}{{{info}}}
{{{maybenote}}}|- class="vsHide"
|
{{{1s}}}
{{{2s}}}
{{{1p}}}
{{{2p}}}
{{{3}}}
|}
</div>]=]
	return mw.ustring.gsub(wikicode, "{{{(+)}}}", repl)
end

export.inflections = inflections
return export