Jump to content

Module:User:Benwing2/cs-verb

From Wiktionary, the free dictionary

This is a private module sandbox of Benwing2, for their own experimentation. Items in this module may be added and removed at Benwing2's discretion; do not rely on this module's stability.


local export = {}


--[=[

Authorship: Ben Wing <benwing2>

]=]

--[=[

TERMINOLOGY:

-- "slot" = A particular combination of tense/mood/person/number/gender/etc.
	 Example slot names for verbs are "pres_1s" (present first singular) and
	 "past_pasv_part_impers" (impersonal past passive participle).
	 Each slot is filled with zero or more forms.

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

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

local lang = require("Module:languages").getByCode("cs")
local m_table = require("Module:table")
local m_links = require("Module:links")
local m_string_utilities = require("Module:string utilities")
local m_script_utilities = require("Module:script utilities")
local iut = require("Module:User:Benwing2/inflection utilities")
local m_para = require("Module:parameters")
local com = require("Module:User:Benwing2/cs-common")

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

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

local TEMP_REFLEXIVE_INSERTION_POINT = u(0xFFF0) -- temporary character used to mark the reflexive insertion point


-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
	local retval = rsubn(term, foo, bar)
	return retval
end


-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
	local retval, nsubs = rsubn(term, foo, bar)
	return retval, nsubs > 0
end


local function tag_text(text)
	return m_script_utilities.tag_text(text, lang)
end


local verb_slots = {
	["infinitive"] = "inf",
	["pres_act_part"] = "pres|act|part",
	["past_act_part"] = "past|act|part",
	["long_pass_part"] = "long|past|pass|part",
	["pres_tgress_m"] = "m|s|pres|tgress",
	["pres_tgress_fn"] = "f//n|s|pres|tgress",
	["pres_tgress_p"] = "p|pres|tgress",
	["past_tgress_m"] = "m|s|past|tgress",
	["past_tgress_fn"] = "f//n|s|past|tgress",
	["past_tgress_p"] = "p|past|tgress",
	["vnoun"] = "vnoun",
	["pres_fut_1s"] = "-",
	["pres_fut_2s"] = "-",
	["pres_fut_3s"] = "-",
	["pres_fut_1p"] = "-",
	["pres_fut_2p"] = "-",
	["pres_fut_3p"] = "-",
	["pres_1s"] = "1|s|pres|ind",
	["pres_2s"] = "2|s|pres|ind",
	["pres_3s"] = "3|s|pres|ind",
	["pres_1p"] = "1|p|pres|ind",
	["pres_2p"] = "2|p|pres|ind",
	["pres_3p"] = "3|p|pres|ind",
	["fut_1s"] = "1|s|fut|ind",
	["fut_2s"] = "2|s|fut|ind",
	["fut_3s"] = "3|s|fut|ind",
	["fut_1p"] = "1|p|fut|ind",
	["fut_2p"] = "2|p|fut|ind",
	["fut_3p"] = "3|p|fut|ind",
	["imp_2s"] = "2|s|imp",
	["imp_1p"] = "1|p|imp",
	["imp_2p"] = "2|p|imp",
}


local function add_part_gender_number(pref, accel_template)
	for _, slot_accel_part in ipairs {
		{"m", "m|s"},
		{"f", "f|s"},
		{"n", "n|s"},
		{"mp_an", "an|m|p"},
		{"mp_in", "in|m|p"},
		{"fp", "f|p"},
		{"np", "n|p"},
	} do
		local slot, accel_part = unpack(slot_accel_part)
		verb_slots[pref .. "_" .. slot] = accel_template:format(accel_part)
	end
end

-- List used for generating person-number-gender tenses like the past and conditional. Each element is a three-element
-- list of {DEST_SUFFIX, GENDER_NUMBER_SUFFIX, TEMPLATE_IND} where DEST_SUFFIX is the suffix to add onto the destination
-- prefix (e.g. "past_") to generate the slot; GENDER_NUMBER_SUFFIX is the suffix used to fetch the participle; and
-- TEMPLATE_IND is the suffix used to fetch the appropriate person-number template.
local person_number_gender_props = {
	{"1sm", "m", 1}, {"1sf", "f", 1}, {"1sn", "n", 1},
	{"2sm", "m", 2}, {"2sf", "f", 2}, {"2sn", "n", 2},
	{"3sm", "m", 3}, {"3sf", "f", 3}, {"3sn", "n", 3},
	{"1pm", "mp_an", 4}, {"1pf", "fp", 4}, {"1pn", "np", 4},
	{"2pm_polite", "m", 5}, {"2pm_plural", "mp_an", 5},
	{"2pf_polite", "f", 5}, {"2pf_plural", "fp", 5},
	{"2pn_polite", "n", 5}, {"2pn_plural", "np", 5},
	{"3pm_an", "mp_an", 6}, {"3pm_in", "mp_in", 6}, {"3pf", "fp", 6}, {"3pn", "np", 6},
}

local function add_tense_person_number_gender(pref)
	for _, suffix_pair in ipairs(person_number_gender_props) do
		local suffix, _, _ = unpack(suffix_pair)
		verb_slots[pref .. "_" .. suffix] = "-"
	end
end

add_part_gender_number("lpart", "%s|l-part")
add_part_gender_number("ppp", "short|%s|past|pass|part")

add_tense_person_number_gender("past")
add_tense_person_number_gender("cond")
-- Skip this as it's obsolete.
-- add_tense_person_number_gender("past_perf")
add_tense_person_number_gender("cond_past")

local budu_forms = {
	["1s"] = "budu",
	["2s"] = "budeš",
	["3s"] = "bude",
	["1p"] = "budeme",
	["2p"] = "budete",
	["3p"] = "budou",
}


local override_stems = m_table.listToSet {
	"pres",
	"past",
	"imp",
	"ppp",
	"vn",
	"prtr",
	"patr",
}


local function skip_slot(base, slot)
	if slot == "infinitive" then
		return false
	end
	if base.nopres and (rfind(slot, "pres") or rfind(slot, "fut")) then
		return true
	end
	if base.nopast and (rfind(slot, "past") or rfind(slot, "lpart")) then
		return true
	end
	if base.noimp and rfind(slot, "imp") then
		return true
	end
	if base.impers then
		-- Include _3s and _3sn slots, as well as _n and _fn slots for participles/transgressives.
		if rfind(slot, "3sn?$") or rfind(slot, "_f?n$") or slot == "vnoun" then
			return false
		else
			return true
		end
	end
	if (base.only3 or base.only3pl) and rfind(slot, "[12]") then
		return true
	end
	if (base.onlypl or base.only3pl) and (rfind(slot, "[123]s") or rfind(slot, "_[mfn]$") or rfind(slot, "_fn$")) then
		return true
	end
	if base.only3orpl and rfind(slot, "[12]s") then
		return true
	end
	return false
end


local function add(base, slot, stems, endings, footnotes)
	if not endings then
		return
	end
	if skip_slot(base, slot) then
		return
	end
	endings = iut.combine_form_and_footnotes(endings, footnotes)
	local function combine_stem_ending(stem, ending)
		return com.combine_stem_ending(base, slot, stem, ending)
	end
	iut.add_forms(base.forms, slot, stems, endings, combine_stem_ending)
end


local function map_forms(forms, fn)
	if type(forms) == "table" then
		forms = iut.convert_to_general_list_form(forms)
		for _, form in ipairs(forms) do
			fn(form.form, form.footnotes)
		end
	else
		fn(forms)
	end
end


local function fetch_footnotes(separated_group, allow_multiple_groups)
	local footnote_groups = allow_multiple_groups and {} or nil
	local footnotes
	for j = 2, #separated_group - 1, 2 do
		if allow_multiple_groups and separated_group[j + 1] == ":" then
			table.insert(footnote_groups, footnotes or {})
		elseif separated_group[j + 1] ~= "" then
			error("Extraneous text after bracketed footnotes: '" .. table.concat(separated_group) .. "'")
		end
		if not footnotes then
			footnotes = {}
		end
		table.insert(footnotes, separated_group[j])
	end
	if allow_multiple_groups then
		if not footnotes then
			error("Footnote-separating colon doesn't precede a footnote")
		end
		table.insert(footnote_groups, footnotes)
		return footnote_groups
	else
		return footnotes
	end
end


-- No generate_default_pres1s_principal_part(), which generates the pres_fut_1s principal part.
-- This must be specified by each verb conjugation.


local function generate_default_pres3s_principal_part(base, do_err)
	return iut.map_forms(base.principal_part_forms.pres1s, function(form)
		local stem = rmatch(form, "^(.*)[ui]$")
		if stem then
			-- There does not seem to be a case where ě can occur in the present third singular.
			return stem .. "e"
		end
		stem = rmatch(form, "^(.*)m$") -- -ím or -ám
		if stem then
			return stem
		end
		error("'pres3s:' must be given in order to specify the present third-person singular principal part because "
			.. "first-person singular principal part '" .. form .. "' does not end in -u, -i or -m")
	end)
end


local function add_present_minus_1s_3p(base)
	local pres3s = base.principal_part_forms.pres3s
	add(base, "pres_fut_2s", pres3s, "š")
	add(base, "pres_fut_3s", pres3s, "")
	add(base, "pres_fut_1p", pres3s, "me")
	add(base, "pres_fut_2p", pres3s, "te")
end



local function get_imptypes_for_stem(base, stem)
	local imptypes
	if rfind(stem, "[sš][tť]$") or rfind(stem, "tř$") then
		imptypes = {"long", "short"}
	else
		-- Substitute 'ch' with a single character to make the following code simpler.
		local modstem = stem:gsub("ch", com.TEMP_CH)
		if rfind(modstem, com.cons_c .. "[lr]" .. com.cons_c .. "$") then
			-- [[trp]]; not long imperative.
			imptypes = "short"
		elseif rfind(modstem, com.cons_c .. com.cons_c .. "$") then
			imptypes = "long"
		else
			imptypes = "short"
		end
	end

	return imptypes
end


local function get_imperative_principal_part_for_imptype(base, stem, imptype)
	local sg2, sg2_2
	if imptype == "long" then
		return com.combine_stem_ending(base, "imp_2s", stem, "i")
	else
		-- See comment below at IV.1, there are rare cases where i-ě alternations occur in imperatives, e.g.
		-- [[podnítit]] impv. 'podniť ~ [rare] podněť'. 'ů' is reduced to 'u' rather than 'o', at least in
		-- [[půlit]] and derivatives.
		stem = com.apply_vowel_alternation(imptype == "short-ě" and "quant-ě" or "quant", stem, "noerror", "ring-u-to-u")
		if rfind(stem, com.velar_c .. "$") then
			return {
				{form = com.apply_first_palatalization(stem, "is verb")},
				{form = com.apply_second_palatalization(stem, "is verb"), footnotes = "[obsolescent]"},
			}
		elseif rfind(stem, "[" .. com.paired_plain .. "]$") then
			return com.apply_second_palatalization(stem, "is verb")
		else
			return stem
		end
	end
end


local function generate_default_imperative_principal_part_from_stem(base, stem)
	local imptypes = get_imptypes_for_stem(base, stem)
	return iut.flatmap_forms(iut.convert_to_general_list_form(imptypes), function(imptype)
		return get_imperative_principal_part_for_imptype(base, stem, imptype)
	end)
end


local function generate_default_imperative_principal_part(base, do_err)
	return iut.flatmap_forms(base.forms.pres_fut_3p, function(form)
		local stem = rmatch(form, "^(.*)ou$") or rmatch(form, "^(.*)í")
		if not stem then
			error("'imp:' must be given in order to specify the imperative principal part because third-person "
				.. "plural present/future '" .. form .. "' does not end in -ou or -í")
		end
		return generate_default_imperative_principal_part_from_stem(base, stem)
	end)
end


local function add_imperative(base, sg2, footnotes)
	add(base, "imp_2s", sg2, "", footnotes)
	-- "Long" imperatives end in -i
	local stem = rmatch(sg2, "^(.-)i$")
	if stem then
		add(base, "imp_1p", stem, "ěme", footnotes)
		add(base, "imp_2p", stem, "ěte", footnotes)
	elseif rfind(sg2, com.vowel_c .. "$") then
		error("Invalid 2sg imperative, ends in vowel other than -i: '" .. sg2 .. "'")
	else
		add(base, "imp_1p", sg2, "me", footnotes)
		add(base, "imp_2p", sg2, "te", footnotes)
	end
end


local function add_pres_fut(base, stems, sg1, sg2, sg3, pl1, pl2, pl3, footnotes)
	add(base, "pres_fut_1s", stems, sg1, footnotes)
	add(base, "pres_fut_2s", stems, sg2, footnotes)
	add(base, "pres_fut_3s", stems, sg3, footnotes)
	add(base, "pres_fut_1p", stems, pl1, footnotes)
	add(base, "pres_fut_2p", stems, pl2, footnotes)
	add(base, "pres_fut_3p", stems, pl3, footnotes)
end


local function generate_default_pres_tgress_principal_part(base, do_err)
	return iut.map_forms(base.forms.pres_fut_3p, function(form)
		local pref = rmatch(form, "^(.*)ou$")
		local ending
		if pref then
			ending = "a"
		else
			pref = rmatch(form, "^(.*)í")
			if pref then
				-- ě may be converted to e by com.combine_stem_ending()
				ending = "ě"
			else
				error("'prtr:' must be given in order to specify the present transgressive principal part because third-person "
					.. "plural present/future '" .. form .. "' does not end in -ou or -í")
			end
		end
		return combine_stem_ending(base, "pres_tgress_m", pref, ending)
	end)
end


local function add_pres_tgress(base)
	add(base, "pres_tgress_m", base.principal_part_forms.prtr, "")
	local prtr_stems = iut.map_forms(base.principal_part_forms.prtr, function(form)
		local pref = rmatch(form, "^(.*)a$")
		if pref then
			pref = pref .. "ou"
		end
		if not pref then
			pref = rmatch(form, "^(.*)[eě]$")
			if pref then
				pref = pref .. "í"
			end
		end
		if not pref then
			error("Unrecognized present transgressive principal part '" .. form .. "', which does not end in -a, -e or -ě")
		end
		return pref
	end)
	add(base, "pres_tgress_fn", prtr_stems, "c")
	add(base, "pres_tgress_p", prtr_stems, "ce")
end


local function generate_default_pres_act_part_principal_part(base, do_err)
	return iut.map_forms(base.forms.pres_tgress_fn, function(form)
		return form .. "í"
	end)
end


local function add_pres_act_part(base)
	add(base, "pres_act_part", base.principal_part_forms.pres_act_part, "")
end


local function add_present_e(base, stems, pres1s3p_stems, soft_stem, prtr_endings, noimp, footnotes)
	pres1s3p_stems = base.pres1s3p_stems or pres1s3p_stems or stems
	stems = base.pres_stems or stems
	local s1_ending, p3_ending
	if soft_stem then
		s1_ending = "i"
		p3_ending = "í"
	else
		s1_ending = "u"
		p3_ending = "ou"
	end
	add_pres_fut(base, stems, nil, "eš", "e", "eme", "ete", nil, footnotes)
	add_pres_fut(base, pres1s3p_stems, s1_ending, nil, nil, nil, nil, p3_ending, footnotes)
	add_pres_tgress(base, stems, prtr_endings or p3_ending, footnotes)
	if not noimp then
		add_imperative_from_present(base, pres1s3p_stems, nil, footnotes)
	end
end


local function add_present_a(base, stems, prtr_endings, noimp, footnotes)
	stems = base.pres_stems or stems
	add_pres_fut(base, stems, "ám", "áš", "á", "áme", "áte", "ají", footnotes)
	add_pres_tgress(base, stems, prtr_endings or "ají", footnotes)
	if not noimp then
		add_present_a_imperative(base, stems, footnotes)
	end
end


local function add_present_i(base, stems, noimp)
	stems = base.pres_stems or stems
	add_pres_fut(base, stems, "ím", "íš", "í", "íme", "íte", "í")
	add_pres_tgress(base, stems, "í")
	if not noimp then
		add_imperative_from_present(base, stems)
	end
end


local part_suffix_to_ending_list = {
	{"m", ""},
	{"f", "a"},
	{"n", "o"},
	{"mp_an", "i"},
	{"mp_in", "y"},
	{"fp", "y"},
	{"np", "a"},
}

local part_suffix_to_ending = {}

for _, suffix_ending in ipairs(part_suffix_to_ending_list) do
	local suffix, ending = unpack(suffix_ending)
	part_suffix_to_ending[suffix] = ending
end


-- No generate_default_past_principal_part(), which generates the past_m principal part.
-- This must be specified by each verb conjugation.


local function add_past_m(base)
	add(base, "lpart_m", base.principal_part_forms.past, "")
end


local function generate_default_past_f_principal_part(base, do_err)
	iut.map_forms(base.principal_part_forms.past, function(form)
		return form .. "a"
	end)
end


local function add_past_other_than_m(base)
	for _, suffix_ending in ipairs(part_suffix_to_ending_list) do
		local suffix, ending = unpack(suffix_ending)
		if suffix ~= "m" then
			add(base, "lpart_" .. suffix, base.principal_part_forms.pastf, ending)
		end
	end
end


local function generate_default_past_tgress_principal_part(base, do_err)
	return iut.map_forms(base.forms.lpart_m, function(form)
		local pref = rmatch(form, "^(.*)l$")
		if not pref then
			error("'patr:' must be given in order to specify the past transgressive principal part because masculine "
				.. "singular l-participle '" .. form .. "' does not end in -l")
		end
		if rfind(pref, com.vowel_c .. "$") then
			pref = pref .. "v"
		end
		return pref
	end)
end


local function add_past_tgress(base)
	-- Past transgressive not available for imperfective verbs.
	if base.aspect == "impf" then
		return
	end
	local patr_stems = base.principal_part_forms.patr
	add(base, "past_tgress_m", patr_stems, "")
	add(base, "past_tgress_fn", patr_stems, "ši")
	add(base, "past_tgress_p", patr_stems, "še")
	add(base, "past_act_part", patr_stems, "ší")
end


-- No generate_default_ppp_principal_part(), which generates the ppp_m principal part.
-- This must be specified by each verb conjugation.


local function add_ppp(base)
	if base.ppp then
		for _, suffix_ending in ipairs(part_suffix_to_ending_list) do
			local suffix, ending = unpack(suffix_ending)
			add(base, "ppp_" .. suffix, base.principal_part_forms.ppp, ending)
		end
		-- Add the long passive participle. Short participles in -án shorten to -an (e.g. 'jmenován' -> 'jmenovaný',
		--  'dělán' -> 'dělaný').
		add(base, "long_past_part", iut.map_forms(base.forms.ppp_m, function(form)
			return form:gsub("án$", "an")
		end), "ý")
	end
end


local function generate_default_vn_principal_part(base, do_err)
	-- Sometimes the vowel shortens; e.g. [[ptát]] ppp 'ptán' vn 'ptaní'; similarly [[hrát]] 'hraní', [[spát]] 'spaní',
	-- [[tkát]] 'tkaní', but not all- monosyllabic verbs, e.g. [[dbát]] ppp 'dbán' vn. 'dbání' or 'dbaní'
	-- and [[znát]] ppp. 'znán' vn. only 'znání'. These exceptional cases must be specified using overrides.
	return iut.map_forms(base.principal_part_forms.ppp, function(form)
		return form .. "í"
	end)
end


local function add_vn(base)
	add(base, "vnoun", base.principal_part_forms.vn, "")
end


--[=[
Data on how to conjugate individual rows (i.e. tense/aspect combinations, such as present indicative or
conditional).

The order listed here matters. It determines the order of generating row forms. The order must have
'inf' < 'pres' < 'sub' < 'imp' < 'negimp' because the present indicative uses the root_stressed_stem generated
by add_infinitive; the present subjunctive uses generated forms from the present indicative; the imperative uses
forms from the present subjunctive and present indicative; and the negative imperative uses forms from the infinitive
and the imperative. Similarly we must have 'fut' < 'cond' because the conditional uses the future principal part.

The following specs are allowed:

-- `desc` must be present and is an all-lowercase English description of the row. It is used in error messages and in
   generating categories of the form 'Italian verbs with irregular ROW' and 'Italian verbs with missing ROW'.
-- `tag_suffix` must be present is a string containing the {{inflection of}} tags that are appended onto the
   person/number tags to form the accelerator spec. For example, the spec "pres|sub" means that the accelerator spec
   for the third singular present subjunctive will be "3|s|pres|sub". This accelerator spec is passed to
   [[Module:inflection utilities]], which in turn passes it to [[Module:links]] when generating the link(s) for the
   corresponding verb form(s). The spec ultimately gets processed by [[Module:accel]] to generate the definition line
   for nonexistent verb forms. (FIXME: Accelerator support is currently disabled for forms with non-final accents.
   We need to change the code in [[Module:inflection utilities]] so it sets the correct target not containing the
   non-final accent.)
-- `persnums` must be present and specifies the possible person/number suffixes to add onto the row-level slot
   (e.g. "phis" for the past historic) to form the individual person/number-specific slot (e.g. "phis2s" for the
   second-person singular past historic).
-- `row_override_persnums`, if present, specifies the person/number suffixes that are specified by a row override.
   If omitted, `persnums` is used.
-- `row_override_persnums_to_full_persnums`, if present, specifies a mapping from the person/number suffixes
   specified by a row override to the person/number/suffixes used for conjugating the row. This is used, for example,
   with the subjunctive and imperfect subjunctive, where the first element of the row override specifies
   (respectively) the 123s and 12s forms, which need to be copied (respectively) to the 1s/2s/3s and 1s/3s forms.
   If omitted, no such copying happens. It's still possible for the row override persnums to disagree with the
   overall persnums. This happens, for example, with the imperative, where the 'improw:' row override spec specifies
   only the 2s and 2p forms; the remaining forms (3s, 1p, 3p) are generated during conjugation by copying from other
   forms, and can't be overridden using a row override. (They can still be overridden using a single override such
   as 'imp3s:...' or a late single override such as 'imp3s!:...'.
-- `generate_default_principal_part`, if present, should be a function of two arguments, `base` and `do_err`, and
   should return the principal part(s) for the row. The return value can be anything that is convertible to the
   "general list form" of a slot's forms, i.e. it can return a string, an object
   {form = FORM, footnotes = {FOOTNOTE, FOOTNOTE, ...}}, or a list of either. It must be present if `conjugate` is
   a table, but may be missing if `conjugate` is a function, in which case the function needs to generate the
   principal part itself or otherwise handle things differently. For example, the present indicative does not
   specify a value for `generate_default_principal_part` because there are actually two principal parts for the
   present tense (first and third singular), which are processed at the beginning of the present indicative
   `conjugate` function. Similarly, the infinitive does not specify a value for `generate_default_principal_part`
   because there is no principal part to speak of; the infinitive is generated directly from the lemma in combination
   with the slash or backslash that follows the auxiliary and (in the case of a root-stressed infinitive) the
   single-vowel spec following the backslash. If `do_err` is given to this function, the function may throw an error
   if it can't generate the principal part; otherwise it should return nil.
-- `conjugate` is either a function to conjugate the row (of two arguments, `base` and `rowslot`), or a table
   containing the endings to add onto the principal part to conjugate the row. In the latter case, there should be
   the same number of elements in the table as there are elements in `row_override_persnums` (if given) or
   `persnums` (otherwise).
-- `no_explicit_principal_part` (DOCUMENT ME)
-- `no_row_overrides` (DOCUMENT ME)
-- `no_single_overrides` (DOCUMENT ME)
-- `add_clitics` is a mandatory function of two arguments, `base` and `rowslot`, to add clitics to the forms for the
   specified row. It will only be called if `base.verb.linked_suf` is non-empty, i.e. there is a clitic to add.
-- `dont_check_defective_status` (DOCUMENT ME)
]=]
local row_conjugations = {
	{"inf", {
		desc = "infinitive",
		-- No generate_default_principal_part; handled specially in add_infinitive.
		conjugate = add_infinitive,
		no_explicit_principal_part = true, -- because handled specially using / or \ notation
		no_row_overrides = true, -- useless because there's only one form; use / or \ notation
		no_single_overrides = true, --useless because there's only one form; use / or \ notation
		add_clitics = add_infinitive_clitics,
		add_prefixed_reflexive_variants = add_non_finite_prefixed_reflexive_variants,
	}},
	{"pres", {
		desc = "present indicative",
		conjugate = add_present_indic,
		-- No setting for no_explicit_principal_part here because it would never be checked; we special-case 'pres:'
		-- overrides before checking no_explicit_principal_part. The reason for special-casing is because there are two
		-- principal parts involved, "pres" and "pres3s", and we allow both to be specified using the syntax
		-- 'pres:PRES^PRES3S'.
		add_clitics = add_finite_clitics,
	}},
	{"sub", {
		desc = "present subjunctive",
		generate_default_principal_part = generate_default_present_subj_principal_part,
		conjugate = add_present_subj,
		add_clitics = add_finite_clitics,
	}},
	{"imp", {
		desc = "imperative",
		tag_suffix = "imp",
		persnums = imp_person_number_list,
		row_override_persnums = {"2s", "2p"},
		generate_default_principal_part = generate_default_imperative_principal_part,
		conjugate = add_imperative,
		add_clitics = add_imperative_clitics,
		add_prefixed_reflexive_variants = add_imperative_prefixed_reflexive_variants,
	}},
	{"past", {
		desc = "past",
		persnums = full_person_number_list,
		generate_default_principal_part = generate_default_past_historic_principal_part,
		conjugate = add_past_historic,
		add_clitics = add_finite_clitics,
		-- Set to "builtin" because normally handled specially in PRES^PRES3S,PHIS,PP spec, but when a built-in verb
		-- is involved, we want a way of overriding the past historic (using 'phis:').
		no_explicit_principal_part = "builtin",
	}},
	{"fut", {
		desc = "future",
		tag_suffix = "fut",
		persnums = full_person_number_list,
		generate_default_principal_part = generate_default_future_principal_part,
		conjugate = {"ò", "ài", "à", "émo", "éte", "ànno"},
		add_clitics = add_finite_clitics,
	}},
	{"cond", {
		desc = "conditional",
		tag_suffix = "cond",
		persnums = full_person_number_list,
		generate_default_principal_part = generate_default_conditional_principal_part,
		conjugate = {"èi", "ésti", {"èbbe", "ébbe"}, "émmo", "éste", {"èbbero", "ébbero"}},
		add_clitics = add_finite_clitics,
	}},
	{"pp", {
		desc = "past participle",
		tag_suffix = "past|part",
		persnums = {""},
		generate_default_principal_part = generate_default_past_participle_principal_part,
		conjugate = {""},
		add_clitics = add_participle_clitics,
		-- Set to "builtin" because normally handled specially in PRES^PRES3S,PHIS,PP spec, but when a built-in verb
		-- is involved, we want a way of overriding the past participle (using 'pp:').
		no_explicit_principal_part = "builtin",
		no_row_overrides = true, -- useless because there's only one form; use the PRES^PRES3S,PHIS,PP or pp: spec
		no_single_overrides = true, --useless because there's only one form; use the PRES^PRES3S,PHIS,PP or pp: spec
	}},
	{"ger", {
		desc = "gerund",
		tag_suffix = "ger",
		persnums = {""},
		generate_default_principal_part = generate_default_gerund_principal_part,
		conjugate = {""},
		add_clitics = add_gerund_clitics,
		add_prefixed_reflexive_variants = add_non_finite_prefixed_reflexive_variants,
		no_row_overrides = true, -- useless because there's only one form; use explicit principal part
		no_single_overrides = true, -- useless because there's only one form; use explicit principal part
	}},
	{"presp", {
		desc = "present participle",
		tag_suffix = "pres|part",
		persnums = {""},
		generate_default_principal_part = generate_default_present_participle_principal_part,
		conjugate = {""},
		add_clitics = add_participle_clitics,
		no_row_overrides = true, -- useless because there's only one form; use explicit principal part
		no_single_overrides = true, -- useless because there's only one form; use explicit principal part
		-- Disable this; seems most verbs do have present participles
		-- not_defaulted = true, -- not defaulted, user has to request it explicitly
		dont_check_defective_status = true, -- this is frequently missing and doesn't indicate a defective verb
	}},
}

local row_conjugation_map = {}

for _, rowconj in ipairs(row_conjugations) do
	local rowslot, rowspec = unpack(rowconj)
	row_conjugation_map[rowslot] = rowspec
end


local function separate_stem_suffix(lemma, regex, class)
	local stem, suffix = rmatch(lemma, regex)
	if not stem and class then
		error("Unrecognized lemma for class " .. class .. ": '" .. lemma .. "'")
	end
	return stem, suffix
end


local function parse_variant_codes(run, allowed_codes, variant_type, parse_err)
	local allowed_code_set = m_table.listToSet(allowed_codes)
	local colon_separated_groups = iut.split_alternating_runs_and_strip_spaces(run, ":")
	local retval = {}
	for _, group in ipairs(colon_separated_groups) do
		for i, code in ipairs(allowed_codes) do
			allowed_codes[i] = "'" .. code .. "'"
		end
		if not allowed_code_set[group[1]] then
			parse_err(("Unrecognized variant code '%s' for %s: should be one of %s"):format(group[1], variant_type,
				m_table.serialCommaJoin(allowed_codes)))
		end
		table.insert(retval, {form = group[1], footnotes = fetch_footnotes(group)})
	end
	return retval
end


local parse = {}
local conjs = {}


--[=[ Verbs whose infinitive and present stems both end in a consonant.
1. Verbs in -k/h:
   [péct]] "to bake": pres. 'peču' (very bookish/outdated 'peku', not in IJP), 'pečeš', 'peče', 'pečeme', 'pečete',
	'pečou' (very bookish/outdated pekou)
   [[síct]] "to sow": pres. 'seču', etc. like péct
   [[téct]] "to flow": pres. 'teču', etc. like péct
   [[tlouct]] "to beat": pres. 'tluču', etc. like péct
   [[vléct]] "to drag": pres. 'vleču', etc. like péct
   [[moct]] "to be able": pres. 'mohu' (colloquial 'můžu'), 'můžeš', ..., 'můžete', 'mohou' (colloquial 'můžou')
   [[stříci se]] "to beware" (bookish, becoming obsolete except the imperative): pres. 'střehu se', 'střežeš se', ...,
	'střežete se', 'střehou se'
   These verbs have alternative bookish/obsolescent infinitives in '-ci', although 'moci' is still common and
	actually more frequent.
   Note also derivatives, e.g. [[přemoct]] "to overpower", [[pomoct]] "to help", [[obléct]] "to put on" (= ob- + vléct).
]=]

--[=[
conjs["I.1"] = function(base, lemma)
	local suffix
	local stem = separate_stem_suffix(lemma, "^(.*)c[ti]$")
	if stem then
		if not base.cons or (base.cons ~= "k" and base.cons ~= "h") then
			error("Must specify consonant as 'k' or 'h' for verbs in -ct/-ci")
		end
	end
	if not stem then
		stem, suffix = separate_stem_suffix(lemma, "^(.*)([bp])sti?$")
		if stem then
			-- [[zábsti]] (pres1s 'zebu'); obsolete [[hřébsti]] (hřebu), [[skúbsti]] (skubu), [[dlúbsti]] (dlubu),
			-- [[tépsti]] (tepu)
			local presstem = com.apply_vowel_alternation(base.ye and "quant-ě" or "quant", stem)
			add_present_e(base, presstem


			
	if last_cons == "к" or last_cons == "г" then
		error("Use class 8 for lemmas in -гти and -кти: '" .. lemma .. "'")
	end
	if last_cons == "р" then
		error("Use class 9 for lemmas in -рти: '" .. lemma .. "'")
	end
	if last_cons == "с" then
		if base.cons then
			last_cons = base.cons
		else
			error("With lemmas in -сти, must specify final consonant: '" .. lemma .. "'")
		end
	elseif base.cons then
		error("Can only specify final consonant '" .. base.cons .. "' with lemma ending in -сти: '" .. lemma .. "'")
	end
	local stressed_stem = com.maybe_stress_final_syllable(stem)
	add_present_e(base, stressed_stem .. last_cons)
	local past_msg, past_rest
	if base.cons == "д" or base.cons == "т" or base.cons == "в" then
		-- NOTE: This applies to плисти́ (with base.cons == "в") but not пливти́
		past_msg = stressed_stem .. "в"
		past_rest = stressed_stem .. "л"
	elseif base.cons == "ст" then
		past_msg = stressed_stem .. "с"
		past_rest = past_msg .. "л"
	else
		past_msg = stressed_stem .. last_cons
		past_rest = past_msg .. "л"
	end
	if base.i then
		past_msg = rsub(past_msg, "[ео](́?" .. com.cons_c .. "+)$", "і%1")
	end
	add_past(base, past_msg, past_rest)
	add_ppp(base, stressed_stem .. last_cons .. "ен")
end
]=]


--[=[
II.1 (stem ends in a consonant):

E.g.

[[blbnout]] past^(nu) (blbl ~ blbnul, blbnut, impf, vn blbnutí)
[[plácnout]] past^(nu) (plácl ~ plácnul, plácnut, plácnuv, vn plácnutí)
[[padnout]] (pf.) patr^nu^- (padl, no PPP, padnuv ~ pad, padnutí)
[[padnout]] (biasp.) patr^nu^- (padl, no PPP, padnuv ~ pad, padnutí)
[[napadnout]] ppp^n.vn^n^t.patr^nu^- (napadl, napaden, napadnuv ~ napad, napadení ~ napadnutí)
[[odpadnout]] patr^-^nu (odpadl, no PPP, odpad ~ odpadnuv, odpadnutí)
[[přepadnout]] ppp^n.vn^n^t.patr^-^nu (přepadl, přepaden, přepad ~ přepadnuv, přepadení ~ přepadnutí)
[[rozpadnout se]] patr^nu^- (rozpadl se, rozpadnut, rozpadnuv se ~ rozpad se, rozpadnutí se)
[[dopadnout]] ppp^n.vn^n^t.patr^nu^- (dopadl, dopaden, dopadnuv ~ dopad, dopadení ~ dopadnutí [VN differentiated by meaning: dopadení [přistižení = "catch?"] vs. dopadnutí [padnutí = "fall?"])
[[spadnout]] ppp^n.vn^t.patr^nu^- (spadl, spaden, spadnuv ~ spad, vn spadnutí)
[[upadnout]] ppp^n^t.vn^t.patr^-^nu (upadl, upaden ~ upadnut, upad ~ upadnuv, vn upadnutí)
[[vypadnout]] patr^-^nu (vypadl, no PPP, vypad ~ vypadnuv, vn vypadnutí)
[[ukradnout]] - (ukradl, ukradnut, ukradnuv, ukradnutí) [but IJP commentary says 'ukraden' and 'ukradení' still in use]
[[chřadnout]] - (chřadl, no PPP, impf, chřadnutí)
[[blednout]] - (bledl, no PPP, impv, blednutí)
[[sednout]] - (sedl, no PPP, sednuv, sednutí)
[[usednout]] - (usedl, usednut, usednuv, usednutí)
[[nastydnout]] - (nastydl, nastydnut, nastydnuv, nastydnutí)
[[sládnout]] - (sládl, no PPP, impf, sládnutí)
[[vládnout]] - (vladl, no PPP, impf, vn vládnutí)
[[zvládnout]] - (zvládl, zvládnut, zvládnuv, vn zvládnutí)
[[rafnout]] past^(nu) (rafl ~ rafnul, rafnut, rafnuv, vn rafnutí)
[[hnout]] past^nu (hnul, hnut, hnuv, hnutí), same for [[pohnout]]
[[nadchnout]] past^(nu).ppp^t^n (nadchl ~ nadchnul, nadchnut ~ nadšen, nadchnuv, nadchnutí ~ nadšení)
[[schnout]] - (schnul, schnut, impf, vn schnutí)
[[uschnout]] past^(nu) (uschl ~ uschnul, uschnut, uschnuv, vn uschnutí)
[[vyschnout]] past^(nu) (vyschl ~ vyschnul, no PPP, vyschnuv, vn vyschnutí)
[[bouchnout]] past^(nu) (bouchl ~ bouchnul, bouchnut, bouchnuv, vn bouchnutí)
[[oblehnout]] ppp^n (oblehl, obležen, oblehnuv, obležení)
[[stihnout]] ppp^n.vn^t^n (stihl, stižen, stihnuv, stihnutí ~ stižení)
[[zastihnout]] ppp^n (zastihl, zastižen, zastihnuv, zastižení)
[[zdvihnout]] ppp^n.vn^t^n (zdvihl, zdvižen, zdvihnuv, vn zdvihnutí ~ zdvižení)
[[pozdvihnout]] ppp^n (pozdvihl, pozdvižen, pozdvihnuv, vn pozdvižení)
[[střihnout]] ppp^n.vn^t^n (střihl, střižen, střihnuv, střihnutí ~ střižení)
[[ustřihnout]] ppp^n.vn^t^n (ustřihl, ustřižen, ustřihnuv, ustřihnutí ~ ustřižení)
[[trhnout]] past^(nu).ppp^t^n (trhl ~ trhnul, trhnut ~ tržen, trhnuv, trhnutí ~ tržení)
[[podtrhnout]] past^(nu).ppp^n.vn^t or past^(nu).ppp^n [by meaning] (podtrhl ~ podtrhnul, podtržen, podtrhnuv, podtrhnutí ~ podtržení [VN differentiated by meaning: podtrhnutí [of a chair], podtržení [of words]])
[[roztrhnout]] ppp^n (roztrhl, roztržen, roztrhnuv, roztržení)
[[vrhnout]] past^(nu).ppp^t^n (vrhl ~ vrhnul, vrhnut ~ vržen, vrhnuv, vn vrhnutí ~ vržení)
[[svrhnout]] ppp^n (svrhl, svržen, svrhnuv, svržení)
[[vyvrhnout]] past^(nu) or past^(nu).ppp^n [by meaning] (vyvhrl ~ vyvrhnul, vyvrhnut ~ vyvržen [PPP differentiated by meaning: 'vyvržen' [ejected (from society)], 'vyvrhnut' [published]], vyvrhnuv, vyvrhnutí ~ vyvržení)
[[sáhnout]] - (sáhl, no PPP, sáhnuv, sáhnutí)
[[zasáhnout]] ppp^n.vn^t^n (zasáhl, zasažen, zasáhnuv, zasáhnuti ~ zasažení)
[[obsáhnout]] - or ppp^n [by meaning] (obsáhl, obsáhnut ~ obsažen [differentiated by meaning], obsáhnuv, obsáhnutí ~ obsažení)
[[přesáhnout]] ppp^n.vn^t (přesáhl, přesažen, přesáhnuv, přesáhnutí)
[[dosáhnout]] ppp^n (dosáhl, dosažen, dosáhnuv, dosažení)
[[táhnout]] ppp^n (táhl, tažen, impv, tažení)
[[zatáhnout]] ppp^n (zatáhl, zatažen, zatáhnuv, zatažení)
[[vytáhnout]] ppp^n (vytáhl, vytažen, vytáhnuv, vytažení)
[[napřáhnout]] ppp^n.vn^t (napřáhl, napřažen, napřáhnuv, napřáhnutí)
[[přeřeknout se]] ppp^n^t.vn^t (přeřekl se, přeřečen ~ přeřeknut, přeřeknuv se, přeřeknutí se)
[[křiknout]] past^(nu) (křikl ~ křiknul, no PPP, křiknuv, vn křiknutí)
[[polknout]] past^(nu) (polkl ~ polknul, polknut, polknuv, vn polknutí)
[[zamknout]] ppp^n^t (zamkl, zamčen ~ zamknut, zamknuv, zamčení ~ zamknutí)
[[obemknout]] - (obemkl, obemknut, obemknuv, obemknutí)
[[odemknout]] ppp^n^t (odemkl, odemčen ~ odemknut, odemknuv, odemčení ~ odemknutí)
[[semknout]] patr^- (semkl, semknut, semk, semknutí)
[[přimknout]] - (přimkl, přimknut, přimknuv, přimknutí)
[[vymknout]] - (vymkl, vymknut, vymknuv, vymknutí)
[[cinknout]] past^(nu) (cinkl ~ cinknul, cinknut, cinknuv, vn cinknutí)
[[fnrknout]] past^(nu) (fnrkl ~ fnrknul, no PPP, fnrknuv, fnrknutí)
[[prasknout]] past^(nu) (praskl ~ prasknul, prasknut, prasknuv, vn prasknutí)
[[tisknout]] - or ppp^n [by meaning] (tiskl, tisknut ~ tištěn [differentiated by meaning], impf, tisknutí ~ tištění)
[[stisknout]] - (stiskl, stisknut, stisknuv, stisknutí)
[[vytisknout]] past^(nu).ppp^t^n (vytiskl ~ vytisknul, vytisknut ~ vytištěn, vytisknuv, vytisknutí ~ vytištění)
[[blýsknout]] past^(nu) (blýskl ~ blýsknul, no PPP, blýsknuv, blýsknutí)
[[tknout se]] - (tknul se, tknut [PPP with reflexive], tknuv se, vn tknutí)
[[dotknout se]] past^(nu).ppp^n^t.vn^t (dotkl se ~ dotknul se, dotčen ~ dotknut [PPP with reflexive], dotknuv se, vn dotknutí)
[[vytknout]] past^(nu).ppp^n^t (vytkl ~ vytknul, vytčen ~ vytknut, vytknuv, vn vytčení ~ vytknutí); same for [[protknout]], [[zatknout]]
[[kouknout]] past^(nu) (koukl ~ kouknul, no PPP, kouknuv, kouknutí)
[[nařknout]] ppp^n^t (nařkl, nařčen ~ nařknut, nařknuv, nařčení ~ nařknutí)
[[přiřknout]] ppp^n^t (přiřkl, přiřčen ~ přiřknut, přiřknuv, přiřčení ~ přiřknutí)
[[uřknout]] - (uřkl, uřknut, uřknuv, uřknutí)
[[vyřknout]] ppp^n^t (vyřkl, vyřčen ~ vyřknut, vyřknuv, vyřčení ~ vyřknutí)
[[obléknout]] [also oblíknout ~ obléct ~ obléci ~ oblíct] ppp^n^t (oblékl, oblečen ~ obléknut, obléknuv, oblečení ~ obléknutí [VN differentiated by meaning: oblečení [of clothing], obléknutí])
[[vléknout]] [obsolete for vléct per IJP]
[[navléknout]] [also navlíknout ~ navléct ~ navléci ~ navlíct] ppp^n^t.vn^t (navlékl, navlečen ~ navléknut, navléknuv, navléknutí)
[[převléknout]] [also převlíknout ~ převléct ~ převléci ~ převlíct] ?? ppp^n^t.vn^n vs. ppp^n^t.vn^t [by meaning; unless PPP is also distinguished by meaning] (převlékl ~ převléknul, převlečen ~ převléknut, převléknuv, převlečení ~ převléknutí [VN differentiated by meaning: převlečení [disguise] ~ převléknutí [change clothing]])
[[svléknout]] [also svlíknout ~ svléct ~ svléci ~ svlíct] past^(nu).ppp^n^t (svlékl ~ svléknul, svlečen ~ svléknut, svléknuv, svlečení ~ svléknutí)
[[oblíknout]] - (oblíkl, oblíknut, oblíknuv, oblíknutí)
[[lnout]] past^nu (lnul, no PPP, impf, lnutí)
[[přilnout]] past^nu (přilnul, přilnut, přilnuv, přilnutí)
[[povšimnout si]] - (povšiml si, povšimnut si, povšimnuv si, povšimnutí)
[[klapnout]] past^(nu) (klapl ~ klapnul, klapnut, klapnuv, vn klapnutí)
[[klepnout]] past^(nu) (klepl ~ klepnul, klepnut, klepnuv, vn klepnutí)
[[dupnout]] past^(nu) (dupl ~ dupnul, no PPP, dupnuv, vn dupnutí)
[[vyhoupnout se]] past^(nu) (vyhoupl se ~ vyhoupnul se, vyhoupnut [WHAT DOES A REFLEXIVE VERB WITH PPP MEAN?], vyhoupnuv se, vyhoupnutí)
[[křupnout]] past^(nu) (křupl ~ křupnul, křupnut, křupnuv, křupnutí)
[[stárnout]] - (stárl, no PPP, impf, stárnutí)
[[užasnout]] - (užasl, no PPP, užasnuv, užasnutí)
[[zesnout]] past^nu (zesnul, no PPP, zesnuv, zesnutí)
[[smlsnout]] past^(nu) (smlsl ~ smlsnul, no PPP, impf, vn smlsnutí)
[[usnout]] past^nu (usnul, usnut, usnuv, usnutí)
[[bohatnout]] - (bohatl, no PPP, impf, bohatnutí)
[[procitnout]] - (procitl, no PPP, procitnuv, procitnutí)
[[zhltnout]] past^(nu) (zhltl ~ zhltnul, zhltnut, zhltnuv, vn zhltnutí)
[[škrtnout]] past^(nu) (šrktl ~ šrktnul, škrtnout, šrktnuv, vn šrktnutí)
[[zvrtnout]] past^(nu) (zvrtl ~ zvrtnul, zvrtnut, zvrtnuv, vn zvrtnutí)
[[couvnout]] past^(nu) (couvl ~ couvnul, no PPP, couvnuv, vn couvnutí)
[[naleznout]] [not in IJP] [but IJP commentary says 'nalezen' and 'nalezení' still in use]
[[vynaleznout]] [not in IJP] [but IJP commentary says 'vynalezen' and 'vynalezení' still in use]
[[mrznout]] past^(nu) (mrzl ~ mrznul, mrznut, impf, vn mrznutí)
[[uváznout]] past^(nu) (uvázl ~ uváznul, no PPP, uváznuv, uváznutí)
[[říznout]] past^(nu) (řízl ~ říznul, říznut, říznuv, říznutí), same for [[vyříznout]], [[doříznout]], [[naříznout]], [[rozříznout]])

Per IJP, the past transgressive always ends in -nuv, and the endingless forms are totally obsolete.
(But not always it seems, see above.)

Variation:

Past: -l, -nul, -l ~ -nul: - nu (nu) [must be specified]
PPP: -nut, -en, -nut ~ -en, -en ~ -nut: t n t:n n:t [defaults to t]
past tgress: -nuv, null, -nuv ~ null, null ~ -nuv: nu - nu:- -:nu [defaults to nu]
vn: -nutí, -ení, -nutí ~ -ení, -ení ~ -nutí: defaults to same as PPP, or t if no PPP
]=]

conj["II.1"] = {
	get_infstem = function(base)
		return separate_stem_suffix(lemma, "^(.*)nout$", "II.1")
	end,
	pres1s = "n",
	imp = "ni",
	ppp = {
		choices = {"l", "nul", "(nu)"},
		default = "iot",
		generate_part = function(base, variant)
			if variant == "iot" then
				local iotated_stem = com.iotate(base.infstem)
				return com.combine_stem_ending(base, "ppp_m", iotated_stem, "en")
			elseif variant == "ni" then
				return com.combine_stem_ending(base, "ppp_m", base.infstem, "en")
			else
				error("Internal error: Saw unrecognized PPP variant code '" .. variant .. "'")
			end
		end,
	},
	past = "il",
	ppp = {
		choices = {"iot", "ni"},
		default = "iot",
		generate_part = function(base, variant)
			if variant == "iot" then
				local iotated_stem = com.iotate(base.infstem)
				return com.combine_stem_ending(base, "ppp_m", iotated_stem, "en")
			elseif variant == "ni" then
				return com.combine_stem_ending(base, "ppp_m", base.infstem, "en")
			else
				error("Internal error: Saw unrecognized PPP variant code '" .. variant .. "'")
			end
		end,
	},
}

parse["II.1"] = function(base, conjmod_run, parse_err)
	local separated_groups = iut.split_alternating_runs_and_strip_spaces(conjmod_run, "[/,]", "preserve splitchar")
	local past_conjmod = separated_groups[1]
	local ppp_conjmod, vn_conjmod, ptr_conjmod
	local expected_ptr_index = 3
	if #separated_groups > 1 and separated_groups[2][1] == "/" then
		ppp_conjmod = separated_groups[3]
		expected_ptr_index = 5
		if #separated_groups > 3 and separated_groups[4][1] == "/" then
			vn_conjmod = separated_groups[5]
			expected_ptr_index = 7
		end
	end
	if #separated_groups >= expected_ptr_index then
		local ptr_separator = separated_groups[expected_ptr_index - 1][1]
		if ptr_separator ~= "," then
			parse_err("Expected a comma as separator for past transgressive but saw '" .. ptr_separator .. "'")
		end
		ptr_conjmod = separated_groups[expected_ptr_index]
		if #separated_groups > expected_ptr_index then
			parse_err("Junk at end of II.1 conjugation modifier spec, after past transgressive spec")
		end
	end

	base.past_stem = parse_variant_codes(past_conjmod, {"-", "nu", "(nu)"}, "II.1 past tense", parse_err)
	if not ppp_conjmod then
		base.ppp_stem = {{form = "t"}}
	else
		base.ppp_stem = parse_variant_codes(ppp_conjmod, {"", "t", "n"}, "II.1 past passive participle", parse_err)
	end
	if not vn_conjmod then
		base.vn_stem = {{form = "t"}}
	else
		base.vn_stem = parse_variant_codes(ppp_conjmod, {"", "t", "n"}, "II.1 verbal noun", parse_err)
	end
	if not vn_conjmod then
		base.ptr_stem = {{form = "nu"}}
	else
		base.ptr_stem = parse_variant_codes(ppp_conjmod, {"-", "nu"}, "II.1 past transgressive", parse_err)
	end
end


conjs["II.1"] = function(base, lemma)
	local stem = separate_stem_suffix(lemma, "^(.*)nout$", "II.1")

	local function shorten_and_palatalize(stem)
		local begin, lastv, lastc = rmatch(stem, "^(.*)(" .. com.vowel_c .. ")(" .. com.cons_c .. "*)$")
		if not begin then
			begin = ""
			lastv = ""
			lastc = stem
		elseif lastv == "á" then
			-- [[vytáhnout]] -> 'vytažen'
			lastv = "a"
		elseif lastv == "é" then
			-- [[obléknout]] -> 'oblečen'
			-- FIXME, do we need to convert ň/ť/ď to plain versions and vowel to ě?
			lastv = "e"
		end
		-- [[tisknout]] -> 'tištěn'
		-- [[nadchnout]] -> 'nadšen'
		-- [[práhnout]] -> 'pražen'
		-- [[vléknout]] -> 'vlečen'
		lastc = com.apply_first_palatalization(lastc, "verb")
		return begin .. lastv .. lastc
	end

	-- Normalize the codes computed by the parse function above.

	-- First the past tense. We need to expand "(nu)" to "-" and "nu", then normalize appropriately.
	local saw_paren_nu = false
	for _, formobj in ipairs(base.past_stem) do
		if formobj.form == "(nu)" then
			saw_paren_nu = true
			break
		end
	end
	local expanded_past
	if saw_paren_nu then
		expanded_past = {}
		for _, formobj in ipairs(base.past_stem) do
			if formobj.form == "(nu)" then
				table.insert(expanded_past, {form = "-", footnotes = formobj.footnotes})
				table.insert(expanded_past, {form = "nu", footnotes = formobj.footnotes})
			else
				table.insert(expanded_past, formobj)
			end
		end
	else
		 expanded_past = base.past_stem
	end
	for _, formobj in ipairs(expanded_past) do
		if formobj.form == "-" then
			formobj.form = stem
		elseif formobj.form == "nu" then
			formobj.form = stem .. "nu"
		else
			error("Internal error: Saw unrecognized past tense variant code '" .. formobj.form .. "'")
		end
	end

	local function normalize_ppp_and_vn(stems, stem_type)
		for _, formobj in ipairs(stems) do
			if formobj.form == "t" then
				formobj.form = stem .. "nut"
			elseif formobj.form == "n" then
				-- We need to call combine_stem_ending() in case of e.g. [[tisknout]] -> stem 'tišť' -> 'tištěn'.
				formobj.form = com.combine_stem_ending(base, nil, shorten_and_palatalize(stem), "en")
			else
				error("Internal error: Saw unrecognized " .. stem_type .. " variant code '" .. formobj.form .. "'")
			end
		end
	end

	-- Now the PPP and verbal noun.
	normalize_ppp_and_vn(base.ppp_stem, "PPP")
	normalize_ppp_and_vn(base.vn_stem, "verbal noun")

	-- Now the past transgressive.
	for _, formobj in ipairs(base.ptr_stem) do
		if formobj.form == "-" then
			formobj.form = stem
		elseif formobj.form == "nu" then
			formobj.form = stem .. "nuv"
		else
			error("Internal error: Saw unrecognized past transgressive variant code '" .. formobj.form .. "'")
		end
	end

	add_present_e(base, stem .. "n")
	-- Masculine singular past may have -l, -nul or both, but other forms of the past generally only have -l.
	add_past(base, expanded_past, stem, base.ptr_stem)
	add_ppp(base, base.ppp_stem, base.vn_stem)
end


--[=[
E.g.:

[[krýt]] (kryji ~ kryju [colloquial], kryl, kryt, kryje, krytí)
[[zakrýt]] (zakryji ~ zakryju [colloquial], zakryl, zakryt, zakryv, zakrytí)
[[mýt]], [[nýt]], [[rýt]], [[týt]], [[výt]] (essentially like 'krýt')
[[pít]] (piji ~ piju [colloquial], pil, pit, impf, pití)
[[vypít]] (vypiji ~ vypiju [colloquial], vypil, vypit, vypiv, vypití)
[[bít]] (biji ~ etc., bil, bit, impf, bití)
[[rozbít]] (rozbiji, rozbil, rozbit, rozbiv, rozbití)
[[šít]] (šiji, šil, šit, impf, šití)
[[sešít]] (sešiji, sešil, sešit, sešiv, sešití) [not in IJP]
[[žít]] (žiji, žil, žit, impf, žití)
[[prožít]] (prožiji, prožil, prožit, proživ, prožití)
[[klít]] (kleji, klel, klet, impf, kletí ~ klení)
[[proklít]] (prokleji, proklel, proklet, proklev, prokletí)
[[dout]] (duji, dul, no PPP, impf, dutí)
[[zadout]] (zaduji, zadul, no PPP, zaduv, zadutí)
[[lát]] (laji [impv laj, prtg laje], lál, lán, impf, lání)
[[vylát]] (vylaji [impv vylaj], vylál, vylán, vyláv, vylání) [not in IJP]
[[tát]] (taji [impv taj, prtg taje], tál, no PPP [given as tán (tát) in Wikipedia], impf, tání)
[[roztát]] (roztaji [impv roztaj, prtg roztaje], roztál, roztán, roztáv, roztání)
[[přát]] (přeji [impv přej, prtg přeje], přál, no PPP, impf, přání)
[[popřát]] (popřeji [impv popřej], popřál, popřán, popřáv, popřání)
[[smát se]] (směji se [impv směj se, prtg směje se], smál se, no PPP [given as smán (smát) in Wikipedia], impf, no VN [given as smání (smátí) in Wikipedia])
[[usmát se]] (usměji se [impv usměj se], usmál se, no PPP, usmáv [NOTE: IJP has usmav but this appears to be a mistake], usmání)
[[nasmát se]] [biaspectual: also prtg 'nasměje se'] (nasměji se [impv nasměj se], nasmál se, no PPP, nasmáv, nasmání)
[[sít]] (seji [impv sej, prtg seje], sel ~ sil, set, impf, setí)
[[zasít]] (zaseji [impv zasej], zasel ~ zasil, zaset, zasev, zasetí)
[[vát]] (věji [impv věj, prtg věje], vál, no PPP [given as ván (vát) in Wikipedia], impf, vání [vátí is very rare])
[[navát]] (navěji [impv navěj, prtg navěje], navál, navát, naváv, navátí)
[[hrát]] (hraji [impv hraj ~ hrej [bookish]], hrál, hrán, hraje, hraní [NOTE: short vowel])

Variation:

* Present tense shortens infinitive vowel: ý -> y; ou -> u; í -> i or ě; á -> a or ě.
* Past tense vowel matches present vowel unless infinitive has á, in which case past tense has á.
* PPP has -t unless infinitive has á, in which case PPP may have -n or -t (or maybe both).
* PPP stem usually matches past stem; but note [[sít]], [[zasít]] with past stem '(za)sel ~ (za)sil' but PPP only
  (za)set. Similarly for past transgressive.
* No instances I can see of verbal noun stem disagreeing with PPP stem.

Variant codes:
- Use 'n' for PPP in -n, 't' for PPP in '-t', 'n:t' or 't:n' for both (defaults to 'n'):
* [[lát]], [[vylát]]: III.1
* [[tát]], [[roztát]]: III.1
* [[přát]], [[popřát]]: III.1.pres^ě
* [[smát se]]: III.1.pres^ě.vn:-
* [[usmát se]], [[nasmát se]]: III.1.ppp^n.ě
* [[vát]]: III.1.ppp^n^t[rare]
* [[navát]]: III.1.ppp^t

]=]
parse["III.1"] = function(base, conjmod_run, parse_err)
	base.ppp_stem = parse_variant_codes(conjmod_run, {"", "t", "n"}, "III.1 past passive participle", parse_err)
end


conjs["III.1"] = function(base, lemma)
	local stem = separate_stem_suffix(lemma, "^(.*)t$", "III.1")
	local begin, lastv = rmatch(stem, "^(.*)(ou)$")
	if not begin then
		begin, lastv = rmatch(stem, "^(.*)([áíý])$")
	end
	if not begin then
		error("Unrecognized lemma for class III.1: '" .. lemma .. "' (should end in -át, -ít, -ýt or -out)")
	end
	local pres_stem = com.apply_vowel_alternation(base.ye and "quant-ě" or "quant", stem)
	local past_stem = lastv == "á" and stem or pres_stem

	-- Normalize the codes computed by the parse function above.
	if not base.ppp_stem then
		base.ppp_stem = {{form = lastv == "á" and "n" or "t"}}
	end
	for _, formobj in ipairs(base.ppp_stem) do
		formobj.form = past_stem .. formobj.form
	end

	add_present_e(base, pres_stem .. "j", nil, "soft")
	add_present_e(base, {}, pres_stem .. "j", false, {}, "noimp", "[colloquial]")
	add_past(base, past_stem)
	add_ppp(base, base.ppp_stem)
end


--[=[
III.2:

[[darovat]] "to donate"
[[sledovat]] "to follow"
[[konstruovat]] "to construct"
]=]
conjs["III.2"] = {
	get_infstem = function(base)
		return separate_stem_suffix(base.lemma, "^(.*)ovat$", "III.2")
	end,
	pres1s = {{form = "uji"}, {form = "uju", footnotes = {"[colloquial]"}}},
	prtr = "ě",
	past = "oval",
	ppp = "ován",
}


--[=[
IV.1:

[[prosit]] (prosím, pros, prosil, prošen, prose, prošení)
[[vyprosit]] (vyprosím, vypros, vyprosil, vyprošen, vyprosiv, vyprošení)
[[nosit]] (nosím, nos, nosil, nošen, nose, nošení)
[[nanosit]] (nanosím, nanos, nanosil, nanošen, nanosiv, nanošení)
[[spasit]] (spasím, spas, spasil, spasen, spasiv, spasení)
[[cizopasit]] (cizopasím, cizopas, cizopasil, cizopasen, cizopase, cizopasení)
[[vozit]] (vozím, voz, vozil, vožen ~ vozen, voze, vožení ~ vození)
[[navozit]] (navozím, navoz, navozil, navozen ~ navožen, navoziv, navození ~ navožení)
[[chodit]] (chodím, choď, chodil, chozen, chodě, chození)
[[nachodit]] (nachodím, nachoď, nachodil, nachozen, nachodiv, nachození)
[[platit]] (platím, plať, platil, placen, platě, placení)
[[naplatit]] (naplatím, naplať, naplatil, naplacen, zaplativ, naplacení)
[[čistit]] (čistím, čisť ~ čisti, čistil, čištěn ~ čistěn, čistě, čištění ~ čistění)
[[vyčistit]] (vyčistím, vyčisť ~ vyčisti, vyčistil, vyčištěn ~ vyčistěn, vyčistiv, vyčištění ~ vyčistění)
[[pustit]] (pustím, pusť, pustil, puštěn, pustiv, puštění)
[[napustit]] (napustím, napusť, napustil, napuštěn, napustiv, napuštění)
[[vábit]] (vábím, vab ~ vábi, vábil, váben, vábě, vábení)
[[učit]] (učím, uč, učil, učen, uče, učení)
[[křivdit]] (křivdím, křivdi ~ křivď, křivdil, křivděn, křivdě, křivdění)
[[mírnit]] (mírním, mírni, mírnil, mírněn, mírně, mírnění)
[[jezdit]] (jezdím, jezdi, jezdil, ježděn ~ jezděn, jezdě, ježdění ~ jezdění)
[[zpozdit]] (zpozdím, zpozdi, zpozdil, zpožděn, zpozdiv, zpoždění)
[[zaostřit]] (zaostřím, zaostři, zaostřil, zaostřen, zaostřiv, zaostření)
[[zvětšit]] (zvětším, zvětši, zvětšil, zvětšen, zvětšiv, zvětšení)
[[oprostit]] (oprostím, oprosti ~ oprosť, oprostil, oproštěn, oprostiv, oproštění)
[[roztříštit]] (roztříštím, roztříšti, roztříštil, roztříštěn, roztříštiv, roztříštění) [NOTE: SSJC says impv roztříšť or roztříšti; IJP's commentary also says this should be the case]
[[opatřit]] (opatřím, opatři ~ opatř, opatřil, opatřen, opatřiv, opatření)
[[brzdit]] (brzdím, brzdi ~ brzď, brzdil, brzděn ~ bržděn, brzdě, brzdění ~ brždění)
[[přesvědčit]] (přesvědčím, přesvědči ~ přesvědč, přesvědčil, přesvědčen, přesvědčiv, přesvědčení)
[[léčit]] (léčím, léči ~ leč, léčil, léčen, léče, léčení)
[[loudit]] (loudím, loudi ~ luď, loudil, louděn, loudě, loudění)
[[toužit]] (toužím, touži ~ tuž, toužil, toužen, touže, toužení)
[[soudit]] (soudím, suď, soudil, souzen, soudě, souzení)
[[koupit]] (koupím, kup, koupil, koupen, koupiv, koupení)
[[rdousit]] (rdousím, rdousi ~ rdus, rdousil, rdoušen, rdouse, rdoušení)
[[bloudit]] (bloudím, bloudi ~ bluď, bloudil, no PPP, bloudě, bloudění)
[[vrátit]] (vrátím, vrať, vrátil, vrácen, vrátiv, vrácení)
[[přiblížit]] (přiblížím, přibliž, přiblížil, přiblížen, přiblíživ, přiblížení)
[[lícit]] (lícím, líci ~ lic, lícil, no PPP, líce, lícení)
[[čepýřit]] (čepýřím, čepyř ~ čepýři, čepýřil, čepýřen, čepýře, čepýření)
[[chýlit]] (chýlím, chyl, chýlil, chýlen, chýle, chýlení)
[[půlit]] (půlím, pul, půlil, půlen, půle, půlení)
[[půjčit]] (půjčím, půjč [IRREG], půjčil, půjčen, půjčiv, půjčení)
[[trůnit]] (trůním, trůni, trůnil, no PPP, trůně, trůnění)
[[podnítit]] (podnítím, podniť ~ [rare] podněť, podnítil, podnícen, podnítiv, podnícení)
[[vštípit]] (vštípím, vštěp ~ vštip, vštípil, vštípen, vštípiv, vštípení)

In -ít:

[[ctít]] (ctím, cti, ctil, ctěn, ctě, ctění)
[[clít]] (clím, cli, clil, clen, cle, clení)
[[dštít]] (dštím, dšti, dštil, dštěn, dště, dštění)
[[pohřbít]] (pohřbím, pohřbi, pohřbil, pohřben, pohřbiv, pohřbení)
[[křtít]] (křtím, křti, křtil, křtěn, křtě, křtění)
[[obelstít]] (obelstím, obelsti, obelstil, obelstěn, obelstiv, obelstění)
[[mdlít]] (mdlím, mdli, mdlil, no PPP?, mdle, mdlení) [NOTE: also mdlít IV.2]
[[mstít]] (mstím, msti, mstil, mstěn, mstě, mstění)
[[mžít]] (mžím, mži, mžil, mžen, mže, mžení)



Variation:

* Imperative can have long forms, short forms or both. Defaults to both forms if stem ends in -st, -št or -tř, otherwise
  long forms if stem ends in two or more consonants, otherwise short forms. Short form imperative also shortens long
  stem vowels (á é í ý ů ou -> a e i y u u); but note půjčit, with imperative půjč. Alternations between í and ě seem
  rare; SSJC mentions [[nítit]] and [[podnítit]] with imperative (pod)niť or rarely (pod)něť; IJP has both and mentions
  that the latter is rare. SSJC also mentions [[svítit]] with imperative sviť or rarely svěť but IJP says only sviť.
  SSJC also mentions [[vštípit]] with imperative vštěp or vštip; IJP agrees. SSJC mentions [[vybílit]] with imperative
  vybil or rarely vyběl; IJP says imperative vybil or vybíli. SSJC has two [[vytížit]] verbs, the second of which has
  imperative only vytěž; IJP doesn't include this verb, only the first [[vytížit]].
* PPP's usually iotate coronals: s -> š, z -> ž, st -> šť, zd -> žď, t -> c, d -> z, n -> ň (velars and plain r do not
  generally occur as the last stem consonant). But not always, and sometimes both iotated and non-iotated variants
  occur. When non-iotated variants of t/d occur, they are palatalized, e.g. [[loudit]] -> 'louděn', [[ctit]] -> 'ctěn'.

Variant codes:
- Use 'long' for long imperative, 'short' for short imperative, 'long:short' or 'short:long' for both (default as above).
- Use 'iot' for iotated PPP, 'ni' for non-iotated PPP, 'iot:ni' or 'ni:iot' for both (default is iotated).
* [[čistit]]: IV.1.imp^short^long.ppp^iot^ni
* [[brzdit]]: IV.1.imp^long^short.ppp^ni^iot
* [[spasit]]: IV.1.ppp^ni
* [[vozit]]: IV.1.ppp^iot^ni
* [[loudit]]: IV.1.imp^long^short.ppp^ni

]=]

conj["IV.1"] = {
	get_infstem = function(base)
		-- Some with -ít e.g. [[pohřbít]]
		local infstem = separate_stem_suffix(base.lemma, "^(.*)[ií]t$", "IV.1")
		return com.convert_paired_plain_to_palatal(infstem)
	end,
	pres1s = "ím",
	imp = {
		choices = {"long", "short", "short-ě"},
		default = function(base)
			return get_imptypes_for_stem(base, base.infstem)
		end,
		generate_part = function(base, variant)
			return get_imperative_principal_part_for_imptype(base, base.infstem, variant)
		end,
	},
	past = "il",
	ppp = {
		choices = {"iot", "ni"},
		default = "iot",
		generate_part = function(base, variant)
			if variant == "iot" then
				local iotated_stem = com.iotate(base.infstem)
				return com.combine_stem_ending(base, "ppp_m", iotated_stem, "en")
			elseif variant == "ni" then
				return com.combine_stem_ending(base, "ppp_m", base.infstem, "en")
			else
				error("Internal error: Saw unrecognized PPP variant code '" .. variant .. "'")
			end
		end,
	},
}


--[=[
IV.2:

[[bdít]] (bdím # bdí, bdi, bděl, bděn, bdě, bdění)
[[čnět]] ~ [[čnít]] (čním # čnějí ~ ční, čni, čněl, čněn, čněje ~ čně, čnění)
[[čpět]] ~ [[čpít]] (čpím # čpí, čpi, čpěl, čpěn, čpě, čpění)
[[dít]] [biasp] (dím # dějí, děj, děl, no PPP, děje / děv, no VN)
[[dlít]] (dlím # dlejí ~ dlí, dli, dlel, dlen, dle ~ dleje, dlení)
[[hřmět]] ~ [[hřmít]] (hřmím # hřmí ~ hřmějí, hřmi, hřměl, no PPP, hřmě ~ presumably hřměje, hřmění)
[[lpět]] ~ [[lpít]] (lpím # lpějí ~ lpí, lpi ~ lpěj, lpěl, lpěn, lpěje ~ lpě, lpění)
[[mdlít]] (mdlím # mdlejí ~ mdlí, mdli, mdlel, no PPP?, mdleje ~ presumably mdle, mdlení) [NOTE: also mdlít IV.1]
etc.

[[trpět]] [default] (trpím # trpí, trp, trpěl, trpěn, trpě, trpění)
[[bolet]] /preslong:short (bolím # bolejí ~ bolí, bol, bolel, no PPP, boleje ~ bole, bolení)
[[hovět]] /preslong:short,implong:short (hovím # hovějí ~ hoví, hověj ~ hov, hověl, no PPP, hověje ~ hově, hovění)
[[náležet]] (náležím # náležejí ~ náleží, náležej ~ nálež, náležel, náležen, náleže ~ náležeje, náležení)
[[souviset]] (souvisím # souvisejí ~ souvisí, souvisej, souvisel, no PPP, souvise ~ souviseje, souvisení)
[[šumět]] (šumím # šumějí ~ šumí, šuměj ~ šum, šuměl, no PPP, šuměje ~ šumě, šumění)
[[večeřet]] (večeřím # večeřejí ~ večeří, večeř, večeřel, no PPP, večeře, večeření)
[[záviset]] (závisím # závisejí ~ závisí, závisej, závisel, no PPP, závise ~ záviseje, závisení)
[[zmizet]] (zmizím # zmizejí ~ zmizí, zmiz, zmizel, zmizen, zmizev, zmizení)
[[čumět]] (čumím # čumějí ~ čumí, čum, čuměl, no PPP, čuměje ~ čumě, čumění)
[[slyšet]] (slyším # slyší, slyš ~ poslyš ["perceive by hearing"], slyšel, slyšen, slyše, slyšení)
[[běžet]] (běžím # běží, běž ~ poběž, běžel, běžen, běže, běžení; fut. poběžím ... poběží)
[[letet]] (letím # letí, leť ~ poleť, letěl, letěn, letě, letění; fut. poletím ... poletí)
[[vidět]] (vidím # vidí, viz [IRREG], viděl, viděn, vida [IRREG], vidění)

]=]


--[=[
[[dělat]] "to do" V.1
[[konat]] "to act" V.1
[[chovat]] "to behave" V.1
[[doufat]] "to hope" V.1
[[ptát se]] "to ask" (vn 'ptaní se') V.1.vn:ptaní
[[dbát]] "to care" (vn 'dbání ~ dbaní') V.1.vn:dbání:dbaní
[[zanedbat]] "to neglect" V.1
[[znát]] "to know" V.1
[[poznat]] "to know (pf.)" V.1
[[poznávat]] "to know (secondary impf.)" V.1
[[nechat]] "to let (pf)" (imperative 'nech ~ nechej') V.1.imp:nech:nechej
[[nechávat]] "to let (impf)" V.1
[[obědvat]] "to lunch" V.1
[[odolat]] "to resist"/[[zdolat]]/[[udolat]] V.1
[[plácat]] "to slap" V.1
[[drncat]] "to rattle" V.1
[[kecat]] "to chatter" V.1
[[cucat]] "to suck" V.1
]=]

conjs["V.1"] = {
	get_infstem = function(base)
		return separate_stem_suffix(base.lemma, "^(.*)[aá]t$", "V.1")
	end,
	pres1s = "ám",
	imp = "ej",
	past = "al",
	ppp = "án",
}

--[=[

* Verbs in [sz]:
[[tesat]] "to carve" (tesám ~ tešu, tesej ~ teš, tesal, tesán, tesaje ~ teše, tesání; tr.ppp): imp^a^e.prtr^a^e
[[česat]] "to comb" (češu ~ česám, češ ~ česej, česal, česán, češe ~ česaje, česání; tr.ppp): pres^e^a.imp^e^a.prtr^e^a
[[klusat]] "to trot" (klusám ~ klušu, klusej, klusal, no PPP, klusaje ~ kluše, klusání; intr): prtr^a^e
[[křesat]] "to scrape" (křesám ~ křešu, křesej ~ křeš, křesal, no PPP, křesaje, křesání; tr.-ppp): imp^a^e
[[řezat]] "to cut" (řežu ~ řezám, řež ~ řežej, řezal, řezán, řeže ~ řezaje, řezání; tr.ppp): pres^e^a.imp^e^a.prtr^e^a
[[lízat]] "to lick" (lízám ~ lížu, lízej ~ liž [NOTE: short vowel], lízal, no PPP, lízaje ~ líže, lízání; tr.-ppp): imp^a^e.prtr^a^e
[[hryzat]] ~ [[hrýzat]] "to bite" (hryzám ~ hryžu, hryzej ~ hryž, hryzal, hryzán, hryzaje ~ hryže, hryzání; tr.ppp): imp^a^e.prtr^a^e
[[pásat se]] "to graze" (not in IJP)
[[přepásat]] (přepásám ~ přepášu, přepásej, přepásal, přepásán, přepásav, přepásání; tr.ppp): [default]
[[klouzat]] "to slide" (klouzám ~ kloužu, klouzej, klouzal, klouzán, klouzaje, klouzání; intr PPP?) [default]

* Verbs in [bpvfm]:
[[hýbat]] "to move" (hýbám ~ hýbu, hýbej, hýbal, no PPP, hýbaje, hýbání; intr no PPP) [default]
[[dlabat]] "to gouge" (dlabám ~ dlabu, dlab ~ dlabej, dlabal, dlabán, dlabaje, dlabání): imp^e^a
[[škrábat]] ~ [[škrabat]] "to scratch" (škrábám ~ škrábu, škrábej ~ škrab [NOTE: short vowel], škrábal, škrábán, škrábaje, škrábání): imp^e^a
[[klepat]] "to knock" (klepám ~ klepu, klep ~ klepej, klepal, klepán, klepaje, klepání): imp^e^a
[[kopat]] "to kick" (kopám ~ kopu, kopej, kopal, kopán, kopaje, kopání) [default]
[[koupat]] "to bathe" (koupám ~ koupu, koupej, koupal, koupán, koupaje, koupání) [default]
[[sypat]] "to sprinkle" (sypám ~ sypu, syp ~ sypej, sypal, sypán, sypaje, sypání): imp^e^a
[[drápat]] "to claw" (drápám ~ drápu, drápej, drápal, drápán, drápaje, drápání) [default]
[[dupat]] "to stomp" (dupám ~ dupu, dupej, dupal, dupán, dupaje, dupání) [default]
[[loupat]] "to peel" (loupám ~ loupu, loupej, loupal, loupán, loupaje, loupání) [default]
[[rýpat]] "to dig" (rýpám ~ rýpu, rýpej, rýpal, rýpán, rýpaje, rýpání) [default]
[[štípat]] "to pinch" (štípám ~ štípu, štípej, štípal, štípán, štípaje, štípání) [default]
[[šlapat]] "to step; to trample" (šlapám ~ šlapu, šlap ~ šlapej, šlapal, šlapán, šlapaje, šlapání): imp^e^a
[[tápat]] "to grope" (tápám ~ tápu, tápej, tápal, no PPP, tápaje, tápání; intr) [default]
[[dřímat]] "to doze" (dřímám ~ dřímu, dřímej, dřímal, no PPP, dřímaje, dřímání; intr) [default]
[[klamat]] "to deceive" (klamu, klamej ~ klam, klamal, klamán, klamaje, klamání): pres^e.imp^a^e
[[lámat]] "to break" (lámu ~ lámam [rare per SSJC; not in IJP except as a note], lam [NOTE: short vowel] ~ lámej, lámal, lámán, lámaje, lámání): pres^e^a[rare].imp^e^a
[[plavat]] "to swim, to float" (plavu, plavej ~ plav ~ poplav, plaval, plaván, plavaje, plavání) [NOTE: fut either 'budu plavat, budeš plavat, etc.' or 'poplavu, poplaveš, etc.']: pres^e.imp^a^e:poplav.fut:+:poplavu
[[klofat]] "to peck; to tap, to knock" (klofám ~ klofu, klofej, klofal, klofán, klofaje, klofání) [default]
[[hrabat]] (hrabu, hrab ~ hrabej, hrabal, hrabán, hrabaje, hrabání): pres^e.imp^e^a
[[zahrabat]] (zahrabu, zahrabej ~ zahrab, zahrabal, zahrabán, zahrabav, zahrabání): pres^e.imp^a^e

* Verbs in [rln]:
[[orat]] "to plow" (orám ~ ořu, orej ~ oř, oral, orán, oraje, orání): imp^a^e
[[párat]] "to unstitch; to unravel" (párám ~ pářu, párej, páral, párán, páraje, párání) [default]
[[dudlat]] "to hum, to drone (of an instrument or musician); to grumble, to groan; to suck (one's thumb, etc.; of a
  child)" (not in IJP; SSJC says pres dudlám ~ dudlu, impv dudlej ~ dudli; tr no PPP): imp^a^e
[[stonat]] "to moan, to groan" (stůňu/stůněš/etc., stonej, stonal, no PPP, stonaje, stonání): pres:stůňu

* Verbs in [kh] and -ch:
[[týkat se]] (týči se ~ týču se ~ týkám se, týkej se, týkal se, no PPP, týče se ~ týkaje se, týkání (se))
[[kdákat]] (kdáču ~ kdákám, kdákej, kdákal, kdákán, kdáče ~ kdákaje, kdákání)
[[kvákat]] (kváču ~ kvákám, kvákej, kvákal, no PPP, kváče ~ kvákaje, kvákání)
[[páchat]] (páchám ~ pášu, páchej, páchal, páchán, páchaje, páchání)

* Verbs in [td]:
[none; all verbs given in Wikipedia as examples are either V.1 or missing in IJP]

Types:
* [default] = a-stem + e-stem, impv only a-stem, prtr only a-stem = [[klusat]], [[přepásat]], [[klouzat]], [[hýbat]], [[škrabat]], [[kopat]], [[koupat]], [[drápat]], [[dupat]], [[loupat]], [[rýpat]], [[štípat]], [[tápat]], [[dřímat]], [[klamat]], [[klofat]], [[párat]], [[páchat]]
* pres^e^a.prtr^e^a = e-stem + a-stem, impv only a-stem = [[týkat se]], [[kdákat]], [[kvákat]]
* pres:stůňu = e-stem, impv only a-stem = [[stonat]]
* imp^e^a = a-stem + e-stem, impv e-stem + a-stem = [[dlabat]], [[klepat]], [[sypat]], [[šlapat]], [[lámat]] (short 'lam')
* pres^e^a.imp^e^a.prtr^e^a = e-stem + a-stem, impv e-stem + a-stem = [[česat]], [[řezat]]
* imp^a^e = a-stem + e-stem, impv a-stem + e-stem = [[tesat]], [[křesat]], [[lízat]], [[hryzat]], [[orat]], [[dudlat]]
* pres^e.imp^a^e = e-stem, impv only a-stem + e-stem = [[plavat]]
]=]

conjs["V.2"] = {
	init = function(base)
		base.infstem = separate_stem_suffix(base.lemma, "^(.*)[aá]t$", "V.2")
		base.palstem = com.apply_first_palatalization(base.infstem, "is verb")
	end,
	pres1s = {
		choices = {"a", "e"},
		default = {"a", "e"},
		generate_part = function(base, variant)
			if variant == "a" then
				return base.infstem .. "ám"
			elseif variant == "e" then
				return base.palstem .. "u"
			else
				error("Internal error: Saw unrecognized pres1s variant code '" .. variant .. "'")
			end
		end,
	},
	imp = {
		choices = {"a", "e"},
		default = {"a"},
		generate_part = function(base, variant)
			if variant == "a" then
				return base.infstem .. "ej"
			else
				return generate_default_imperative_principal_part_from_stem(base, base.palstem)
			end
		end,
	},
	past = "al",
	ppp = "án",
	prtr = {
		choices = {"a", "e"},
		default = {"a"},
		generate_part = function(base, variant)
			if variant == "a" then
				return base.infstem .. "aje"
			else
				return com.combine_stem_ending(base, "pres_tgress_m", base.palstem, "ě")
			end
		end,
	},
}


conjs["5"] = function(base, lemma)
	local stem, suffix, ac = separate_stem_suffix_accent(lemma, "5", "^(.*)([іая])(́?)ти$")
	local stem_ends_in_vowel = com.ends_in_vowel(stem)
	if suffix == "я" and not stem_ends_in_vowel then
		error("Ending -яти can only be used with a vocalic stem: '" .. lemma .. "'")
	elseif suffix ~= "я" and stem_ends_in_vowel then
		error("Ending -яти must be used with a vocalic stem: '" .. lemma .. "'")
	end
	local stressed_stem = com.maybe_stress_final_syllable(stem)
	local sg2
	if base.i then
		if not rfind(stem, "о́?$") then
			error("і-modifier can only be used with stem ending in -о: '" .. lemma .. "'")
		end
		sg2 = com.maybe_stress_final_syllable(rsub(stem, "о(́?)$", "і%1й"))
	end
	add_present_i(base, stressed_stem, sg2)
	add_past(base, stem .. suffix .. ac)
	add_retractable_ppp(base, (suffix == "і" and com.iotate(stem) .. "е" or stem .. suffix) .. ac .. "н")
end

conjs["8"] = function(base, lemma)
	local stem, last_cons = rmatch(lemma, "^(.*)([кг])ти́?$")
	if not stem then
		error("Unrecognized lemma for class 8: '" .. lemma .. "'")
	end
	local palatalized_cons = com.iotate(last_cons)
	local stressed_stem = com.maybe_stress_final_syllable(stem)
	add_present_e(base, stressed_stem .. palatalized_cons)
	local past_msg = stressed_stem .. last_cons
	local past_rest = past_msg .. "л"
	if base.i then
		past_msg = rsub(past_msg, "[еоя](́?" .. com.cons_c .. "+)$", "і%1")
	end
	add_past(base, past_msg, past_rest)
	add_ppp(base, stressed_stem .. palatalized_cons .. "ен")
end


conjs["9"] = function(base, lemma)
	local stem, suffix = rmatch(lemma, "^(.*)(е́?р)ти$")
	if not stem then
		error("Unrecognized lemma for class 9: '" .. lemma .. "'")
	end
	if accent ~= "a" and accent ~= "b" then
		error("Only accent a or b allowed for class 9: '" .. base.conj .. "'")
	end
	local pres_stem
	if base.conj_star then
		pres_stem = rsub(stem, "^(.*)(.)$", "%1і%2")
	else
		pres_stem = stem
	end
	add_present_e(base, pres_stem .. "р")
	local stressed_stem = com.maybe_stress_final_syllable(stem .. suffix)
	add_past(base, stressed_stem, stressed_stem .. "л")
	add_ppp(base, stressed_stem .. "т")
end


conjs["10"] = function(base, lemma)
	local stem, suffix, ac = separate_stem_suffix_accent(lemma, "10", "^(.*)(о[лр]о)(́?)ти$")
	if accent ~= "a" and accent ~= "c" then
		error("Only accent a or c allowed for class 10: '" .. base.conj .. "'")
	end
	local stressed_stem = com.maybe_stress_final_syllable(rsub(stem .. suffix, "о$", ""))
	add_present_e(base, stressed_stem, "1sg3pl")
	add_past(base, stem .. suffix .. ac)
	-- If explicit present stem given (e.g. for моло́ти), use it/them in the н-participle.
	local n_ppps
	if base.pres_stems then
		n_ppps = {}
		for _, pres_stem in ipairs(base.pres_stems) do
			table.insert(n_ppps, pres_stem .. "ен")
		end
	else
		n_ppps = stressed_stem .. "ен"
	end
	local t_ppp = stressed_stem .. "от"
	if base.conj == "н" then
		add_ppp(base, n_ppps)
	elseif base.conj == "т" then
		add_ppp(base, t_ppp)
	else
		add_ppp(base, n_ppps)
		add_ppp(base, t_ppp)
	end
end


conjs["11"] = function(base, lemma)
	local stem, suffix, ac = separate_stem_suffix_accent(lemma, "11", "^(.*)(и)(́?)ти$")
	if accent ~= "a" and accent ~= "b" then
		error("Only accent a or b allowed for class 11: '" .. base.conj .. "'")
	end
	local pres_stem
	if base.conj_star then
		pres_stem = rsub(stem, "^(.*)(.)$", "%1і%2")
	else
		pres_stem = stem
	end
	if rfind(pres_stem, "л$") then
		pres_stem = pres_stem .. "л"
	else
		pres_stem = pres_stem .. "'"
	end
	local full_stem = stem .. suffix .. ac
	add_present_e(base, pres_stem, "all", full_stem .. "й")
	add_past(base, full_stem)
	add_ppp(base, full_stem .. "т")
end


conjs["12"] = function(base, lemma)
	local stem = rmatch(lemma, "^(.*" .. com.vowel_c .. AC .. "?)ти$")
	if not stem then
		error("Unrecognized lemma for class 12: '" .. lemma .. "'")
	end
	if accent ~= "a" and accent ~= "b" then
		error("Only accent a or b allowed for class 12: '" .. base.conj .. "'")
	end
	add_present_e(base, stem)
	add_past(base, stem)
	add_ppp(base, stem .. "т")
end


conjs["13"] = function(base, lemma)
	local stem = rmatch(lemma, "^(.*а)ва́ти$")
	if not stem then
		error("Unrecognized lemma for class 13: '" .. lemma .. "'")
	end
	if accent ~= "b" then
		error("Only accent b allowed for class 13: '" .. base.conj .. "'")
	end
	local full_stem = stem .. "ва́"
	add_present_e(base, stem, nil, full_stem .. "й")
	add_past(base, full_stem)
	add_retractable_ppp(base, full_stem .. "н")
end


conjs["14"] = function(base, lemma)
	-- -сти occurs in п'я́сти́ and роз(і)п'я́сти́
	local stem = rmatch(lemma, "^(.*[ая]́?)с?ти́?$")
	if not stem then
		error("Unrecognized lemma for class 14: '" .. lemma .. "'")
	end
	if not base.pres_stems then
		error("With class 14, must specify explicit present stem using 'pres:STEM'")
	end
	add_present_e(base, "foo")
	local stressed_stem = com.maybe_stress_final_syllable(stem)
	add_past(base, stressed_stem)
	add_retractable_ppp(base, stressed_stem .. "т")
end


conjs["irreg"] = function(base, lemma)
	local prefix = rmatch(lemma, "^(.*)да́?ти$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		if stressed_prefix then
			add_pres_fut(base, prefix, "дам", "даси", "дасть", "дамо", "дасте", "дадуть")
			add_imperative(base, prefix .. "дай")
			add_past(base, prefix .. "да")
			add_retractable_ppp(base, prefix .. "дан") -- ви́даний from ви́дати
		else
			add_pres_fut(base, prefix, "да́м", "даси́", "да́сть", "дамо́", "дасте́", "даду́ть")
			add_imperative(base, prefix .. "да́й")
			add_past(base, prefix .. "да́")
			add_retractable_ppp(base, prefix .. "да́н") -- e.g. пере́даний from переда́ти
		end
		return
	end
	prefix = rmatch(lemma, "^(.*по)ві́?сти́?$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		if stressed_prefix then
			add_pres_fut(base, prefix, "вім", "віси", "вість", "вімо", "вісте", "відять")
			add_imperative(base, prefix .. "відж", "[lc]")
			add_imperative(base, prefix .. "віж", "[lc]")
			add_past(base, prefix .. "ві")
			-- no PPP
		else
			add_pres_fut(base, prefix, "ві́м", "віси́", "ві́сть", "вімо́", "вісте́", "відя́ть")
			add_imperative(base, prefix .. "ві́дж", "[lc]")
			add_imperative(base, prefix .. "ві́ж", "[lc]")
			add_past(base, prefix .. "ві́")
			-- no PPP
		end
		return
	end
	prefix = rmatch(lemma, "^(.*)ї́?сти$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		if stressed_prefix then
			add_pres_fut(base, prefix, "їм", "їси", "їсть", "їмо", "їсте", "їдять")
			add_imperative(base, prefix .. "їж")
			add_past(base, prefix .. "ї")
			add_ppp(base, prefix .. "їден") -- ви́їдений from ви́їсти
		else
			add_pres_fut(base, prefix, "ї́м", "їси́", "ї́сть", "їмо́", "їсте́", "їдя́ть")
			add_imperative(base, prefix .. "ї́ж")
			add_past(base, prefix .. "ї́")
			add_ppp(base, prefix .. "ї́ден") -- e.g. прої́дений from прої́сти
		end
		return
	end
	prefix = rmatch(lemma, "^(.*)бу́?ти$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		if prefix == "" then
			error("Can't handle unprefixed irregular verb бу́ти yet")
		end
		add_present_e(base, prefix .. (stressed_prefix and "буд" or "бу́д"), "a")
		add_past(base, prefix .. (stressed_prefix and "бу" or "бу́"))
		add_ppp(base, prefix .. (stressed_prefix and "бут" or "бу́т")) -- e.g. забу́тий from забу́ти
		return
	end
	prefix = rmatch(lemma, "^(.*)ї́?хати$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		add_present_e(base, prefix .. (stressed_prefix and "їд" or "ї́д"), "a")
		add_past(base, prefix .. (stressed_prefix and "їха" or "ї́ха"))
		-- no PPP
		return
	end
	prefix = rmatch(lemma, "^(.*)шиби́?ти$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		add_present_e(base, prefix .. "шиб", stressed_prefix and "a" or "b")
		local past_msg = prefix .. (stressed_prefix and "шиб" or "ши́б")
		add_past(base, past_msg, past_msg .. "л")
		add_ppp(base, prefix .. (stressed_prefix and "шиблен" or "ши́блен")) -- e.g. проши́блений from прошиби́ти
		return
	end
	prefix = rmatch(lemma, "^(.*соп)і́ти$")
	if prefix then
		add_pres_fut(base, prefix, "лю́", "е́ш", "е́", {"е́м", "емо́"}, "ете́", "ля́ть")
		add_imperative(base, prefix .. "и́")
		add_past(base, prefix .. "і́")
		-- no PPP
		return
	end
	prefix = rmatch(lemma, "^(.*)жи́?ти$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		add_present_e(base, prefix .. "жив", stressed_prefix and "a" or "b")
		add_past(base, prefix .. (stressed_prefix and "жи" or "жи́"))
		add_ppp(base, prefix .. (stressed_prefix and "жит" or "жи́т")) -- e.g. пережи́тий from пережи́ти
		return
	end
	prefix = rmatch(lemma, "^(.*)бі́?гти$")
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		add_present_i(base, prefix .. "біж", stressed_prefix and "a" or "b")
		local past_msg = prefix .. (stressed_prefix and "біг" or "бі́г")
		add_past(base, past_msg, past_msg .. "л")
		-- no PPP
		return
	end
	prefix = rmatch(lemma, "^(п?і)ти́$")
	if not prefix then
		prefix = rmatch(lemma, "^(.*й)ти́?$")
	end
	if prefix then
		local stressed_prefix = com.is_stressed(prefix)
		add_present_e(base, com.maybe_stress_final_syllable(prefix .. "д"),
			stressed_prefix and "a" or (prefix == "і" or prefix == "й") and "b" or "c")
		add_past(base, prefix .. (stressed_prefix and "шов" or "шо́в"), com.maybe_stress_final_syllable(prefix .. "шл"))
		add_retractable_ppp(base, prefix .. (stressed_prefix and "ден" or "де́н")) -- e.g. пере́йдений from перейти́
		return
	end
	error("Unrecognized irregular verb: '" .. lemma .. "'")
end


local function add_infinitive(base)
	add(base, "infinitive", base.lemma, "")
	-- FIXME: Consider adding old infinitive in -ti as an alternant at the end (after adding imperfective future)
end


local function set_present_future(base)
	local forms = base.forms
	if base.aspect == "pf" then
		for slot_suffix, _ in pairs(budu_forms) do
			forms["fut_" .. slot_suffix] = forms["pres_fut_" .. slot_suffix]
			forms["pres_fut_" .. slot_suffix] = nil
		end
	else
		for slot_suffix, _ in pairs(budu_forms) do
			forms["pres_" .. slot_suffix] = forms["pres_fut_" .. slot_suffix]
			forms["pres_fut_" .. slot_suffix] = nil
		end
		-- Do the periphrastic future with [[budu]]
		if forms.infinitive then
			for slot_suffix, budu_form in pairs(budu_forms) do
				local futslot = "fut_" .. slot_suffix
				if not skip_slot(base, futslot) then
					iut.insert_forms(forms, futslot, iut.map_forms(forms.infinitive, function(form)
						if not form:find("%[") then
							form = "[[" .. form .. "]]"
						end
						return "[[" .. budu_form .. "]]" .. TEMP_REFLEXIVE_INSERTION_POINT .. " " .. form
					end))
				end
			end
		end
	end
end


-- Generate a composed tense. `pref` is the prefix of the tense (e.g. "past") and `templates` is a 6-element list of
-- templates used to generate the composed tense. Each template should have a %s where the l-participle is substituted
-- and a * where TEMP_REFLEXIVE_INSERTION_POINT is substituted, indicating where the reflexive clitic should go.
-- `dual_template` is true if each template has two slots in it (the first for the l-participle of [[být]], the second
-- for the l-participle of the verb itself).
local function add_composed_tense(base, pref, templates, dual_template)
	for _, props in ipairs(person_number_gender_props) do
		local dest_suffix, part_suffix, template_index = unpack(props)
		local template = templates[template_index]
		template = template:gsub("%*", TEMP_REFLEXIVE_INSERTION_POINT)
		iut.insert_forms(base.forms, pref .. "_" .. dest_suffix, iut.map_forms(base.forms["lpart_" .. part_suffix], function(form)
			if not form:find("%[") then
				form = "[[" .. form .. "]]"
			end
			if dual_template then
				local byl_form = "[[byl" .. part_suffix_to_ending[part_suffix] .. "]]"
				return template:format(byl_form, form)
			else
				return template:format(form)
			end
		end))
	end
end


local function generate_composed_tenses(base)
	-- Then generate the past tense by combining the l-participle with the present tense of [[být]].
	add_composed_tense(base, "past", {"%s [[jsem]]*", "%s [[jsi]]*", "%s*", "%s [[jsme]]*", "%s [[jste]]*", "%s*"})
	add_composed_tense(base, "cond", {"%s [[bych]]*", "%s [[bys]]*", "%s [[by]]*", "%s [[bychom]]*", "%s [[byste]]*", "%s [[by]]*"})
	add_composed_tense(base, "cond_past", {"%s [[bych]]* %s", "%s [[bys]]* %s", "%s [[by]]* %s",
		"%s [[bychom]]* %s", "%s [[byste]]* %s", "%s [[by]]* %s"}, "dual template")
end


-- Add a reflexive pronoun as appropriate to the base forms that were generated.
local function add_reflexive_to_forms(base)
	if not base.refl then
		-- Remove insertion point character.
		for slot, accel in pairs(verb_slots) do
			if base.forms[slot] then
				for _, form in ipairs(base.forms[slot]) do
					form.form = form.form:gsub(TEMP_REFLEXIVE_INSERTION_POINT, "")
				end
			end
		end
		return
	end

	clitic = " [[" .. base.refl .. "]]"
	local paren_clitic = " ([[" .. base.refl .. "]])"
	for slot, accel in pairs(verb_slots) do
		if base.forms[slot] then
			local this_clitic = slot == "vnoun" and paren_clitic or clitic
			-- Add clitic as separate word before all other forms.
			for _, form in ipairs(base.forms[slot]) do
				if form.form:find(TEMP_REFLEXIVE_INSERTION_POINT) then
					form.form = form.form:gsub(TEMP_REFLEXIVE_INSERTION_POINT, this_clitic)
				else
					if not form.form:find("%[") then
						form.form = "[[" .. form.form .. "]]"
					end
					form.form = form.form .. this_clitic
				end
				form.form = form.form:gsub("%[%[bys%]%] %[%[(s[ei])%]%]", "[[by]] [[%1s]]")
				form.form = form.form:gsub("%[%[jsi%]%] %[%[(s[ei])%]%]", "[[%1s]]")
			end
		end
	end
end


local function conjugate_verb(base)
	add_infinitive(base)
	conjs[base.conj](base, base.lemma)
	set_present_future(base)
	generate_composed_tenses(base)
	add_reflexive_to_forms(base)
end


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


local function parse_indicator_spec(angle_bracket_spec)
	local inside = rmatch(angle_bracket_spec, "^<(.*)>$")
	assert(inside)
	local function parse_err(msg)
		error(msg .. ": '" .. inside .. "'")
	end
	local base = {overrides = {}, forms = {}}
	local segments = iut.parse_balanced_segment_run(inside, "[", "]")
	local dot_separated_groups = iut.split_alternating_runs_and_strip_spaces(segments, "%.")
	local major_class = dot_separated_groups[1][1]
	if major_class ~= "I" and major_class ~= "II" and major_class ~= "III" and major_class ~= "IV" and
		major_class ~= "V" and major_class ~= "irreg" then
		parse_err("Unrecognized major verb class '" .. major_class .. "'; expected 'I', 'II', 'III', 'IV', 'V' or 'irreg'")
	end
	if #dot_separated_groups[1] > 1 then
		parse_err("No footnotes allowed after major class")
	end
	local start_of_indicators = major_class == "irreg" and 2 or 3
	if major_class == "irreg" then
		base.conj = "irreg"
	else
		local minor_class_and_variants = dot_separated_groups[2][1]
		local minor_class, variants = rmatch(minor_class_and_variants, "^([123])/(.*)$")
		if not minor_class then
			minor_class = rmatch(minor_class_and_variants, "^([123])$")
		end
		if not minor_class then
			parse_err("Unrecognized minor verb class; expected 1, 2 or 3")
		end
		base.conj = major_class .. "." .. minor_class
		if variants then
			dot_separated_groups[2][1] = variants
			if parse[base.conj] then
				local function parse_err(msg)
					error(msg .. ": '" .. table.concat(dot_separated_groups[2]) .. "'")
				end
				parse[base.conj](base, dot_separated_groups[2], parse_err)
			else
				parse_err("No variants allowed for conjugation " .. base.conj)
			end
		elseif #dot_separated_groups[2] > 1 then
			parse_err("No footnotes allowed after minor class")
		end
	end
	for i, dot_separated_group in ipairs(dot_separated_groups) do
		if i >= start_of_indicators then
			local part = dot_separated_group[1]
			local stem, rest = rmatch(part, "^([a-z_]+):(.*)$")
			if override_stems[stem] then
				if base.overrides[stem] then
					parse_err(("Two overrides specified for stem '%s'"):format(stem))
				end
				base.overrides[stem] = {}
				dot_separated_group[1] = rest
				local colon_separated_groups = iut.split_alternating_runs_and_strip_spaces(dot_separated_group, ":")
				for i, colon_separated_group in ipairs(colon_separated_groups) do
					local form = colon_separated_group[1]
					if form == "" then
						-- No need to use parse_err() as the overall spec is probably irrelevant
						error(("Use - to indicate a missing stem '%s': '%s'"):format(stem, table.concat(dot_separated_group)))
					elseif form == "-" then
						if #colon_separated_group > 1 then
							error(("No footnotes allowed with '-' as stem value for stem '%s': '%s'"):format(stem,
								table.concat(dot_separated_group)))
						end
						-- don't record a value
					else
						local value = {}
						value.form = form
						value.footnotes = fetch_footnotes(colon_separated_group)
						table.insert(base.overrides[stem], value)
					end
				end
			elseif part == "" then
				if #dot_separated_group == 1 then
					error("Blank indicator: '" .. inside .. "'")
				end
				base.footnotes = fetch_footnotes(dot_separated_group)
			elseif #dot_separated_group > 1 then
				error("Footnotes only allowed with stem overridese or by themselves: '" .. table.concat(dot_separated_group) .. "'")
			elseif part == "impf" or part == "pf" or part == "both" then
				if base.aspect then
					parse_err("Can't specify aspect twice")
				end
				base.aspect = part
			elseif part == "tr" or part == "intr" or part == "mixed" then
				if base.trans then
					parse_err("Can't specify transitivity twice")
				end
				base.trans = part
			elseif part == "ppp" or part == "-ppp" then
				if base.ppp ~= nil then
					parse_err("Can't specify past passive participle indicator twice")
				end
				base.ppp = part == "ppp"
			elseif part == "impers" or part == "3only" or part == "plonly" or part == "3plonly" or part == "3orplonly" or
				part == "ě" then
				local field = part
				if part == "ě" then
					field = "ye"
				end
				if base[field] then
					parse_err(("Can't specify '%s' twice"):format(part))
				end
				base[field] = true
			else
				error("Unrecognized indicator '" .. part .. "': " .. angle_bracket_spec)
			end
		end
	end
	return base
end


local function normalize_all_lemmas(alternant_multiword_spec, pagename)
	iut.map_word_specs(alternant_multiword_spec, function(base)
		if base.lemma == "" then
			base.lemma = pagename
		end
		base.orig_lemma = base.lemma
		base.orig_lemma_no_links = m_links.remove_links(base.lemma)
		-- If reflexive verb is explicitly specified by the user, we will convert the space before the reflexive clitic
		-- to an underscore in split_bracketed_runs_into_words().
		local active_verb, refl = rmatch(base.orig_lemma_no_links, "^(.*)[ _](s[ei])$")
		if active_verb then
			base.refl = refl
			base.lemma = active_verb
		else
			base.lemma = base.orig_lemma_no_links
		end
		-- Convert "old-style" lemma e.g. [[dělati]], [[nésti]], [[moci]] into new-style [[dělat]], [[nést]], [[moct]]
		local old_style_stem = rmatch(base.lemma, "^(.*)i$")
		if old_style_stem then
			if rfind(old_style_stem, "c$") then
				-- [[moci]], [[peci]], etc.
				base.lemma = old_style_stem .. "t"
			elseif rfind(old_style_stem, "t$") then
				base.lemma = old_style_stem
			else
				error(("Unrecognized old-style lemma '%s', should end in -ci or -ti"):format(base.orig_lemma_no_links))
			end
		end
	end)
end


local function detect_indicator_spec(base)
	if not base.aspect then
		error("Aspect of 'pf', 'impf' or 'both' must be specified")
	end
	if base.refl then
		if base.trans then
			error("Can't specify transitivity with reflexive verb, they're always intransitive: '" .. base.orig_lemma_no_links .. "'")
		end
	elseif not base.trans then
		error("Transitivity of 'tr', 'intr' or 'mixed' must be specified")
	end
	if base.ppp ~= nil then
		if base.trans == "intr" then
			error("Can't specify 'ppp' or '-ppp' with intransitive verbs")
		end
	elseif base.trans and base.trans ~= "intr" then
		error("Must specify 'ppp' or '-ppp' with transitive or mixed-transitive verbs")
	end
end


local function detect_all_indicator_specs(alternant_multiword_spec)
	iut.map_word_specs(alternant_multiword_spec, function(base)
		detect_indicator_spec(base)
		if not alternant_multiword_spec.aspect then
			alternant_multiword_spec.aspect = base.aspect
		elseif alternant_multiword_spec.aspect ~= base.aspect then
			alternant_multiword_spec.aspect = "both"
		end
		if alternant_multiword_spec.refl == nil then
			alternant_multiword_spec.refl = base.refl
		elseif alternant_multiword_spec.refl ~= base.refl then
			error("With multiple alternants, all must agree on reflexive clitic")
		end
		if not alternant_multiword_spec.trans then
			alternant_multiword_spec.trans = base.trans
		elseif alternant_multiword_spec.trans ~= base.trans then
			alternant_multiword_spec.trans = "mixed"
		end
		for _, prop in ipairs({"nopres", "noimp", "nopast", "impers", "only3", "onlypl", "only3pl", "only3orpl"}) do
			if alternant_multiword_spec[prop] == nil then
				alternant_multiword_spec[prop] = base[prop]
			elseif alternant_multiword_spec[prop] ~= base[prop] then
				alternant_multiword_spec[prop] = false
			end
		end
	end)
end


local function add_categories(alternant_multiword_spec)
	local cats = {}
	local function insert(cattype)
		table.insert(cats, "Czech " .. cattype .. " verbs")
	end
	if alternant_multiword_spec.aspect == "impf" then
		insert("imperfective")
	elseif alternant_multiword_spec.aspect == "pf" then
		insert("perfective")
	else
		assert(alternant_multiword_spec.aspect == "both")
		insert("imperfective")
		insert("perfective")
		insert("biaspectual")
	end
	if alternant_multiword_spec.trans == "tr" then
		insert("transitive")
	elseif alternant_multiword_spec.trans == "intr" then
		insert("intransitive")
	elseif alternant_multiword_spec.trans == "mixed" then
		insert("transitive")
		insert("intransitive")
	end
	if alternant_multiword_spec.is_refl then
		insert("reflexive")
	end
	if alternant_multiword_spec.impers then
		insert("impersonal")
	end
	iut.map_word_specs(alternant_multiword_spec, function(base)
		if base.conj == "irreg" or base.irreg then
			insert("irregular")
		end
		if base.conj ~= "irreg" then
			insert("class " .. base.conj)
			insert("class " .. rsub(base.conj, "^([0-9]+).*", "%1"))
		end
	end)
	alternant_multiword_spec.categories = cats
end


local function show_forms(alternant_multiword_spec)
	local lemmas = {}
	if alternant_multiword_spec.forms.infinitive then
		for _, inf in ipairs(alternant_multiword_spec.forms.infinitive) do
			table.insert(lemmas, inf.form)
		end
	end
	local props = {
		lemmas = lemmas,
		slot_table = verb_slots,
		lang = lang,
	}
	iut.show_forms(alternant_multiword_spec.forms, props)
end


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

	local table_spec_overall = [=[
<div class="NavFrame" style="width:90em;">
<div class="NavHead" style="background:#e0e0ff;">{title}{annotation}</div>
<div class="NavContent">
{\op}| class="inflection-table inflection inflection-cs inflection-verb" style="border: 2px solid black;" border=1
|-
! rowspan=3 colspan=2 style="background:#cddfff" |
! colspan=3 style="background:#cddfff; text-align: center;" | [[singular]]
! colspan=4 style="background:#cddfff; text-align: center;" | [[plural]]
|-
! rowspan=2 style="background:#e7f3ff; text-align: center;vertical-align:middle;"| [[masculine]]
! rowspan=2 style="background:#e7f3ff; text-align: center;vertical-align:middle;"| [[feminine]]
! rowspan=2 style="background:#e7f3ff; text-align: center;vertical-align:middle;"| [[neuter]]
! colspan=2 style="background:#e7f3ff; text-align: center;"| [[masculine]]
! rowspan=2 style="background:#e7f3ff; text-align: center;vertical-align:middle;"| [[feminine]]
! rowspan=2 style="background:#e7f3ff; text-align: center;vertical-align:middle;"| [[neuter]]
|-
! style="background:#e7f3ff; text-align: center;"| [[animate]]
! style="background:#e7f3ff; text-align: center;"| [[inanimate]]
|-
! style="background:#e7f3ff; text-align: center; width:8%;"| invariable
! style="background:#cddfff; text-align: center; width:8%;"| [[infinitive]]
| colspan=7 | {infinitive}
|-
! rowspan=4 style="background:#e7f3ff; text-align: center; vertical-align: middle;"| number/gender<br/>only
! style="background:#cddfff; text-align: center;"| [[short]]&nbsp;[[passive]]&nbsp;[[participle]]
| {ppp_m}
| {ppp_f}
| {ppp_n}
| {ppp_mp_an}
| {ppp_mp_in}
| {ppp_fp}
| {ppp_np}
|-
! style="background:#cddfff; text-align: center;"| l-participle
| {lpart_m}
| {lpart_f}
| {lpart_n}
| {lpart_mp_an}
| {lpart_mp_in}
| {lpart_fp}
| {lpart_np}
|-
! style="background:#cddfff; text-align: center;"| [[present]]&nbsp;[[transgressive]]
| {pres_tgress_m}
| colspan=2|{pres_tgress_fn}
| colspan=4|{pres_tgress_p}
|-
! style="background:#cddfff; text-align: center;"| [[past]]&nbsp;[[transgressive]]
| {past_tgress_m}
| colspan=2|{past_tgress_fn}
| colspan=4|{past_tgress_p}
|-
! rowspan=3 style="background:#e7f3ff; text-align: center; vertical-align: middle;"| declined<br/>as<br/>adjective
! style="background:#cddfff; text-align: center;"| [[present]]&nbsp;[[active]]&nbsp;[[participle]]
| colspan=7 | {pres_act_part}
|-
! style="background:#cddfff; text-align: center;"| [[past]]&nbsp;[[active]]&nbsp;[[participle]]
| colspan=7 | {past_act_part}
|-
! style="background:#cddfff; text-align: center;"| [[long]]&nbsp;[[passive]]&nbsp;[[participle]]
| colspan=7 | {long_pass_part}
|-
! style="background:#e7f3ff; text-align: center;"| case/number<br/>only
! style="background:#cddfff; text-align: center; vertical-align: middle;"| [[verbal noun|verbal&nbsp;noun]]
| style="vertical-align: middle;" colspan=7 | {vnoun}
|-
{indicative_header}{present_table}| {fut_1s}
| {fut_2s}
| {fut_3s}
| {fut_1p}
| colspan=2 | {fut_2p}
| {fut_3p}
|-
{past_table}{conditional_header}{cond_table}{cond_past_table}! style="background:#cddfff; text-align: center; border-top-width: 3px;" colspan=2 | [[imperative mood|imperative]]
| style="border-top-width: 3px;" | —
| style="border-top-width: 3px;" | {imp_2s}
| style="border-top-width: 3px;" | —
| style="border-top-width: 3px;" | {imp_1p}
| style="border-top-width: 3px;" colspan=2 | {imp_2p}
| style="border-top-width: 3px;" | —
|{\cl}{notes_clause}</div></div>]=]

	local table_spec_person_number_header = [=[
!style="background:#cddfff; text-align: center; vertical-align: middle; border-top-width: 3px;" rowspan=3 colspan=2 | MOOD
!style="background:#cddfff; text-align: center; border-top-width: 3px;" colspan=3 | [[singular]]
!style="background:#cddfff; text-align: center; border-top-width: 3px;" colspan=4 | [[plural]] (or polite)
|-
!style="background:#e7f3ff; text-align: center; vertical-align: middle;" rowspan=2 | [[first person|first]]
!style="background:#e7f3ff; text-align: center; vertical-align: middle;" rowspan=2 | [[second person|second]]
!style="background:#e7f3ff; text-align: center; vertical-align: middle;" rowspan=2 | [[third person|third]]
!style="background:#e7f3ff; text-align: center; vertical-align: middle;" rowspan=2 | [[first person|first]]
!style="background:#e7f3ff; text-align: center; vertical-align: middle;" colspan=2 | [[second person|second]]
!style="background:#e7f3ff; text-align: center; vertical-align: middle;" rowspan=2 | [[third person|third]]
|-
!style="background:#e7f3ff; text-align: center;" | [[polite]] [[singular]]
!style="background:#e7f3ff; text-align: center;" | [[plural]]
|-
]=]

	local table_spec_single_aspect_present = [=[
! style="background:#cddfff; text-align: center;" colspan=2 | [[present tense|present]]
| {pres_1s}
| {pres_2s}
| {pres_3s}
| {pres_1p}
| colspan=2 | {pres_2p}
| {pres_3p}
|-
! style="background:#cddfff; text-align: center;" colspan=2 | [[future tense|future]]
]=]

	local table_spec_biaspectual_present = [=[
! style="background:#cddfff; text-align: center;" colspan=2 | [[present tense|present]]&nbsp;(imperfective)
| rowspan=2 | {pres_1s}
| rowspan=2 | {pres_2s}
| rowspan=2 | {pres_3s}
| rowspan=2 | {pres_1p}
| colspan=2 rowspan=2 | {pres_2p}
| rowspan=2 | {pres_3p}
|-
! style="background:#cddfff; text-align: center;" colspan=2 | [[future tense|future]]&nbsp;(perfective)
|-
! style="background:#cddfff; text-align: center;" colspan=2 | [[future tense|future]]&nbsp;(imperfective)
]=]

	local table_spec_person_number_gender = [=[
!style="background:#cddfff; text-align: center; vertical-align: middle;" rowspan=4 | TENSE
!style="background:#e7f3ff; text-align: center;"| [[masculine]]&nbsp;[[animate]]
| rowspan=2 | {PREF_1sm}
| rowspan=2 | {PREF_2sm}
| rowspan=2 | {PREF_3sm}
| rowspan=2 | {PREF_1pm}
| rowspan=2 | {PREF_2pm_polite}
| rowspan=2 | {PREF_2pm_plural}
| {PREF_3pm_an}
|-
!style="background:#e7f3ff; text-align: center;"| [[masculine]]&nbsp;[[inanimate]]
| {PREF_3pm_in}
|-
!style="background:#e7f3ff; text-align: center;"| [[feminine]]
| {PREF_1sf}
| {PREF_2sf}
| {PREF_3sf}
| {PREF_1pf}
| {PREF_2pf_polite}
| {PREF_2pf_plural}
| {PREF_3pf}
|-
!style="background:#e7f3ff; text-align: center;"| [[neuter]]
| {PREF_1sn}
| {PREF_2sn}
| {PREF_3sn}
| {PREF_1pn}
| {PREF_2pn_polite}
| {PREF_2pn_plural}
| {PREF_3pn}
|-
]=]

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

	if alternant_multiword_spec.title then
		forms.title = alternant_multiword_spec.title
	else
		forms.title = 'Conjugation of <i lang="cs">' .. forms.lemma .. '</i>'
	end

	local ann_parts = {}
	local saw_irreg_conj = false
	local saw_base_irreg = false
	local all_irreg_conj = true
	local conjs = {}
	iut.map_word_specs(alternant_multiword_spec, function(base)
		m_table.insertIfNot(conjs, base.conj)
		if base.conj == "irreg" then
			saw_irreg_conj = true
		else
			all_irreg_conj = false
		end
		if base.irreg then
			saw_base_irreg = true
		end
	end)
	if all_irreg_conj then
		table.insert(ann_parts, "irregular")
	else
		table.insert(ann_parts, "class " .. table.concat(conjs, " // "))
	end
	table.insert(ann_parts,
		alternant_multiword_spec.aspect == "impf" and "imperfective" or
		alternant_multiword_spec.aspect == "pf" and "perfective" or
		"biaspectual")
	if alternant_multiword_spec.trans then
		table.insert(ann_parts,
			alternant_multiword_spec.trans == "tr" and "transitive" or
			alternant_multiword_spec.trans == "intr" and "intransitive" or
			"transitive and intransitive"
		)
	end
	if alternant_multiword_spec.is_refl then
		table.insert(ann_parts, "reflexive")
	end
	if alternant_multiword_spec.impers then
		table.insert(ann_parts, "impersonal")
	end
	if saw_base_irreg and not saw_irreg_conj then
		table.insert(ann_parts, "irregular")
	end
	forms.annotation = " (" .. table.concat(ann_parts, ", ") .. ")"

	if alternant_multiword_spec.aspect == "pf" then
		forms.aspect_indicator = "[[perfective aspect]]"
	elseif alternant_multiword_spec.aspect == "impf" then
		forms.aspect_indicator = "[[imperfective aspect]]"
	else
		forms.aspect_indicator = "[[biaspectual]]"
	end

	forms.notes_clause = forms.footnote ~= "" and
		m_string_utilities.format(notes_template, forms) or ""
	forms.present_table = m_string_utilities.format(
		alternant_multiword_spec.aspect == "both" and table_spec_biaspectual_present or table_spec_single_aspect_present,
		forms
	)
	local table_spec_indicative_header = table_spec_person_number_header:gsub("MOOD", "[[indicative mood|indicative]]")
	forms.indicative_header = m_string_utilities.format(table_spec_indicative_header, forms)
	local table_spec_conditional_header = table_spec_person_number_header:gsub("MOOD", "[[conditional mood|conditional]]")
	forms.conditional_header = m_string_utilities.format(table_spec_conditional_header, forms)
	local table_spec_past = table_spec_person_number_gender:gsub("TENSE", "[[past tense|past]]"):gsub("PREF", "past")
	forms.past_table = m_string_utilities.format(table_spec_past, forms)
	local table_spec_cond = table_spec_person_number_gender:gsub("TENSE", "[[present tense|present]]"):gsub("PREF", "cond")
	forms.cond_table = m_string_utilities.format(table_spec_cond, forms)
	local table_spec_cond_past = table_spec_person_number_gender:gsub("TENSE", "[[past tense|past]]"):gsub("PREF", "cond_past")
	forms.cond_past_table = m_string_utilities.format(table_spec_cond_past, forms)
	return m_string_utilities.format(table_spec_overall, forms)
end


-- Externally callable function to parse and conjugate a verb given user-specified arguments. Return value is
-- ALTERNANT_MULTIWORD_SPEC, an object where the conjugated forms are in `ALTERNANT_MULTIWORD_SPEC.forms`
-- for each slot. If there are no values for a slot, the slot key will be missing. The value for a given slot
-- is a list of objects {form=FORM, footnotes=FOOTNOTES}.
function export.do_generate_forms(parent_args, from_headword)
	local params = {
		[1] = {required = true, default = "jmenovat<III.2.both.tr.ppp>"},
		title = {},
		pagename = {},
		json = {type = "boolean"},
		pos = {},
	}

	local args = m_para.process(parent_args, params)

	-- Ensure we don't split a reflexive verb by replacing the space before 'se' or 'si' with an underscore.
	local function split_bracketed_runs_into_words(bracketed_runs)
		for j, segment in ipairs(bracketed_runs) do
			if j % 2 == 1 then
				bracketed_runs[j] = segment:gsub(" (s[ei])$", "_%1")
			end
		end
		return iut.default_split_bracketed_runs_into_words(bracketed_runs)
	end

	local parse_props = {
		parse_indicator_spec = parse_indicator_spec,
		split_bracketed_runs_into_words = split_bracketed_runs_into_words,
		angle_brackets_omittable = true,
		allow_blank_lemma = true,
	}
	local alternant_multiword_spec = iut.parse_inflected_text(args[1], parse_props)
	alternant_multiword_spec.title = args.title
	alternant_multiword_spec.args = args
	local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
	alternant_multiword_spec.forms = {}
	normalize_all_lemmas(alternant_multiword_spec, pagename)
	detect_all_indicator_specs(alternant_multiword_spec)
	local inflect_props = {
		slot_table = verb_slots,
		lang = lang,
		inflect_word_spec = conjugate_verb,
		-- We add links around the generated verbal forms rather than allow the entire multiword
		-- expression to be a link, so ensure that user-specified links get included as well.
		include_user_specified_links = true,
	}
	iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
	-- process_overrides(alternant_multiword_spec.forms, args)
	add_categories(alternant_multiword_spec)
	return alternant_multiword_spec
end


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


return export