Module:vot-conj

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

local export = {}
local m_vot = require("Module:vot")

local function get_stem(word, ending)
    local fragment = mw.ustring.match(word, ending .. "$")
	if fragment then
		return mw.ustring.sub(word, 1, -mw.ustring.len(fragment) - 1), fragment
	end
	error("Unexpected ending for this inflection type! Wrong type?")
end

local function frontalize(w, vh)
	if vh == "ä" then
		w = mw.ustring.gsub(w, "[aouõ]", { a = "ä", o = "ö", u = "y", ["õ"] = "e" })
	end
	return w
end

local function reduce(w)
	return select(2, m_vot.guess_reduction(w))
end

local function get_e(vh)
	return (vh == "ä") and "e" or "õ"
end

local function get_or(value, fallback)
	return (value and #value > 0) and value or fallback
end

local function join(...)
	local t = {}
	for _, s in ipairs(arg) do
		if type(s) == "table" then
			for _, v in ipairs(s) do
				table.insert(t, v)
			end
		elseif type(s) == "string" then
			table.insert(t, s)
		end
	end
	return t
end

local function gsub_all(t, p, q)
	if type(t) == "string" then
		return mw.ustring.gsub(t, p, q)
	end
	local r = {}
	for _, v in ipairs(t) do
		table.insert(r, mw.ustring.gsub(v, p, q))
	end
	return r
end

local function append(t, x)
	if not x then return t end
	if type(t) == "string" then
		return t .. x
	end
	local r = {}
	for _, v in ipairs(t) do
		table.insert(r, v .. x)
	end
	return r
end

local function make_gradation(s, w)
	if s == w then
		return "no gradation"
	else
		return s .. "-" .. w .. " gradation"
	end
end

local function with_note(note, t)
	if not note then return t end
	if type(t) == "string" then
		return { { form = t, note = note } }
	end
	local r = {}
	for _, v in ipairs(t) do
		table.insert(r, { form = v, note = note })
	end
	return r
end

local function geminate(t)
	local gem = m_vot.guess_gemination(t)
	if gem then
		return gem .. mw.ustring.sub(t, -1)
	else
		return t
	end
end

local function geminate_all(t)
	if type(t) == "string" then return geminate(t) end
	local r = {}
	for _, v in ipairs(t) do table.insert(r, geminate(v)) end
	return r
end

local function do_action(data, t, action)
	if type(forms) == "table" then
		local r = {}
		for _, form in ipairs(forms) do
			table.insert(r, do_action(data, form, action))
		end
		return r
	end
	return action(t)
end

local function chiefly_in_dialect(...)
	if #arg == 1 then
		return "Chiefly in the " .. arg[1] .. " dialect."
	elseif #arg > 1 then
		return "Chiefly in the " .. require("Module:table").serialCommaJoin(arg) .. " dialects."
	else
		return 1
	end
end

local function get_any(stems)
	if type(stems) == "string" then
		return stems
	elseif type(stems) == "table" then
		return stems[1]
	end
end

local function extract_grade(data, strong, weak, stem)
	if data.geminate ~= false then
		if mw.ustring.match(strong, "^" .. m_vot.consonant .. m_vot.palatalize .. "?$") then
			-- gemination
			local final = strong
			if mw.ustring.match(stem, final .. final .. "$") then
				data.geminate = true
				stem = get_stem(stem, final)
			end
		elseif strong == "ts" and mw.ustring.match(stem, "tts$") then
			-- gemination
			data.geminate = true
			stem = get_stem(stem, "tts") .. "ts"
		elseif strong == "tš" and mw.ustring.match(stem, "ttš$") then
			-- gemination
			data.geminate = true
			stem = get_stem(stem, "ttš") .. "tš"
		end
	end
	return get_stem(stem, strong)
end

local function unreduce_one(x)
	return ({["õ"] = "a", ["e"] = "ä"})[x] or x
end

local function make_i_stem(stem, strong, weak, final, s)
	if mw.ustring.find(strong, "t$") and not mw.ustring.find(weak, "t$") and mw.ustring.find(final, "[eõ]") then
		if mw.ustring.find(weak, "[hlnr]$") then
			return stem .. mw.ustring.sub(weak, 1, 1) .. s
		else
			return stem .. s
		end
	end
	return stem .. (s == "z" and weak or strong)
end

local function lengthen(t)
    if mw.ustring.find(t, m_vot.vowel .. "$") and not mw.ustring.find(t, m_vot.vowel .. m_vot.vowel .. "$") then
		return t .. mw.ustring.sub(t, -1)
	else
		return t
	end
end

local function postprocess(form, stem, do_not_reduce)
	form = do_not_reduce and form or m_vot.apply_reduction(form, stem)
	
	if stem then
		local len_stem = mw.ustring.len(stem)
		if len_stem > 1 then
			local uvv0, uvv1 = mw.ustring.find(form, "[uü]vv", len_stem - 1)
			if uvv0 and not mw.ustring.match(form, "^[uü" .. m_vot.consonants .. "]", uvv0 - 1) then
				form = mw.ustring.sub(form, 1, uvv0 - 1) .. "vv" .. mw.ustring.sub(form, uvv1 + 1)
			end
		end
	end
	
	return form
end

local function process(data)
	local A = data.vh
	local E = (A == "ä") and "e" or "õ"
	local O = (A == "ä") and "ö" or "o"
	local U = (A == "ä") and "ü" or "u"

	-- present stem (strong, weak)
	local pres_s = data.stem_pres_s or data.stem_pres
	local pres_w = data.stem_pres_w or data.stem_pres
	-- past stem (strong, weak)
	local past_s = data.stem_past_s or data.stem_past
	local past_w = data.stem_past_w or data.stem_past
	-- present passive stem
	local pasv = data.stem_pasv or append(gsub_all(pres_w, m_vot.vowel .. "$", ""), "t")
	-- 2nd infinitive
	local inf2 = data.stem_inf2 or pres_s
	-- conditional stem
	local cond = data.stem_cond or gsub_all(past_s, "i$", "")
	-- imperative stem
	local impr = data.stem_impr or (pres_w .. "k")
	-- past active participle stem
	local ptcp_a = data.stem_ptcp_a or append(pres_s, "n")
	-- past passive participle stem
	local ptcp_p = data.stem_ptcp_p or pasv

	local result = { }

	result.pres_part = with_note("also used as the [[agent noun]]", append(pres_s, "j" .. A))
	result.past_part = append(ptcp_a, U)
	result.past_pasv_part = append(ptcp_p, U)

	result.pres_pasv = append(pasv, A)
	result.pres_1sg = append(pres_w, "n")
	result.pres_2sg = append(pres_w, "d")
	result.pres_3sg = append(pres_w, "b")
	result.pres_1pl = append(pres_w, "mm" .. A)
	result.pres_2pl = append(pres_w, "tt" .. A)
	result.pres_3pl = result.pres_pasv
	result.pres_conn = pres_w
	result.pres_pasv_conn = append(pasv, A)

	result.past_1sg = append(past_w, "n")
	result.past_2sg = append(past_w, "d")
	result.past_3sg = past_s
	result.past_1pl = append(past_w, "mm" .. A)
	result.past_2pl = append(past_w, "tt" .. A)
	result.past_pasv = append(ptcp_p, "ii")
	result.past_3pl = result.past_pasv

	result.cond_1sg = append(cond, "izin")
	result.cond_2sg = append(cond, "izid")
	result.cond_3sg = append(cond, "izʹ")
	result.cond_1pl = append(cond, "izimm" .. A)
	result.cond_2pl = append(cond, "izitt" .. A)
	result.cond_pasv = append(ptcp_p, A .. "izʹ")
	result.cond_3pl = result.cond_pasv

	result.impr_2sg = result.pres_conn
	result.impr_3sg = append(impr, "o")
	result.impr_2pl = append(impr, "a")
	result.impr_3pl = append(impr, "od")

	result.inf1 = lengthen(data.title)
	result.inf2_ill = append(geminate_all(append(inf2, "m" .. A)), A)
	result.inf2_ine = append(inf2, "m" .. A .. "z")
	result.inf2_ela = append(inf2, "m" .. A .. "ss" .. E)
	result.inf2_abe = append(inf2, "m" .. A .. "tt" .. E)

	-- cleanup virtual syllable breaks and do reductions
	for k, v in pairs(result) do
		local do_not_reduce = {["pres_3pl"] = true, ["pres_pasv"] = true, ["impr_2pl"] = true}
		if type(v) == "string" then
			result[k] = postprocess(mw.ustring.gsub(v, m_vot.virtual_syllable_break, ""), data.infl_root, do_not_reduce[k])
		elseif type(v) == "table" then
			for i, f in ipairs(v) do
				if type(f) == "table" and f.form then
					f.form = postprocess(mw.ustring.gsub(f.form, m_vot.virtual_syllable_break, ""), data.infl_root, do_not_reduce[k])
				elseif type(f) == "string" then
					v[i] = postprocess(mw.ustring.gsub(f, m_vot.virtual_syllable_break, ""), data.infl_root, do_not_reduce[k])
				end
			end
		end
	end
	return result
end

-- inflection classes begin
local inflections = {}

inflections["savvõ"] = function (data)
	data.typeno = "I"
	local word = data.title
	local vh = data.vh

	local stem = get_stem(word, "[" .. vh .. get_e(vh) .. "]")
	local stem_short
	local final_cons
	if mw.ustring.find(stem, "dʹdʹ$") then
		stem = mw.ustring.sub(stem, 1, -5) .. "i"
		final_cons = "j"
	elseif mw.ustring.find(stem, "jj$") then
		stem = mw.ustring.sub(stem, 1, -3)
		final_cons = "jj"
	elseif mw.ustring.find(stem, "j$") then
		stem = mw.ustring.sub(stem, 1, -2)
		final_cons = "j"
	elseif mw.ustring.find(stem, "[^aäeiõouüö][aäeiõouüö]vv$") then
		stem_short = get_stem(stem, "vv")
		stem = stem_short .. mw.ustring.sub(stem, -3, -3)
		final_cons = "vv"
	else
		stem = get_stem(stem, "vv")
		final_cons = "vv"
	end
	stem_short = stem_short or stem
	
	local final_pres = get_or(data.args[1], nil)
	local final_past = mw.ustring.sub(stem, -2, -2)
	final_past = get_or(data.args[2], final_past == "ö" and "e" or final_past)

	if final_pres then
		data.stem_pres = mw.ustring.sub(stem, 1, -3) .. final_pres .. final_pres
	else
		data.stem_pres = stem
	end
	data.stem_past = mw.ustring.sub(stem, 1, -3) .. final_past .. "i"

	data.stem_pasv = stem_short .. final_cons
	data.stem_cond = data.stem_past .. "s" .. get_e(vh)
	data.stem_inf2 = stem
	data.stem_ptcp_p = stem .. "t"
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["kuttsua"] = function (data)
	data.typeno = "II-1"
	local word = data.title
	local vh = data.vh
	local strong = data.args[1] or error("must specify strong grade")
	local weak = data.args[2] or error("must specify weak grade")

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local final = data.args[3] or mw.ustring.sub(word, -2, -2)
	local stem = get_stem(word, final == vh and vh or (final .. "[" .. vh .. get_e(vh) .. "]"))
	stem = extract_grade(data, strong, weak, stem)

	data.stem_pres = stem .. weak .. final
	data.stem_pres_s = stem .. strong .. final
	data.stem_past_s = stem .. strong .. final
	data.stem_past_w = stem .. strong .. final .. "zi"
	data.stem_pasv = stem .. weak .. (final == vh and get_e(vh) or final) .. "t"
	data.stem_ptcp_a = stem .. strong .. final .. "nn"
	data.stem_ptcp_p = stem .. weak .. (final == vh and get_e(vh) or final) .. "tt"
	data.stem_inf2 = stem .. strong .. final
	data.stem_impr = stem .. strong .. final .. "g"
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["alkua"] = function (data)
	data.typeno = "II-2"
	local word = data.title
	local vh = data.vh
	local strong = data.args[1] or error("must specify strong grade")
	local weak = data.args[2] or error("must specify weak grade")

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local final = mw.ustring.sub(word, -2, -2)
	local stem = get_stem(word, strong .. final .. "[" .. vh .. get_e(vh) .. "]")

	data.stem_pres = stem .. weak .. final
	data.stem_pres_s = stem .. strong .. final
	data.stem_past_s = stem .. strong .. final
	data.stem_past_w = stem .. weak .. final .. "zi"
	data.stem_pasv = stem .. weak .. final .. "t"
	data.stem_ptcp_a = stem .. strong .. final .. "nn"
	data.stem_ptcp_p = stem .. weak .. final .. "t"
	data.stem_inf2 = stem .. strong .. final
	data.stem_impr = stem .. strong .. final .. "g"
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["tuntõa"] = function (data)
	data.typeno = "III-1"
	local word = data.title
	local vh = data.vh
	local strong = data.args[1] or error("must specify strong grade")
	local weak = data.args[2] or error("must specify weak grade")
	local e = get_e(vh)

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local stem = get_stem(word, strong .. "[" .. vh .. e .. "i]" .. vh .. "?")
	local final = get_or(data.args[3], vh)
	local final_pasv = mw.ustring.find(final, "[aä]") and frontalize("õ", vh) or final

	data.stem_pres = stem .. weak .. final
	data.stem_pres_s = stem .. strong .. final
	data.stem_past_s = make_i_stem(stem, strong, weak, e, "s") .. "i"
	data.stem_past_w = make_i_stem(stem, strong, weak, e, "z") .. "i"
	data.stem_pasv = stem .. weak .. final_pasv .. "t"
	data.stem_ptcp_a = stem .. strong .. final .. "nn"
	data.stem_ptcp_p = stem .. weak .. final_pasv .. "tt"
	data.stem_cond = stem .. strong .. final
	data.stem_impr = stem .. strong .. final .. "g"
	data.stem_inf2 = stem .. strong .. final
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

local function get_type3_past_ending(stem, final, vh)
	if vh == "a" and final == "a" and mw.ustring.find(stem, "a") then
		return "õ"
	else
		return "i"
	end
end

inflections["õssa"] = function (data)
	data.typeno = "III-2"
	local word = data.title
	local vh = data.vh
	local strong = data.args[1] or error("must specify strong grade")
	local weak = data.args[2] or error("must specify weak grade")
	local e = get_e(vh)

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local final = get_or(data.args[3], vh)
	local final_pasv = mw.ustring.find(final, "[aä]") and frontalize("õ", vh) or final
	local stem = get_stem(word, "[" .. vh .. e .. "i]" .. vh .. "?")
	stem = extract_grade(data, strong, weak, stem)

	data.stem_pres = stem .. weak .. final
	data.stem_pres_s = stem .. strong .. final
	data.stem_past_s = make_i_stem(stem, strong, weak, e, "s") .. "i"
	data.stem_past_w = make_i_stem(stem, strong, weak, e, "z") .. "i"
	data.stem_pasv = stem .. weak .. final_pasv .. "t"
	data.stem_ptcp_a = stem .. strong .. final .. "nn"
	data.stem_ptcp_p = stem .. weak .. final_pasv .. "tt"
	data.stem_cond = stem .. strong .. final
	data.stem_impr = stem .. strong .. final .. "g"
	data.stem_inf2 = stem .. strong .. final
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["täätä"] = function (data)
	data.typeno = "III-2"
	local word = data.title
	local vh = data.vh
	local strong = "t"
	local weak = ""
	local e = get_e(vh)

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local stem = get_stem(word, strong .. "[" .. vh .. e .. "i]" .. vh .. "?")
	local final = get_or(data.args[3], vh)
	local final_pasv = mw.ustring.find(final, "[aä]") and frontalize("õ", vh) or final

	data.stem_pres = stem
	data.stem_pres_s = stem .. "t" .. final
	data.stem_past_s = make_i_stem(stem, "t", weak, e, "s") .. "i"
	data.stem_past_w = make_i_stem(stem, "t", weak, e, "z") .. "i"
	data.stem_pasv = stem .. "j" .. final_pasv .. "t"
	data.stem_ptcp_a = stem .. "t" .. final .. "nn"
	data.stem_ptcp_p = stem .. "j" .. final_pasv .. "tt"
	data.stem_cond = stem .. "t" .. final
	data.stem_impr = stem .. "t" .. final .. "g"
	data.stem_inf2 = stem .. "t" .. final
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["anta"] = function (data)
	data.typeno = "III-3"
	local word = data.title
	local vh = data.vh
	local strong = data.args[1] or error("must specify strong grade")
	local weak = data.args[2] or error("must specify weak grade")
	local e = get_e(vh)

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local stem = get_stem(word, "[" .. vh .. e .. "i]" .. vh .. "?")
	stem = extract_grade(data, strong, weak, stem)
	local final = get_or(data.args[3], vh)
	local final_pasv = mw.ustring.find(final, "[aä]") and frontalize("õ", vh) or final

	data.stem_pres = stem .. weak .. final
	data.stem_pres_s = stem .. strong .. final
	data.stem_past_s = make_i_stem(stem, strong, weak, final, "s") .. "i"
	data.stem_past_w = make_i_stem(stem, strong, weak, final, "z") .. "i"
	data.stem_pasv = stem .. weak .. final_pasv .. "t"
	data.stem_ptcp_a = stem .. strong .. final .. "nn"
	data.stem_ptcp_p = stem .. weak .. final_pasv .. "tt"
	data.stem_cond = stem .. strong .. (final == "i" and "" or final)
	data.stem_impr = stem .. strong .. final .. "g"
	data.stem_inf2 = stem .. strong .. final
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["ellä"] = function (data)
	data.typeno = "III-4"
	local word = data.title
	local vh = data.vh
	local strong = data.args[1] or error("must specify strong grade")
	local weak = data.args[2] or error("must specify weak grade")
	local e = get_e(vh)

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local stem = get_stem(word, "[" .. vh .. e .. "i]" .. vh .. "?")
	stem = extract_grade(data, strong, weak, stem)
	local final = get_or(data.args[3], vh)
	local final_pasv = mw.ustring.find(final, "[aä]") and frontalize("õ", vh) or final

	local past_ending = get_type3_past_ending(stem, final, vh)

	data.stem_pres = stem .. weak .. final
	data.stem_pres_s = stem .. strong .. final
	data.stem_past_s = make_i_stem(stem, strong, weak, final, "s") .. past_ending
	data.stem_past_w = make_i_stem(stem, strong, weak, final, "z") .. past_ending
	data.stem_pasv = stem .. weak .. final_pasv .. "t"
	data.stem_ptcp_a = stem .. strong .. final .. "nn"
	data.stem_ptcp_p = stem .. weak .. final_pasv .. "tt"
	data.stem_cond = stem .. strong .. final
	data.stem_impr = stem .. strong .. final .. "g"
	data.stem_inf2 = stem .. strong .. final
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["vasatõ"] = function (data)
	data.typeno = "IV"
	local word = data.title
	local vh = data.vh
	local weak = data.args[1] or error("must specify weak grade")
	local strong = data.args[2] or error("must specify strong grade")

	--if strong ~= "" and weak == "" then weak = m_vot.virtual_syllable_break end
	
	local cons = mw.ustring.sub(word, -2, -2)
	local stem, final
	if cons == "t" then
		final = unreduce_one(mw.ustring.sub(word, -3, -3))
		stem = get_stem(word, weak .. m_vot.vowel .. "t[aäõe]?")
	elseif cons == "s" then
		final = mw.ustring.sub(word, -4, -4)
		stem = mw.ustring.gsub(get_stem(word, final .. "ss[aäõe]?"), final .. "$", "")
		final = unreduce_one(final)
	else
		error("Unrecognized infinitive for type IV")	
	end
	final = get_or(data.args[3], final)

	local strong_stem = stem .. strong .. final
	local weak_stem = stem .. weak .. final
	local u = frontalize("u", vh)
	local do_geminate = data.geminate == nil and (final == vh) or data.geminate

	data.stem_pres = do_geminate and (geminate(stem .. strong .. final) .. final) or strong_stem
	data.stem_past_s = strong_stem .. "zi"
	data.stem_past_w = strong_stem .. "zi"
	data.stem_pasv = weak_stem .. cons .. (cons == "s" and cons or "")
	data.stem_impr = weak_stem .. cons .. "k"
	data.stem_cond = mw.ustring.gsub(reduce(strong_stem), "i$", "")
	data.stem_ptcp_a = weak_stem .. "nn"
	data.stem_ptcp_p = weak_stem .. cons .. "t"
	data.stem_inf2 = data.stem_pres
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["tehä"] = function (data)
	data.typeno = "V-1"
	local word = data.title
	local vh = data.vh
	local stem = get_stem(word, "h" .. vh)
	local e = get_e(vh)

	data.stem_pres = stem .. e
	data.stem_past_s = stem .. "tši"
	data.stem_past_w = stem .. "i"
	data.stem_pasv = stem .. "ht"
	data.stem_impr = stem .. "hk"
	data.stem_cond = stem .. "tše"
	data.stem_ptcp_a = stem .. "hn"
	data.stem_ptcp_p = data.stem_pasv
	data.stem_inf2 = stem .. "tš" .. e
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["pesse"] = function (data)
	data.typeno = "V-2"
	local word = data.title
	local vh = data.vh
	local cons = mw.ustring.sub(word, -2, -2)
	local e = get_e(vh)
	local stem = get_stem(word, "ss[" .. vh .. e .. "]")

	local cons2 = cons == "s" and "z" or cons

	data.stem_pres = stem .. cons2 .. e
	data.stem_past_s = stem .. cons2 ..  "i"
	data.stem_past_w = stem .. cons .. "i"
	data.stem_pasv = stem .. cons .. cons
	data.stem_cond = stem .. cons .. e
	data.stem_impr = stem .. cons .. "k"
	data.stem_ptcp_a = stem .. (cons == "s" and "zn" or cons .. cons)
	data.stem_ptcp_p = stem .. cons .. "t"
	data.stem_inf2 = stem .. cons .. e
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["joossõ"] = function (data)
	data.typeno = "V-2"
	local word = data.title
	local vh = data.vh
	local cons = mw.ustring.sub(word, -2, -2)
	local e = get_e(vh)
	local stem = get_stem(word, "ss[" .. vh .. e .. "]")
	local stemh = mw.ustring.sub(stem, 1, -2) .. "h"

	data.stem_pres = stemh .. "z" .. e
	data.stem_past_s = stemh .. "zi"
	data.stem_past_w = stemh .. "si"
	data.stem_pasv = stem .. "ss"
	data.stem_cond = stemh .. "s" .. e
	data.stem_impr = stem .. "sk"
	data.stem_ptcp_a = stem .. "zn"
	data.stem_ptcp_p = stem .. "ss"
	data.stem_inf2 = stemh .. "s" .. e
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["tullõ"] = function (data)
	data.typeno = "V-3"
	local word = data.title
	local vh = data.vh
	local e = get_e(vh)
	local weak = data.args[1] or error("must specify weak grade")
	local strong = data.args[2] or error("must specify strong grade")
	local cons = mw.ustring.sub(word, -2, -2)
	local stem = get_stem(word, "[lnr][lnr][" .. vh .. e .. "]")
	local final = get_or(data.args[3], mw.ustring.sub(stem, -1, -1))
	stem = get_stem(stem, weak .. m_vot.vowel)
	local strong_stem = stem .. strong .. final
	local weak_stem = stem .. weak .. final

	if cons == "r" or mw.ustring.find(stem, m_vot.vowel) then
		data.stem_pres = strong_stem .. cons .. e
	else
		local pres = strong_stem
		if mw.ustring.find(pres, m_vot.vowel .. "$") and not mw.ustring.find(pres, m_vot.vowel .. m_vot.vowel .. "$") then
			pres = pres .. mw.ustring.sub(pres, -1)
		end
		if mw.ustring.find(strong_stem, "[uy]$") then
			data.stem_pres = strong_stem .. e
		else
			data.stem_pres = pres
		end
		data.stem_pres_s = strong_stem .. cons .. e
	end
	data.stem_past_s = strong_stem .. cons .. "i"
	data.stem_past_w = weak_stem .. cons .. "i"
	data.stem_pasv = weak_stem .. cons .. cons
	data.stem_cond = strong_stem .. cons .. e
	data.stem_impr = weak_stem .. cons .. "k"
	data.stem_ptcp_a = weak_stem .. cons .. cons
	data.stem_ptcp_p = weak_stem .. cons .. "t"
	data.stem_inf2 = strong_stem .. cons .. e
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["ootõllõ"] = function (data)
	data.typeno = "V-4"
	local word = data.title
	local vh = data.vh
	local e = get_e(vh)
	local weak = data.args[1] or error("must specify weak grade")
	local strong = data.args[2] or error("must specify strong grade")
	local cons = mw.ustring.sub(word, -2, -2)
	local stem = get_stem(word, "[lnr][lnr][" .. vh .. e .. "]")
	local final = get_or(data.args[3], mw.ustring.sub(stem, -1, -1))
	stem = get_stem(stem, weak .. m_vot.vowel)
	local strong_stem = stem .. strong .. final
	local weak_stem = stem .. weak .. final

	data.stem_pres = strong_stem
	data.stem_pres_s = strong_stem .. cons .. e
	data.stem_past_s = strong_stem .. cons .. "i"
	data.stem_past_w = strong_stem .. cons .. "i"
	data.stem_pasv = weak_stem .. cons .. cons
	data.stem_cond = strong_stem .. cons .. vh
	data.stem_impr = weak_stem .. cons .. "k"
	data.stem_ptcp_a = weak_stem .. cons .. cons
	data.stem_ptcp_p = weak_stem .. cons .. "t"
	data.stem_inf2 = strong_stem
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

inflections["apatõ"] = function (data)
	data.typeno = "V-5"
	local word = data.title
	local vh = data.vh
	local e = get_e(vh)
	local weak = data.args[1] or error("must specify weak grade")
	local strong = data.args[2] or error("must specify strong grade")
	local final = data.args[3] or error("must specify final stem vowel")
	local stem = get_stem(word, "t[" .. vh .. e .. "]")
	stem = get_stem(stem, weak .. m_vot.vowel)
	local strong_stem = stem .. strong .. final
	local weak_stem = stem .. weak .. final

	data.stem_pres = strong_stem .. "n" .. e
	data.stem_past_s = strong_stem .. "ni"
	data.stem_past_w = strong_stem .. "ni"
	data.stem_pasv = weak_stem .. "t"
	data.stem_cond = strong_stem .. "n" .. e
	data.stem_impr = strong_stem .. "tk"
	data.stem_ptcp_a = weak_stem .. "nn"
	data.stem_ptcp_p = data.stem_pasv
	data.stem_inf2 = strong_stem .. "n" .. e
	
	data.grade = make_gradation(strong, weak)
	data.infl_root = stem
	return process(data)
end

-- inflection classes end

local conj_table = [=[{| class="inflection-table vsSwitcher" data-toggle-category="conjugation" style="border:1px solid #CCCCFF;text-align:left;"
|-
! colspan=6 class="vsToggleElement" style="background:rgb(80%,80%,100%);text-align:left;width:35em;"| Conjugation of {{{title}}} (<span style="font-size:90%">{{{type}}}</span>)
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=6 style="width:10em;" | Indicative
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=3 style="width:10em;" | Present
! colspan=3 style="width:10em;" | Perfect
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! style="width:10em;" |
! style="width:13em; background:rgb(90%,90%,100%)" | positive
! style="width:13em; background:rgb(90%,90%,100%)" | negative
! style="width:10em;" |
! style="width:13em; background:rgb(90%,90%,100%)" | positive
! style="width:13em; background:rgb(90%,90%,100%)" | negative
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st singular
| {{{pres_1sg}}}
| {{{en !pres_conn}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 1st singular
| {{{õõn !past_part}}}
| {{{en õõ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd singular
| {{{pres_2sg}}}
| {{{ed !pres_conn}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd singular
| {{{õõd !past_part}}}
| {{{ed õõ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd singular
| {{{pres_3sg}}}
| {{{eb !pres_conn}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd singular
| {{{on !past_part}}}
| {{{eb õõ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st plural
| {{{pres_1pl}}}
| {{{emmä !pres_conn}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 1st plural
| {{{öõmmõ !past_part}}}
| {{{emmä õõ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd plural
| {{{pres_2pl}}}
| {{{että !pres_conn}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd plural
| {{{õõttõ !past_part}}}
| {{{että õõ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd plural
| {{{pres_3pl}}}
| {{{eväd !pres_conn}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd plural
| {{{õlla !past_pasv_part}}}
| {{{eväd õõ !past_pasv_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | impersonal
| {{{pres_pasv}}}
| {{{eväd !pres_conn}}}
! style="width:11em; background:rgb(80%,80%,100%)" | impersonal
| {{{õlla !past_pasv_part}}}
| {{{eväd õõ !past_pasv_part}}}
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=3 style="width:11em;" | Imperfect
! colspan=3 style="width:11em;" | Pluperfect
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! style="width:11em;" |
! style="width:11em; background:rgb(90%,90%,100%)" | positive
! style="width:11em; background:rgb(90%,90%,100%)" | negative
! style="width:11em;" |
! style="width:11em; background:rgb(90%,90%,100%)" | positive
! style="width:11em; background:rgb(90%,90%,100%)" | negative
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st singular
| {{{past_1sg}}}
| {{{en !past_part}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 1st singular
| {{{õlin !past_part}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd singular
| {{{past_2sg}}}
| {{{ed !past_part}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd singular
| {{{õlid !past_part}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd singular
| {{{past_3sg}}}
| {{{eb !past_part}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd singular
| {{{õli !past_part}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st plural
| {{{past_1pl}}}
| {{{emmä !past_part}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 1st plural
| {{{õlimmõ !past_part}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd plural
| {{{past_2pl}}}
| {{{että !past_part}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd plural
| {{{õlittõ !past_part}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd plural
| {{{past_3pl}}}
| {{{eväd !past_pasv_part}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd plural
| {{{õlti !past_pasv_part}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | impersonal
| {{{past_pasv}}}
| {{{eväd !past_pasv_part}}}
! style="width:11em; background:rgb(80%,80%,100%)" | impersonal
| {{{õlti !past_pasv_part}}}
| —
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=6 style="width:11em;" | Conditional
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=3 style="width:11em;" | Present
! colspan=3 style="width:11em;" | Perfect
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! style="width:11em;" |
! style="width:11em; background:rgb(90%,90%,100%)" | positive
! style="width:11em; background:rgb(90%,90%,100%)" | negative
! style="width:11em;" |
! style="width:11em; background:rgb(90%,90%,100%)" | positive
! style="width:11em; background:rgb(90%,90%,100%)" | negative
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st singular
| {{{cond_1sg}}}
| {{{en !cond_3sg}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 1st singular
| {{{õllõizin !past_part}}}
| {{{en õllõizʹ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd singular
| {{{cond_2sg}}}
| {{{ed !cond_3sg}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd singular
| {{{õllõizid !past_part}}}
| {{{ed õllõizʹ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd singular
| {{{cond_3sg}}}
| {{{eb !cond_3sg}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd singular
| {{{õllõizʹ !past_part}}}
| {{{eb õllõizʹ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st plural
| {{{cond_1pl}}}
| {{{emmä !cond_3sg}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 1st plural
| {{{õllõizimmõ !past_part}}}
| {{{emmä õllõizʹ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd plural
| {{{cond_2pl}}}
| {{{että !cond_3sg}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd plural
| {{{õllõizittõ !past_part}}}
| {{{että õllõizʹ !past_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd plural
| {{{cond_3pl}}}
| {{{eväd !cond_pasv}}}
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd plural
| {{{õltaizʹ !past_pasv_part}}}
| {{{eväd õltaizʹ !past_pasv_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | impersonal
| {{{cond_pasv}}}
| {{{eväd !cond_pasv}}}
! style="width:11em; background:rgb(80%,80%,100%)" | impersonal
| {{{õltaizʹ !past_pasv_part}}}
| {{{eväd õltaizʹ !past_pasv_part}}}
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=6 style="width:11em;" | Imperative
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=3 style="width:11em;" | Present
! colspan=3 rowspan="9" | 
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! style="width:11em;" |
! style="width:11em; background:rgb(90%,90%,100%)" | positive
! style="width:11em; background:rgb(90%,90%,100%)" | negative
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st singular
| —
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd singular
| {{{impr_2sg}}}
| {{{elä !impr_2sg}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd singular
| {{{impr_3sg}}}
| {{{elko !impr_3sg}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 1st plural
| —
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 2nd plural
| {{{impr_2pl}}}
| {{{elka !impr_2pl}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | 3rd plural
| {{{impr_3pl}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(80%,80%,100%)" | impersonal
| —
| —
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=6 style="width:11em;" | Nominal forms
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=3 style="width:11em;" | Infinitives
! colspan=3 style="width:11em;" | Participles
|- class="vsHide" style="background:rgb(80%,80%,100%);vertical-align:top;"
! colspan=2 style="width:11em;" |
! style="width:11em; background:rgb(90%,90%,100%)" |
! style="width:11em;" |
! style="width:11em; background:rgb(90%,90%,100%)" | active
! style="width:11em; background:rgb(90%,90%,100%)" | passive
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! colspan=2 style="width:11em; background:rgb(80%,80%,100%)" | 1st
| {{{inf1}}}
! style="width:11em; background:rgb(80%,80%,100%)" | present
| {{{pres_part}}}
| —
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! rowspan=4 style="width:11em; background:rgb(80%,80%,100%)" | 2nd
! style="width:11em; background:rgb(90%,90%,100%)" | illative
| {{{inf2_ill}}}
! style="width:11em; background:rgb(80%,80%,100%)" | past
| {{{past_part}}}
| {{{past_pasv_part}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(90%,90%,100%)" | inessive
| {{{inf2_ine}}}
| colspan=3 rowspan=3 style="font-size:smaller;" | {{{notes}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(90%,90%,100%)" | elative
| {{{inf2_ela}}}
|- class="vsHide" style="background:rgb(95%,95%,100%);vertical-align:top;"
! style="width:11em; background:rgb(90%,90%,100%)" | abessive
| {{{inf2_abe}}}
|}]=]

local function tag(text)
	return require("Module:script utilities").tag_text(text, m_vot.lang, nil, "term")
end

local function link(text, prefix)
	return require("Module:script utilities").tag_text((prefix and prefix or "") .. require("Module:links").language_link{ term = text, lang = m_vot.lang }, m_vot.lang)
end

local function mention(text)
	return require("Module:links").full_link({ term = text, lang = m_vot.lang }, "term")
end

local function help_tooltip(text)
	return "<sup>" .. tostring(mw.html.create("span"):attr("style", "cursor:help"):attr("title", text):wikitext("?")) .. "</sup>"
end

local function note_reference(note)
	if note then
		return "<sup>" .. note .. ")</sup>"
	else
		return ""
	end
end

local function link_and_note(word_prefix, form, prefix)
	if type(form) == "table" then
		return link(word_prefix .. form.form, prefix) .. note_reference(form.note)
	else
		return link(word_prefix .. form, prefix)
	end
end

local function note_text(reference, text)
	return note_reference(reference) .. " " .. text
end

-- extract notes from forms into a list and replace by their index
local function format_notes_for_form(data, form)
	local note = form.note
	if note then
		local index = data.notes_reverse[note]
		if index then
			form.note = index
		else
			index = tostring(#data.notes + 1)
			table.insert(data.notes, note)
			data.notes_reverse[note] = index
			form.note = index
		end
	end
end

local function format_notes(data, forms)
	data.notes = {}
	data.notes_reverse = {}
	for k, v in pairs(forms) do
		if type(v) == "table" then
			if v[1] then
				for _, form in ipairs(v) do
					if type(form) == "table" then
						format_notes_for_form(data, form)
					end
				end
			else
				format_notes_for_form(data, v)
			end
		end
	end
	data.notes_reverse = nil
end

function export.raw(word, infl_type, grad1, grad2, args)
	if not infl_type then error("inflection class not specified") end
	args = args or {}

	local infl = inflections[infl_type] or error("unsupported inflection type")
	local geminate, vh
	if args["g"] == "1" then
		geminate = true
	elseif args["g"] == "0" or args["g"] == "-" then
		geminate = false
	end
	
	if args["v"] then
		vh = args["v"]
		if vh ~= "a" and vh ~= "ä" then
			error("Invalid vowel harmony specification")
		end
	else
		vh = m_vot.guess_vowel_harmony(word)
	end

	args[1] = args[1] or grad1
	args[2] = args[2] or grad2
	local data = { title = word, geminate = geminate, vh = vh, args = args }
	return infl(data)
end

function export.show(frame)
	local infl_type = frame.args[1] or error("inflection class not specified")
	local infl = inflections[infl_type] or error("unsupported inflection type")
	local args = frame:getParent().args
	local title = args["title"] or mw.title.getCurrentTitle().text

	local geminate, vh
	if args["g"] == "1" then
		geminate = true
	elseif args["g"] == "0" or args["g"] == "-" then
		geminate = false
	end
	
	if args["v"] then
		vh = args["v"]
		if vh ~= "a" and vh ~= "ä" then
			error("Invalid vowel harmony specification")
		end
	else
		vh = m_vot.guess_vowel_harmony(title)
	end

	local prefix = args["prefix"]

	if prefix then
		local prefix_len = mw.ustring.len(prefix)
		local split
		split, title = mw.ustring.sub(title, 1, prefix_len), mw.ustring.sub(title, prefix_len + 1)
		if #title == 0 or prefix ~= split then
			error("Prefix is too long or does not match title")
		end
	end

	local data = { title = title, geminate = geminate, vh = vh, args = args, prefix = prefix }
	local word_prefix = prefix or ""
	local categories = {}

	local forms = infl(data)
	format_notes(data, forms)

	local function repl(form)
		local prefix = nil
		if form == "title" then
			return "'''" .. title .. "'''"
		elseif form == "type" then
			if data.irregular then
				return "irregular"
			end
			local s = "type " .. data.typeno .. "/" .. mention(infl_type)
			if data.grade then
				s = s .. ", " .. data.grade
			else
				s = s .. ", " .. make_gradation(nil, nil)
			end
			if data.geminate then
				s = s .. ", gemination"
			end
			return s
		elseif form == "notes" then
			local results = {}
			for index, note in ipairs(data.notes) do
				table.insert(results, note_text(tostring(index), note))
			end
			return table.concat(results, "<br />")
		elseif mw.ustring.find(form, "!") then
			local excl = mw.ustring.find(form, "!")
			prefix = mw.ustring.sub(form, 1, excl - 1)
			form = mw.ustring.sub(form, excl + 1)
		end
		local value = forms[form]
		if type(value) == "table" and value[1] then
			local result = {}
			for _, f in ipairs(value) do
				table.insert(result, link_and_note(word_prefix, f, prefix))
			end
			return table.concat(result, ", ")
		elseif type(value) == "string" and #value > 0 then
			return link_and_note(word_prefix, value, prefix)
		else
			return "&mdash;"
		end
	end

	if mw.title.getCurrentTitle().namespace == 0 then
		table.insert(categories, "Votic " .. infl_type .. "-type verbs")
	end
	
	local result = mw.ustring.gsub(conj_table, "{{{([a-z0-9äöüõʹ _:!]+)}}}", repl)
	result = mw.ustring.gsub(result, "{{m|vot|([^}]-)}}", mention)
	return result .. require("Module:utilities").format_categories(categories, m_vot.lang)
end

return export