Module:User:Benwing2/hi-verb

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

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 person/number/gender/tense/etc.
	 Example slot names for verbs are "inf_mp" (masculine plural infinitive),
	 "prog" (undeclined progressive form), "pfv_ind_fut_2sm" (second-person singular
	 masculine perfective indicative future). Each slot is filled with zero or
	 more forms.

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

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

--[=[

FIXME:

1. (DONE) If rearrangement for syncretism stays, need to change accelerators.
2. (DONE) In add_slot_gendered(), for participles, potentially put back obl|m|s for -e form accelerators.
3. (DONE) Split adjectival participle into perfective and habitual.
4. (DONE) Some errors in future intimate imperatives of पीना, देना, लेना.
5. Stop generating 3s and 3p forms (same as 2s and 1p forms, respectively) except for imperatives;
    rename 3s -> 23s and 1p -> 13p.
6. (DONE) Add alternative (regular) perfective participle of जाना; footnote that
   it only works with auxiliary जाना and occasionally करना in the habitual.
7. (DONE) Fix forms listed in imperative footnote for verbs like सीना, छूना.
8. (DONE) Remove fut2 forms (= to presumptive) from होना.
9. (DONE) Contrafactual -> PST only.
10. (DONE) In verbs like हिलना-डुलना, suffixes -गा and -वाला should occur only once.
11. (DONE) Indicative future of छूना should be छुऊँगा (maybe along with छूँगा?).
12. Merge cases where Devanagari spelling occurs twice with different translits.
13. (DONE) Add "rare" footnote for हूजिए.
14. (DONE) Add future tense to presumptive forms for non-aspectual and progressive.

FIXES NEEDING DISCUSSING:

1. Should we change the label of non-होना subjunctives to FUT only not PRES/FUT?
2. Should we remove subjunctive forms of imperatives and convert them to footnote?
3. Do verbs like खाना have alternative perfective participles खाये, खायी, खायीं?
4. Should 2pl pres imperative in -o be -yo after -i? If so, is -o an alternative form?
5. What is the progressive of हिलना-डुलना? Currently I have हिलते-हिलते-दुलते-दुलते.
6. Are forms like पिइयो (पीना) and जिइयो (जीना) are correct, and should forms
   like पियो and जियो be used instead or as alternatives?
7. Should we have distinct colors for masc/fem/combined rows or should they be a single color?
]=]

local lang = require("Module:languages").getByCode("hi")
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/hi-common")

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 usub = mw.ustring.sub
local uupper = mw.ustring.upper
local ulower = mw.ustring.lower

-- vowel diacritics; don't display nicely on their own
local M = u(0x0901)
local N = u(0x0902)
local AA = u(0x093e)
local AI = u(0x0948)
local AU = u(0x094c)
local E = u(0x0947)
local EN = E .. N
local I = u(0x093f)
local II = u(0x0940)
local IIN = II .. N
local O = u(0x094b)
local U = u(0x0941)
local UU = u(0x0942)
local UUM = UU .. M
local R = u(0x0943)
local VIRAMA = u(0x094d)
local TILDE = u(0x0303)

-- 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 function term_link(hi, tr)
    return m_links.full_link({term = hi, tr = tr, lang = lang}, "term")
end


local irreg_perf = {
	["कर"] = "की",
	["जा"] = "ग*", --  this is a special sign to request independent endings in some cases
	["ले"] = "ली",
	["दे"] = "दी",
	["हो"] = "हु",
}

local irreg_subj = {
	["दे"] = "द",
	["ले"] = "ल",
}

local irreg_polite_imp = {
	["कर"] = {"कीज", "कर"},
	["ले"] = "लीज",
	["दे"] = "दीज",
	["पी"] = "पीज",
	["हो"] = {"हो", {form = "हूज", footnotes = {"[rare]"}}},
}

local verb_slots_impers = {
	inf = "inf",
	inf_obl = "obl|inf",
	stem = "stem",
	conj = "conj|form",
	prog = "prog|form",
}

local verb_slots_pers = {
}

local function add_slot_gendered(slot_prefix, tag_suffix, verb_slots)
	verb_slots = verb_slots or verb_slots_pers
	verb_slots[slot_prefix .. "_ms"] = tag_suffix == "-" and "-" or "m|s|" .. tag_suffix
	verb_slots[slot_prefix .. "_mp"] = tag_suffix == "-" and "-" or "m|p|" .. tag_suffix
	verb_slots[slot_prefix .. "_fs"] = tag_suffix == "-" and "-" or "f|s|" .. tag_suffix
	verb_slots[slot_prefix .. "_fp"] = tag_suffix == "-" and "-" or "f|p|" .. tag_suffix
end

local function add_slot_gendered_part(slot_prefix, tag_suffix, verb_slots)
	verb_slots = verb_slots or verb_slots_pers
	verb_slots[slot_prefix .. "_ms"] = tag_suffix == "-" and "-" or "dir|m|s|" .. tag_suffix
	verb_slots[slot_prefix .. "_mp"] = tag_suffix == "-" and "-" or "m|p|" .. tag_suffix .. "|;|obl|m|s|" .. tag_suffix
	verb_slots[slot_prefix .. "_fs"] = tag_suffix == "-" and "-" or "f|s|" .. tag_suffix
	verb_slots[slot_prefix .. "_fp"] = tag_suffix == "-" and "-" or "f|p|" .. tag_suffix
end

local function personal_tags(slot_prefix, tag_suffix, pers, num, gender)
	gender = gender and gender .. "|" or ""
	local suffix = gender .. tag_suffix
	local is_imp = slot_prefix:find("^imp_")
	if pers == "2" and num == "s" then
		if is_imp then
			return "2|s|intim|" .. suffix
		else
			return "2|s|intim|" .. suffix .. "|;|3|s|" .. suffix
		end
	elseif pers == "1" and num == "p" and not is_imp then
		return "13|p|" .. suffix .. "|;|2|formal|" .. suffix
	elseif pers == "2" and num == "p" then
		return "2|fam|" .. suffix
	elseif pers == "3" and num == "p" then
		return "3|p|" .. suffix .. "|;|2|formal|" .. suffix
	else
		return pers .. "|" .. num .. "|" .. suffix
	end
end
	
local function add_slot_personal(slot_prefix, tag_suffix, verb_slots)
	verb_slots = verb_slots or verb_slots_pers
	for _, num in ipairs({"s", "p"}) do
		for _, pers in ipairs({"1", "2", "3"}) do
			local slot = slot_prefix .. "_" .. pers .. num
			if tag_suffix == "-" then
				verb_slots[slot] = "-"
			else
				verb_slots[slot] = personal_tags(slot_prefix, tag_suffix, pers, num)
			end
		end
	end
end

local function add_slot_gendered_personal(slot_prefix, tag_suffix, verb_slots)
	verb_slots = verb_slots or verb_slots_pers
	for _, num in ipairs({"s", "p"}) do
		for _, pers in ipairs({"1", "2", "3"}) do
			for _, gender in ipairs({"m", "f"}) do
				local slot = slot_prefix .. "_" .. pers .. num .. gender
				if tag_suffix == "-" then
					verb_slots[slot] = "-"
				else
					verb_slots[slot] = personal_tags(slot_prefix, tag_suffix, pers, num, gender)
				end
			end
		end
	end
end

add_slot_gendered_part("inf", "inf|part", verb_slots_impers)
add_slot_gendered_part("hab", "hab|part", verb_slots_impers)
add_slot_gendered_part("pfv", "pfv|part", verb_slots_impers)
add_slot_gendered_part("agent", "prospective//agentive|part", verb_slots_impers)
add_slot_gendered_part("adj_pfv", "-", verb_slots_impers)
add_slot_gendered_part("adj_hab", "-", verb_slots_impers)

add_slot_personal("ind_pres", "pres|ind") -- only for होना
add_slot_gendered("ind_impf", "impf|ind") -- only for होना
add_slot_gendered("ind_perf", "perf|ind")
add_slot_gendered_personal("ind_fut", "fut|ind")
add_slot_gendered_personal("presum", "presumptive")
add_slot_personal("subj", "subj") -- not for होना
add_slot_personal("subj_pres", "pres|subj") -- only for होना
add_slot_personal("subj_fut", "fut|subj") -- only for होना
add_slot_gendered("cfact", "cfact")
add_slot_personal("imp_pres", "pres|imp")
add_slot_personal("imp_fut", "fut|imp")

add_slot_gendered_personal("hab_ind_pres", "-")
add_slot_gendered("hab_ind_past", "-")
add_slot_gendered_personal("hab_presum", "-")
add_slot_gendered_personal("hab_subj", "-")
add_slot_gendered("hab_cfact", "-")

for _, mood in ipairs({"pfv", "prog"}) do
	add_slot_gendered_personal(mood .. "_ind_pres", "-")
	add_slot_gendered(mood .. "_ind_past", "-")
	add_slot_gendered_personal(mood .. "_ind_fut", "-")
	add_slot_gendered_personal(mood .. "_presum", "-")
	add_slot_gendered_personal(mood .. "_subj_pres", "-")
	add_slot_gendered_personal(mood .. "_subj_fut", "-")
	add_slot_gendered(mood .. "_cfact", "-")
end

verb_slots_impers["inf_ms_linked"] = verb_slots_impers["inf_ms"]

local all_verb_slots = {}
for k, v in pairs(verb_slots_impers) do
	all_verb_slots[k] = v
end
for k, v in pairs(verb_slots_pers) do
	all_verb_slots[k] = v
end


local function add(base, stem, translit_stem, slot, ending, footnotes, double_word)
	local function doadd(new_stem, new_translit_stem, new_ending, slot_footnotes)
		new_ending = new_ending or ending
		if new_ending and base.notlast then
			-- If we're not the last verb in a multiword expression, chop off
			-- anything after a space. This is to handle verbs like [[हिलना-डुलना]],
			-- which have e.g. nonaspectual subjunctive 1sg masc हिलूँ-डुलूँ
			-- but perfective future indicative 1sg masc हिला-डुला हूँगा. Also, there
			-- are some special cases:
			-- (1) the conjunctive form should be e.g. हिल-डुलकर or हिल-डुलके
			-- (2) the future 1sg should be e.g. हिलूँ-डुलूँगा
			-- (3) the agentive should be e.g. हिलने-डुलनेवाला
			if slot == "conj" then
				new_ending = ""
			elseif slot:find("^ind_fut") then
				new_ending = rsub(new_ending, "ग.$", "") -- गा, गे or गी
			elseif slot:find("^agent") then
				new_ending = rsub(new_ending, "वाल." .. N .. "?$", "") -- वाला, वाले, वाली, वालीं
			else
				new_ending = rsub(new_ending, " .*", "")
			end
		end
		com.add_form(base, new_stem or stem, new_translit_stem or translit_stem, slot,
			new_ending, iut.combine_footnotes(slot_footnotes, footnotes), "link words", double_word)
	end
	if ending then
		if rfind(stem, "[" .. II .. I .. "]$") then
			-- Implement sandhi changes after stem ending in -ī or -i.
			local stem_butlast, translit_stem_butlast =
				com.strip_ending_from_stem(stem, translit_stem, rfind(stem, II .. "$") and II or I)
			local ending_first = usub(ending, 1, 1)
			if ending_first == AA or ending_first == E then
				-- FIXME: Should this happen before all vowels? E.g. 1sg subj सिऊँ or सियूँ?
				local form_stem = stem_butlast .. I
				local form_tr = translit_stem_butlast and translit_stem_butlast .. "i"
				local function link(form_ending)
					return term_link(form_stem .. form_ending, form_tr and form_tr .. lang:transliterate(form_ending))
				end
				local aae_c = "[" .. AA .. E .. "]"
				local y_footnote
				if rfind(ending, "^" .. aae_c .. "$") or rfind(ending, "^" .. aae_c .. " ") then
					y_footnote = {"[the participles " .. link("या") .. " and " .. link("ये") ..
						" can also be spelled without the ''y'': " .. link("आ") .. " and " .. link("ए") .. "]"}
				elseif rfind(slot, "^ind_fut") then
					y_footnote = {"[the forms " .. link("येगा") .. ", " .. link("येंगे") .. ", " ..
						link("येगी") .. " and " .. link("येंगी") .. " can also be spelled without the ''y'': " ..
						link("एगा") .. ", " .. link("एँगे") .. ", " .. link("एगी") .. " and " .. link("एँगी") .. "]"}
				elseif rfind(slot, "^subj") then
					y_footnote = {"[the forms " .. link("ये") .. " and " .. link("यें") ..
					" can also be spelled without the ''y'': " .. link("ए") .. " and " .. link("एँ") .. "]"}
				else
					error("Internal error: Don't know what to do with ending " .. ending .. " for slot " .. slot)
				end
				doadd(form_stem .. "य", form_tr and form_tr .. "y", nil, y_footnote)
				-- doadd(form_stem, form_tr)
				return
			elseif ending_first == II then
				doadd(stem_butlast, translit_stem_butlast)
				return
			elseif ending_first == O then
				doadd(stem_butlast .. I .. "य", translit_stem_butlast and translit_stem_butlast .. "iy")
				return
			elseif rfind(ending_first, "[" .. com.vowels .. "]") then
				doadd(stem_butlast .. I, translit_stem_butlast and translit_stem_butlast .. "i")
				return
			end
		elseif rfind(stem, "[" .. UU .. U .. "]$") then
			-- Implement sandhi changes after stem ending in -ū or -u.
			local stem_butlast, translit_stem_butlast = com.strip_ending_from_stem(stem, translit_stem,
				rfind(stem, UU .. "$") and UU or U)
			local ending_first = usub(ending, 1, 1)
			if rfind(ending_first, "[" .. com.vowels .. "]") then
				doadd(stem_butlast .. U, translit_stem_butlast and translit_stem_butlast .. "u")
				return
			end
		elseif rfind(stem, "[" .. AA .. O .. "आऔ]$") and rfind(ending, "^" .. AA) then
			-- Implement sandhi changes after stem ending in -ā or -o and ending beginning in -ā.
			doadd(stem .. "य", translit_stem and translit_stem .. "y")
			return
		elseif rfind(stem, "[" .. E .. "ए]$") and rfind(ending, "^" .. AA) then
			local function link(form_ending)
				return term_link(stem .. form_ending, translit_stem and translit_stem .. lang:transliterate(form_ending))
			end
			-- Implement sandhi changes after stem ending in -e and ending beginning in -ā.
			local y_footnote = {"[the participle " .. link("या") .. " can also be spelled without the ''y'': " ..
				link("आ") .. "]"}
			doadd(stem .. "य", translit_stem and translit_stem .. "y", nil, y_footnote)
			-- doadd()
			return
		elseif rfind(stem, "%*$") then -- special hacks for ग(य)-, perfective stem of जाना
			local stem_butlast, translit_stem_butlast =
				com.strip_ending_from_stem(stem, translit_stem, "%*")
			if ending:find("^" .. E) or ending:find("^" .. II) then
				local function link(form_ending)
					return term_link(stem_butlast .. form_ending,
						translit_stem and translit_stem .. lang:transliterate(form_ending))
				end
				local y_footnote = {"[the participles " .. link("ए") .. ", " .. link("ई") .. " and " .. link("ईं") ..
					" can also be spelled with a ''y'': " .. link("ये") .. ", " .. link("यी") ..
					" and " .. link("यीं") .. "]"}
				-- Use full translit_stem rather than stripping off the final 'a' that should be there
				doadd(stem_butlast, translit_stem,
					ending:find("^" .. E) and rsub(ending, "^" .. E, "ए") or rsub(ending, "^" .. II, "ई"),
					y_footnote)
				-- doadd(stem_butlast .. "य", translit_stem and translit_stem .. "y")
			else
				doadd(stem_butlast .. "य", translit_stem and translit_stem .. "y")
			end
			return
		end
	end

	doadd()
end


local function add_conj_gendered(base, slot_prefix, stem, translit_stem, m_s, m_p, f_s, f_p, footnotes)
	if not stem then
		stem = base.stem
		translit_stem = base.stem_translit
	end
	add(base, stem, translit_stem, slot_prefix .. "_ms", m_s, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_mp", m_p, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_fs", f_s, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_fp", f_p, footnotes)
end


local function add_conj_personal(base, slot_prefix, stem, translit_stem, s1, s2, s3, p1, p2, p3, footnotes)
	if not stem then
		stem = base.stem
		translit_stem = base.stem_translit
	end
	add(base, stem, translit_stem, slot_prefix .. "_1s", s1, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2s", s2, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3s", s3, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1p", p1, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2p", p2, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3p", p3, footnotes)
end

local function add_conj_gendered_personal(base, slot_prefix, stem, translit_stem,
	s1m, s2m, s3m, p1m, p2m, p3m, s1f, s2f, s3f, p1f, p2f, p3f, footnotes)
	if not stem then
		stem = base.stem
		translit_stem = base.stem_translit
	end
	add(base, stem, translit_stem, slot_prefix .. "_1sm", s1m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2sm", s2m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3sm", s3m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1pm", p1m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2pm", p2m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3pm", p3m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1sf", s1f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2sf", s2f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3sf", s3f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1pf", p1f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2pf", p2f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3pf", p3f, footnotes)
end

local function handle_derived_slots_and_overrides(base)
	-- No overrides implemented currently.
	-- process_slot_overrides(base)

	-- Compute linked versions of potential lemma slots, for use in {{hi-verb}}.
	-- We substitute the original lemma (before removing links) for forms that
	-- are the same as the lemma, if the original lemma has links.
	for _, slot in ipairs({"inf_ms"}) do
		iut.insert_forms(base.forms, slot .. "_linked", iut.map_forms(base.forms[slot], function(form, translit)
			if form == base.orig_lemma_no_links and translit == base.lemma_translit
				and rfind(base.orig_lemma, "%[%[") then
				return base.orig_lemma, base.lemma_translit
			else
				return form, translit
			end
		end))
	end
end


local conjs = {}
local conjprops = {}

conjs["normal"] = function(base)
	local function fetch_irreg(irreg_table)
		if irreg_table[base.stem] then
			local stem = irreg_table[base.stem]
			return stem, nil
		else
			return base.stem, base.stem_translit
		end
	end

	local perf, trperf = fetch_irreg(irreg_perf)
	local subj, trsubj = fetch_irreg(irreg_subj)
	local polite_imp, tr_polite_imp = fetch_irreg(irreg_polite_imp)

	-- Undeclined forms
	add(base, base.stem, base.stem_translit, "stem", "")
	add(base, base.stem, base.stem_translit, "inf", "ना")
	add(base, base.stem, base.stem_translit, "inf_obl", "ने")
	add(base, base.stem, base.stem_translit, "conj", "कर")
	add(base, base.stem, base.stem_translit, "conj", "के")
	add(base, base.stem, base.stem_translit, "prog", "ते", nil, "double word")

	-- Participles
	add_conj_gendered(base, "inf", nil, nil, "ना", "ने", "नी", "नीं")
	add_conj_gendered(base, "hab", nil, nil, "ता", "ते", "ती", "तीं")
	add_conj_gendered(base, "pfv", perf, trperf, AA, E, II, IIN)
	if base.stem == "जा" then
		add_conj_gendered(base, "pfv", "जा", nil, AA, E, II, IIN,
			{"[only with the copula " .. term_link("जाना") ..
				" and (in the habitual aspect only) with the auxiliary verb " .. term_link("करना") .. "]"})
	end
	add_conj_gendered(base, "agent", nil, nil, "नेवाला", "नेवाले", "नेवाली", "नेवालीं")
	add_conj_gendered(base, "adj_pfv", perf, trperf, AA .. " हुआ", E .. " हुए", II .. " हुई", II .. " हुईं")
	add_conj_gendered(base, "adj_hab", nil, nil, "ता हुआ", "ते हुए", "ती हुई", "ती हुईं")

	-- Non-aspectual
	add_conj_gendered(base, "ind_perf", perf, trperf, AA, E, II, IIN)
	add_conj_gendered_personal(base, "ind_fut", subj, trsubj,
		UUM .. "गा", E .. "गा", E .. "गा", EN .. "गे", O .. "गे", EN .. "गे",
		UUM .. "गी", E .. "गी", E .. "गी", EN .. "गी", O .. "गी", EN .. "गी")
	if base.stem == "हो" then
		add_conj_gendered_personal(base, "presum", "", "",
			"हूँगा", "होगा", "होगा", "होंगे", "होगे", "होंगे",
			"हूँगी", "होगी", "होगी", "होंगी", "होगी", "होंगी")
		add_conj_personal(base, "ind_pres", "", "", "हूँ", "है", "है", "हैं", "हो", "हैं")
		add_conj_gendered(base, "ind_impf", "थ", "th", AA, E, II, IIN)
		add_conj_personal(base, "subj_pres", "", "", "हूँ", "हो", "हो", "हों", "हो", "हों")
		add_conj_personal(base, "subj_fut", nil, nil, UUM, E, E, EN, O, EN)
	else
		add_conj_personal(base, "subj", subj, trsubj, UUM, E, E, EN, O, EN)
	end
	add_conj_gendered(base, "cfact", nil, nil, "ता", "ते", "ती", "तीं")

	add_conj_personal(base, "imp_pres", nil, nil, nil, "")
	add_conj_personal(base, "imp_pres", subj, trsubj, nil, nil, E, nil, O)
	add_conj_personal(base, "imp_fut", subj, trsubj, nil, I .. "यो", E)
	add_conj_personal(base, "imp_fut", nil, nil, nil, nil, nil, nil, "ना")
	-- There may be more than one possible polite imperative stem, particularly for
	-- irregular verbs. Loop over them.
	if type(polite_imp) ~= "table" or polite_imp.form then
		polite_imp = {polite_imp}
	end
	if tr_polite_imp and type(tr_polite_imp) ~= "table" then
		tr_polite_imp = {tr_polite_imp}
	end
	for i, polimp in ipairs(polite_imp) do
		local tr_polimp = tr_polite_imp and tr_polite_imp[i]
		local imp_footnote
		if polimp.form then
			imp_footnote = polimp.footnotes
			polimp = polimp.form
		end
		-- Implement sandhi changes after stem ending in -ī or -ū, before ending in i-.
		-- FIXME: Merge these with the sandhi code in add().
		local sandhi_polimp, sandhi_tr_polimp
		if rfind(polimp, II .. "$") then
			sandhi_polimp, sandhi_tr_polimp = com.strip_ending_from_stem(polimp, tr_polimp, II)
			sandhi_polimp = sandhi_polimp .. I
			sandhi_tr_polimp = sandhi_tr_polimp and sandhi_tr_polimp .. "i" or nil
		elseif rfind(polimp, UU .. "$") then
			sandhi_polimp, sandhi_tr_polimp = com.strip_ending_from_stem(polimp, tr_polimp, UU)
			sandhi_polimp = sandhi_polimp .. U
			sandhi_tr_polimp = sandhi_tr_polimp and sandhi_tr_polimp .. "u" or nil
		else
			sandhi_polimp, sandhi_tr_polimp = polimp, tr_polimp
		end
		local function link_i(form_ending)
			return term_link(sandhi_polimp .. I .. form_ending,
				sandhi_tr_polimp and sandhi_tr_polimp .. "i" .. lang:transliterate(form_ending))
		end
		local y_footnote = {
			"[the forms " .. link_i("ये") .. " and " .. link_i("येगा") .. " can also be spelled without the ''y'': " ..
			link_i("ए") .. " and " .. link_i("एगा") .. "]"}
		local all_footnotes = iut.combine_footnotes(imp_footnote, y_footnote)
		add_conj_personal(base, "imp_pres", polimp, tr_polimp, nil, nil, nil, nil, nil, I .. "ये", all_footnotes)
		add_conj_personal(base, "imp_fut", polimp, tr_polimp, nil, nil, nil, nil, nil, I .. "येगा", all_footnotes)
	end

	-- Habitual
	add_conj_gendered_personal(base, "hab_ind_pres", nil, nil,
		"ता हूँ", "ता है", "ता है", "ते हैं", "ते हो", "ते हैं",
		"ती हूँ", "ती है", "ती है", "ती हैं", "ती हो", "ती हैं")
	add_conj_gendered(base, "hab_ind_past", nil, nil, "ता था", "ते थे", "ती थी", "ती थीं")
	add_conj_gendered_personal(base, "hab_presum", nil, nil,
		"ता हूँगा", "ता होगा", "ता होगा", "ते होंगे", "ते होगे", "ते होंगे",
		"ती हूँगी", "ती होगी", "ती होगी", "ती होंगी", "ती होगी", "ती होंगी")
	add_conj_gendered_personal(base, "hab_subj", nil, nil,
		"ता हूँ", "ता हो", "ता हो", "ते हों", "ते हो", "ते हों",
		"ती हूँ", "ती हो", "ती हो", "ती हों", "ती हो", "ती हों")
	add_conj_gendered(base, "hab_cfact", nil, nil,
		"ता होता", "ते होते", "ती होती", "ती होतीं")

	-- Perfective
	add_conj_gendered_personal(base, "pfv_ind_pres", perf, trperf,
		AA .. " हूँ", AA .. " है", AA .. " है", E .. " हैं", E .. " हो", E .. " हैं",
		II .. " हूँ", II .. " है", II .. " है", II .. " हैं", II .. " हो", II .. " हैं")
	add_conj_gendered(base, "pfv_ind_past", perf, trperf,
		AA .. " था", E .. " थे", II .. " थी", II .. " थीं")
	add_conj_gendered_personal(base, "pfv_ind_fut", perf, trperf,
		AA .. " हूँगा", AA .. " होगा", AA .. " होगा", E .. " होंगे", E .. " होगे", E .. " होंगे",
		II .. " हूँगी", II .. " होगी", II .. " होगी", II .. " होंगी", II .. " होगी", II .. " होंगी")
	add_conj_gendered_personal(base, "pfv_presum", perf, trperf,
		AA .. " हूँगा", AA .. " होगा", AA .. " होगा", E .. " होंगे", E .. " होगे", E .. " होंगे",
		II .. " हूँगी", II .. " होगी", II .. " होगी", II .. " होंगी", II .. " होगी", II .. " होंगी")
	add_conj_gendered_personal(base, "pfv_subj_pres", perf, trperf,
		AA .. " हूँ", AA .. " हो", AA .. " हो", E .. " हों", E .. " हो", E .. " हों",
		II .. " हूँ", II .. " हो", II .. " हो", II .. " हों", II .. " हो", II .. " हों")
	add_conj_gendered_personal(base, "pfv_subj_fut", perf, trperf,
		AA .. " होऊँ", AA .. " होए", AA .. " होए", E .. " होएँ", E .. " होओ", E .. " होएँ",
		II .. " होऊँ", II .. " होए", II .. " होए", II .. " होएँ", II .. " होओ", II .. " होएँ")
	add_conj_gendered(base, "pfv_cfact", perf, trperf, AA .. " होता", E .. " होते", II .. " होती", II .. " होतीं")

	-- Progressive
	add_conj_gendered_personal(base, "prog_ind_pres", nil, nil,
		" रहा हूँ", " रहा है", " रहा है", " रहे हैं", " रहे हो", " रहे हैं",
		" रही हूँ", " रही है", " रही है", " रही हैं", " रही हो", " रही हैं")
	add_conj_gendered(base, "prog_ind_past", nil, nil,
		" रहा था", " रहे थे", " रही थी", " रही थीं")
	add_conj_gendered_personal(base, "prog_ind_fut", nil, nil,
		" रहा हूँगा", " रहा होगा", " रहा होगा", " रहे होंगे", " रहे होगे", " रहे होंगे",
		" रही हूँगी", " रही होगी", " रही होगी", " रही होंगी", " रही होगी", " रही होंगी")
	add_conj_gendered_personal(base, "prog_presum", nil, nil,
		" रहा हूँगा", " रहा होगा", " रहा होगा", " रहे होंगे", " रहे होगे", " रहे होंगे",
		" रही हूँगी", " रही होगी", " रही होगी", " रही होंगी", " रही होगी", " रही होंगी")
	add_conj_gendered_personal(base, "prog_subj_pres", nil, nil,
		" रहा हूँ", " रहा हो", " रहा हो", " रहे हों", " रहे हो", " रहे हों",
		" रही हूँ", " रही हो", " रही हो", " रही हों", " रही हो", " रही हों")
	add_conj_gendered_personal(base, "prog_subj_fut", nil, nil,
		" रहा होऊँ", " रहा होए", " रहा होए", " रहे होएँ", " रहे होओ", " रहे होएँ",
		" रही होऊँ", " रही होए", " रही होए", " रही होएँ", " रही होओ", " रही होएँ")
	add_conj_gendered(base, "prog_cfact", nil, nil, " रहा होता", " रहे होते", " रही होती", " रही होतीं")
end


--[=[
Parse an indicator spec (text consisting of angle brackets and zero or more
dot-separated indicators within them). Return value is an object of the form

{
  forms = {}, -- forms for a single spec alternant; see `forms` below

  -- The following additional fields are added by other functions:
  orig_lemma = "ORIGINAL-LEMMA", -- as given by the user or taken from pagename
  orig_lemma_no_links = "ORIGINAL-LEMMA-NO-LINKS", -- links removed
  lemma = "LEMMA", -- `orig_lemma_no_links`, converted to singular form if plural
  phon_lemma = "LEMMA-PHONETIC-RESPELLING", -- as specified by the user; may be missing
  lemma_translit = "LEMMA-TRANSLIT", -- translit of phon_lemma (if present)
  forms = {
	SLOT = {
	  {
		form = "FORM",
		footnotes = {"FOOTNOTE", "FOOTNOTE", ...} -- may be missing
	  },
	  ...
	},
	...
  },
  conj = "CONJ", -- declension, e.g. "normal"
}
]=]
local function parse_indicator_spec(angle_bracket_spec)
	local inside = rmatch(angle_bracket_spec, "^<(.*)>$")
	assert(inside)
	local base = {overrides = {}, forms = {}}
	if inside ~= "" then
		local segments = iut.parse_balanced_segment_run(inside, "[", "]")
		local dot_separated_groups = iut.split_alternating_runs(segments, "%.")
		for i, dot_separated_group in ipairs(dot_separated_groups) do
			-- No indicators allowed currently.
			local part = dot_separated_group[1]
			error("Unrecognized indicator '" .. part .. "': '" .. inside .. "'")
		end
	end
	return base
end


local function detect_indicator_spec(base)
	base.conj = "normal"
	base.stem, base.stem_translit = com.strip_ending(base, "ना")
end


local function detect_all_indicator_specs(alternant_multiword_spec)
	iut.map_word_specs(alternant_multiword_spec, function(base)
		detect_indicator_spec(base)
	end)

	-- Set notlast=true on verbs that aren't the last one in a multiword expression, and
	-- multiword=true on all verbs in multiword expressions (as well as at top level), so
	-- we can properly handle verbs like [[हिलना-डुलना]].
	for i, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
		if alternant_or_word_spec.alternants then
			for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
				for j, word_spec in ipairs(multiword_spec.word_specs) do
					if j < #multiword_spec.word_specs then
						word_spec.notlast = true
					end
					if #multiword_spec.word_specs > 1 then
						word_spec.multiword = true
						alternant_multiword_spec.multiword = true
					end
				end
			end
		else
			if i < #alternant_multiword_spec.alternant_or_word_specs then
				alternant_or_word_spec.notlast = true
			end
			if #alternant_multiword_spec.alternant_or_word_specs > 1 then
				alternant_or_word_spec.multiword = true
				alternant_multiword_spec.multiword = true
			end
		end
	end
end


local function conjugate_verb(base)
	if not conjs[base.conj] then
		error("Internal error: Unrecognized conjugation type '" .. base.conj .. "'")
	end
	conjs[base.conj](base)
	if base.multiword then
		-- See comment in add_variant_codes() for the purpose of this.
		com.add_variant_codes(base)
	end
	handle_derived_slots_and_overrides(base)
end


-- Compute the categories to add the verb to, as well as the annotation to display in the
-- conjugation title bar. We combine the code to do these functions as both categories and
-- title bar contain similar information.
local function compute_categories_and_annotation(alternant_multiword_spec)
	local cats = {}
	local function insert(cattype)
		cattype = rsub(cattype, "~", alternant_multiword_spec.pos)
		m_table.insertIfNot(cats, "Hindi " .. cattype)
	end
	local annotation
	if alternant_multiword_spec.manual then
		alternant_multiword_spec.annotation = ""
	else
		local function do_word_spec(base)
			if base.lemma_translit and lang:transliterate(base.lemma) ~= base.lemma_translit then
				insert("~ with phonetic respelling")
			end
		end
		iut.map_word_specs(alternant_multiword_spec, function(base)
			do_word_spec(base)
		end)
	end
	alternant_multiword_spec.categories = cats
end


local function show_forms(alternant_multiword_spec)
	local lemmas = alternant_multiword_spec.forms.inf_ms or {}
	local props = {
		lang = lang,
	}
	if alternant_multiword_spec.multiword then
		-- Remove variant codes that were added to ensure only parallel variants in
		-- multiword expressions like [[हिलना-डुलना]] get generated. See com.add_variant_codes()
		-- for more information.
		props.canonicalize = function(form)
			return com.remove_variant_codes(form)
		end
	end
	iut.show_forms_with_translit(alternant_multiword_spec.forms, lemmas,
		verb_slots_impers, props, alternant_multiword_spec.footnotes_impers)
	alternant_multiword_spec.forms.footnote_impers = alternant_multiword_spec.forms.footnote
	iut.show_forms_with_translit(alternant_multiword_spec.forms, lemmas,
		verb_slots_pers, props, alternant_multiword_spec.footnotes_pers)
	alternant_multiword_spec.forms.footnote_pers = alternant_multiword_spec.forms.footnote
end


local function make_table(alternant_multiword_spec)
	local table_spec_impersonal = [=[
<div class="NavFrame">
<div class="NavHead hi-table-title" style="background: #d9ebff; width: 27em;">Impersonal forms of {inf_raw}</div>
<div class="NavContent">
{\op}| class="inflection-table inflection-hi inflection-verb" data-toggle-category="inflection"
|-
| class="hi-tense-aspect-cell"colspan=100% | ''Undeclined''
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Stem''
| colspan="100%" | {stem}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Infinitive''
| colspan="100%" | {inf}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Oblique Infinitive''
| colspan="100%" | {inf_obl}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Conjunctive''
| colspan="100%" | {conj}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Progressive''
| colspan="100%" | {prog}
|-
| class="hi-tense-aspect-cell" colspan=100% | ''Participles''
|-
|- class="hi-part-gender-number-header"
| class="hi-tense-aspect-cell" colspan=2 |
| {dir} {m} {s}
| {m} {p}<br />{obl} {m} {s}
| {f} {s}
| {f} {p}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Infinitive''
| {inf_ms}
| {inf_mp}
| {inf_fs}
| {inf_fp}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Habitual''
| {hab_ms}
| {hab_mp}
| {hab_fs}
| {hab_fp}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Perfective''
| {pfv_ms}
| {pfv_mp}
| {pfv_fs}
| {pfv_fp}
|-
| class="hi-tense-aspect-cell" colspan=2 | ''Prospective<br>Agentive''
| {agent_ms}
| {agent_mp}
| {agent_fs}
| {agent_fp}
|-
| class="hi-tense-aspect-cell" rowspan=2 | ''Adjectival''
| class="hi-tense-aspect-cell" | ''Perfective''
| {adj_pfv_ms}
| {adj_pfv_mp}
| {adj_pfv_fs}
| {adj_pfv_fp}
|-
| class="hi-tense-aspect-cell" | ''Habitual''
| {adj_hab_ms}
| {adj_hab_mp}
| {adj_hab_fs}
| {adj_hab_fp}
|{\cl}{notes_clause}</div></div>]=]

	local person_number_header_two_row = [=[
|- class="hi-table-header"
| rowspan=2 |
| rowspan=2 |
| class="hi-mf-cell" rowspan=2 |
]=]

	local person_number_header_sg_pl_headers = [=[
| colspan=3 | '''Singular'''
| colspan=1 | '''Singular/Plural'''
| colspan=2 | '''Plural/Formal'''
]=]

	local person_number_header_table_div = [=[
|- class="hi-table-header"
]=]

	local person_number_header_pers_num_headers = [=[
| '''1<sup>st</sup>'''<br><span lang="hi" class="Deva">[[मैं]]</span>
| '''2<sup>nd</sup> intimate'''<br><span lang="hi" class="Deva">[[तू]]</span>
| '''3<sup>rd</sup>'''<br><span lang="hi" class="Deva">[[यह]]/[[वह]]</span>
| '''2<sup>nd</sup> familiar'''<br><span lang="hi" class="Deva">[[तुम]]</span>
| '''1<sup>st</sup>'''<br><span lang="hi" class="Deva">[[हम]]</span>
| '''2<sup>nd</sup> formal, 3<sup>rd</sup>'''<br><span lang="hi" class="Deva">[[ये]]/[[वे]]/[[आप]]</span>
]=]

	local person_number_header =
		person_number_header_table_div .. person_number_header_two_row .. person_number_header_sg_pl_headers ..
		person_number_header_table_div .. person_number_header_pers_num_headers
	local reversed_person_number_header =
		person_number_header_table_div .. person_number_header_two_row .. person_number_header_pers_num_headers ..
		person_number_header_table_div .. person_number_header_sg_pl_headers

	local table_spec_personal = [=[
<div class="NavFrame">
<div class="NavHead hi-table-title" style="background: #d9ebff; width: 47em;">Personal forms of {inf_raw}</div>
<div class="NavContent">
{\op}| class="inflection-table inflection-hi inflection-verb" data-toggle-category="inflection"
|-
| class="hi-sec-div" rowspan=1 colspan=100% | ''Non-Aspectual''
{person_number_header}{pres_impf_table}| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PERF}
| class="hi-mf-cell" | {m}
| colspan=3 | {ind_perf_ms}
| colspan=3 | {ind_perf_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {ind_perf_fs}
| colspan=2 | {ind_perf_fp}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {FUT}
| class="hi-mf-cell" | {m}
| {ind_fut_1sm}
| colspan=2 | {ind_fut_2sm}
| {ind_fut_2pm}
| colspan=2 | {ind_fut_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {ind_fut_1sf}
| colspan=2 | {ind_fut_2sf}
| {ind_fut_2pf}
| colspan=2 | {ind_fut_1pf}
{presum_table}{subj_table}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Contrafactual''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PST}
| class="hi-mf-cell" | {m}
| colspan=3 | {cfact_ms}
| colspan=3 | {cfact_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {cfact_fs}
| colspan=2 | {cfact_fp}
|-
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Imperative''
| class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {PRS}
| class="hi-mf-cell" | {mf}
|
| {imp_pres_2s}
| {imp_pres_3s}
| {imp_pres_2p}
|
| {imp_pres_3p}
|-
| class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {FUT}
| class="hi-mf-cell" | {mf}
|
| {imp_fut_2s}
| {imp_fut_3s}
| {imp_fut_2p}
|
| {imp_fut_3p}
|-
| class="hi-sec-div" rowspan=1 colspan=100% | ''Habitual''
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=4 colspan=1 | ''Indicative''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS}
| class="hi-mf-cell" | {m}
| {hab_ind_pres_1sm}
| colspan=2 | {hab_ind_pres_2sm}
| {hab_ind_pres_2pm}
| colspan=2 | {hab_ind_pres_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {hab_ind_pres_1sf}
| colspan=2 | {hab_ind_pres_2sf}
| {hab_ind_pres_2pf}
| colspan=2 | {hab_ind_pres_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PST}
| class="hi-mf-cell" | {m}
| colspan=3 | {hab_ind_past_ms}
| colspan=3 | {hab_ind_past_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {hab_ind_past_fs}
| colspan=2 | {hab_ind_past_fp}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Presumptive''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS_PST}
| class="hi-mf-cell" | {m}
| {hab_presum_1sm}
| colspan=2 | {hab_presum_2sm}
| {hab_presum_2pm}
| colspan=2 | {hab_presum_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {hab_presum_1sf}
| colspan=2 | {hab_presum_2sf}
| {hab_presum_2pf}
| colspan=2 | {hab_presum_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Subjunctive''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS}
| class="hi-mf-cell" | {m}
| {hab_subj_1sm}
| colspan=2 | {hab_subj_2sm}
| {hab_subj_2pm}
| colspan=2 | {hab_subj_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {hab_subj_1sf}
| colspan=2 | {hab_subj_2sf}
| {hab_subj_2pf}
| colspan=2 | {hab_subj_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Contrafactual''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PST}
| class="hi-mf-cell" | {m}
| colspan=3 | {hab_cfact_ms}
| colspan=3 | {hab_cfact_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {hab_cfact_fs}
| colspan=2 | {hab_cfact_fp}
|-
| class="hi-sec-div" rowspan=1 colspan=100% | ''Perfective''
{person_number_header}|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=6 colspan=1 | ''Indicative''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS}
| class="hi-mf-cell" | {m}
| {pfv_ind_pres_1sm}
| colspan=2 | {pfv_ind_pres_2sm}
| {pfv_ind_pres_2pm}
| colspan=2 | {pfv_ind_pres_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {pfv_ind_pres_1sf}
| colspan=2 | {pfv_ind_pres_2sf}
| {pfv_ind_pres_2pf}
| colspan=2 | {pfv_ind_pres_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PST}
| class="hi-mf-cell" | {m}
| colspan=3 | {pfv_ind_past_ms}
| colspan=3 | {pfv_ind_past_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {pfv_ind_past_fs}
| colspan=2 | {pfv_ind_past_fp}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {FUT}
| class="hi-mf-cell" | {m}
| {pfv_ind_fut_1sm}
| colspan=2 | {pfv_ind_fut_2sm}
| {pfv_ind_fut_2pm}
| colspan=2 | {pfv_ind_fut_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {pfv_ind_fut_1sf}
| colspan=2 | {pfv_ind_fut_2sf}
| {pfv_ind_fut_2pf}
| colspan=2 | {pfv_ind_fut_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Presumptive''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS_PST}
| class="hi-mf-cell" | {m}
| {pfv_presum_1sm}
| colspan=2 | {pfv_presum_2sm}
| {pfv_presum_2pm}
| colspan=2 | {pfv_presum_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {pfv_presum_1sf}
| colspan=2 | {pfv_presum_2sf}
| {pfv_presum_2pf}
| colspan=2 | {pfv_presum_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=4 colspan=1 | ''Subjunctive''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS}
| class="hi-mf-cell" | {m}
| {pfv_subj_pres_1sm}
| colspan=2 | {pfv_subj_pres_2sm}
| {pfv_subj_pres_2pm}
| colspan=2 | {pfv_subj_pres_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {pfv_subj_pres_1sf}
| colspan=2 | {pfv_subj_pres_2sf}
| {pfv_subj_pres_2pf}
| colspan=2 | {pfv_subj_pres_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {FUT}
| class="hi-mf-cell" | {m}
| {pfv_subj_fut_1sm}
| colspan=2 | {pfv_subj_fut_2sm}
| {pfv_subj_fut_2pm}
| colspan=2 | {pfv_subj_fut_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {pfv_subj_fut_1sf}
| colspan=2 | {pfv_subj_fut_2sf}
| {pfv_subj_fut_2pf}
| colspan=2 | {pfv_subj_fut_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Contrafactual''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PST}
| class="hi-mf-cell" | {m}
| colspan=3 | {pfv_cfact_ms}
| colspan=3 | {pfv_cfact_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {pfv_cfact_fs}
| colspan=2 | {pfv_cfact_fp}
|-
| class="hi-sec-div" rowspan=1 colspan=100% | ''Progressive''
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=6 colspan=1 | ''Indicative''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS}
| class="hi-mf-cell" | {m}
| {prog_ind_pres_1sm}
| colspan=2 | {prog_ind_pres_2sm}
| {prog_ind_pres_2pm}
| colspan=2 | {prog_ind_pres_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {prog_ind_pres_1sf}
| colspan=2 | {prog_ind_pres_2sf}
| {prog_ind_pres_2pf}
| colspan=2 | {prog_ind_pres_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PST}
| class="hi-mf-cell" | {m}
| colspan=3 | {prog_ind_past_ms}
| colspan=3 | {prog_ind_past_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {prog_ind_past_fs}
| colspan=2 | {prog_ind_past_fp}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {FUT}
| class="hi-mf-cell" | {m}
| {prog_ind_fut_1sm}
| colspan=2 | {prog_ind_fut_2sm}
| {prog_ind_fut_2pm}
| colspan=2 | {prog_ind_fut_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {prog_ind_fut_1sf}
| colspan=2 | {prog_ind_fut_2sf}
| {prog_ind_fut_2pf}
| colspan=2 | {prog_ind_fut_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Presumptive''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS_PST_FUT}
| class="hi-mf-cell" | {m}
| {prog_presum_1sm}
| colspan=2 | {prog_presum_2sm}
| {prog_presum_2pm}
| colspan=2 | {prog_presum_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {prog_presum_1sf}
| colspan=2 | {prog_presum_2sf}
| {prog_presum_2pf}
| colspan=2 | {prog_presum_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=4 colspan=1 | ''Subjunctive''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS}
| class="hi-mf-cell" | {m}
| {prog_subj_pres_1sm}
| colspan=2 | {prog_subj_pres_2sm}
| {prog_subj_pres_2pm}
| colspan=2 | {prog_subj_pres_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {prog_subj_pres_1sf}
| colspan=2 | {prog_subj_pres_2sf}
| {prog_subj_pres_2pf}
| colspan=2 | {prog_subj_pres_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {FUT}
| class="hi-mf-cell" | {m}
| {prog_subj_fut_1sm}
| colspan=2 | {prog_subj_fut_2sm}
| {prog_subj_fut_2pm}
| colspan=2 | {prog_subj_fut_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {prog_subj_fut_1sf}
| colspan=2 | {prog_subj_fut_2sf}
| {prog_subj_fut_2pf}
| colspan=2 | {prog_subj_fut_1pf}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Contrafactual''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PST}
| class="hi-mf-cell" | {m}
| colspan=3 | {prog_cfact_ms}
| colspan=3 | {prog_cfact_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {prog_cfact_fs}
| colspan=2 | {prog_cfact_fp}
{reversed_person_number_header}|{\cl}{notes_clause}</div></div>]=]

	local pres_impf_table = [=[
|-
| class="hi-tense-aspect-cell" rowspan=7 colspan=1 | ''Indicative''
| class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {PRS}
| class="hi-mf-cell" | {mf}
| {ind_pres_1s}
| colspan=2 | {ind_pres_2s}
| {ind_pres_2p}
| colspan=2 | {ind_pres_1p}
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {IMPF}
| class="hi-mf-cell" | {m}
| colspan=3 | {ind_impf_ms}
| colspan=3 | {ind_impf_mp}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| colspan=4 | {ind_impf_fs}
| colspan=2 | {ind_impf_fp}
|- class="hi-row-m"
]=]

	local pres_impf_table_missing = [=[
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=4 colspan=1 | ''Indicative''
]=]

	local presum_table = [=[
|- class="hi-row-m"
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Presumptive''
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | {PRS_PST_FUT}
| class="hi-mf-cell" | {m}
| {presum_1sm}
| colspan=2 | {presum_2sm}
| {presum_2pm}
| colspan=2 | {presum_1pm}
|- class="hi-row-f"
| class="hi-mf-cell" | {f}
| {presum_1sf}
| colspan=2 | {presum_2sf}
| {presum_2pf}
| colspan=2 | {presum_1pf}
]=]

	local combined_subj = [=[
|-
| class="hi-tense-aspect-cell" rowspan=1 colspan=1 | ''Subjunctive''
| class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {PRS_FUT}
| class="hi-mf-cell" | {mf}
| {subj_1s}
| colspan=2 | {subj_2s}
| {subj_2p}
| colspan=2 | {subj_1p}]=]

	local split_subj = [=[
|-
| class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Subjunctive''
| class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {PRS}
| class="hi-mf-cell" | {mf}
| {subj_pres_1s}
| colspan=2 | {subj_pres_2s}
| {subj_pres_2p}
| colspan=2 | {subj_pres_1p}
|-
| class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {FUT}
| class="hi-mf-cell" | {mf}
| {subj_fut_1s}
| colspan=2 | {subj_fut_2s}
| {subj_fut_2p}
| colspan=2 | {subj_fut_1p}]=]

	local notes_template = [===[
<div class="hi-footnote-outer-div">
<div class="hi-footnote-inner-div">
{footnote}
</div></div>
]===]

	local forms = alternant_multiword_spec.forms

	local function make_gender_abbr(title, text)
		return '<span class="gender"><abbr title="' .. title .. '">' .. text .. '</abbr></span>'
	end
	local function make_tense_aspect_abbr(title, text)
		local template = [=[
''<abbr style="font-variant: small-caps; text-transform: lowercase;" title="{title}">{text}</abbr>'']=]
		return m_string_utilities.format(template, {title = title, text = text})
	end
	forms.m = make_gender_abbr("masculine gender", "m")
	forms.f = make_gender_abbr("feminine gender", "f")
	forms.mf = forms.m .. "<br />" .. forms.f
	forms.s = make_gender_abbr("singular number", "s")
	forms.p = make_gender_abbr("plural number", "p")
	forms.dir = make_gender_abbr("direct", "dir")
	forms.obl = make_gender_abbr("oblique", "obl")
	forms.PERF = make_tense_aspect_abbr("Perfect", "PERF")
	forms.IMPF = make_tense_aspect_abbr("Imperfect", "IMPF")
	forms.PST = make_tense_aspect_abbr("Past", "PST")
	forms.FUT = make_tense_aspect_abbr("Future", "FUT")
	forms.PRS = make_tense_aspect_abbr("Present", "PRS")
	forms.PRS_FUT = make_tense_aspect_abbr("Present/Future", "PRS<br />FUT")
	forms.PRS_PST = make_tense_aspect_abbr("Present/Past", "PRS<br />PST")
	forms.PRS_PST_FUT = make_tense_aspect_abbr("Present/Past/Future", "PRS<br />PST<br />FUT")
	forms.inf_raw = tag_text(forms.lemma)

	-- Now format the impersonal table.
	forms.footnote = forms.footnote_impers
	forms.notes_clause = forms.footnote ~= "" and
		m_string_utilities.format(notes_template, forms) or ""
	local formatted_table_impers = m_string_utilities.format(table_spec_impersonal, forms)

	-- Now format the personal table.
	forms.footnote = forms.footnote_pers
	forms.notes_clause = forms.footnote ~= "" and
		m_string_utilities.format(notes_template, forms) or ""
	if forms.ind_pres_1s ~= "—" then -- होना
		forms.subj_table = m_string_utilities.format(split_subj, forms)
		forms.pres_impf_table = m_string_utilities.format(pres_impf_table, forms)
		forms.presum_table = m_string_utilities.format(presum_table, forms)
	else
		forms.subj_table = m_string_utilities.format(combined_subj, forms)
		forms.pres_impf_table = pres_impf_table_missing
		forms.presum_table = ""
	end
	forms.person_number_header = person_number_header
	forms.reversed_person_number_header = reversed_person_number_header
	local formatted_table_pers = m_string_utilities.format(table_spec_personal, forms)

	-- Concatenate both.
	return require("Module:TemplateStyles")("Module:User:Benwing2/hi-verb/style.css") .. formatted_table_impers .. formatted_table_pers
end


-- Implementation of template 'hi-verb cat'.
function export.catboiler(frame)
	local SUBPAGENAME = mw.title.getCurrentTitle().subpageText
	local params = {
		[1] = {},
	}
	local args = m_para.process(frame:getParent().args, params)

	local function get_pos()
		local pos = rmatch(SUBPAGENAME, "^Hindi.- ([^ ]*)s ")
		if not pos then
			pos = rmatch(SUBPAGENAME, "^Hindi.- ([^ ]*)s$")
		end
		if not pos then
			error("Invalid category name, should be e.g. \"Hindi verbs with ...\" or \"Hindi ... verbs\"")
		end
		return pos
	end

	local function get_sort_key()
		local pos, sort_key = rmatch(SUBPAGENAME, "^Hindi.- ([^ ]*)s with (.*)$")
		if sort_key then
			return sort_key
		end
		pos, sort_key = rmatch(SUBPAGENAME, "^Hindi ([^ ]*)s (.*)$")
		if sort_key then
			return sort_key
		end
		return rsub(SUBPAGENAME, "^Hindi ", "")
	end

	local cats = {}, pos

	-- Insert the category CAT (a string) into the categories. String will
	-- have "Hindi " prepended and ~ substituted for the plural part of speech.
	local function insert(cat, atbeg)
		local fullcat = "Hindi " .. rsub(cat, "~", pos .. "s")
		if atbeg then
			table.insert(cats, 1, fullcat)
		else
			table.insert(cats, fullcat)
		end
	end

	local maintext
	while true do
		if args[1] then
			maintext = "~ " .. args[1]
			pos = get_pos()
			break
		end

		error("Unrecognized Hindi verb category name")
	end

	insert("~|" .. get_sort_key(), "at beginning")

	local categories = {}
	for _, cat in ipairs(cats) do
		table.insert(categories, "[[Category:" .. cat .. "]]")
	end

	return "This category contains Hindi " .. rsub(maintext, "~", pos .. "s")
		.. "\n" ..
		mw.getCurrentFrame():expandTemplate{title="hi-categoryTOC", args={}}
		.. table.concat(categories, "")
end

-- Externally callable function to parse and conjugate a verb given user-specified arguments.
-- Return value is WORD_SPEC, an object where the conjugated forms are in `WORD_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, translit=TRANSLIT, footnotes=FOOTNOTES}.
function export.do_generate_forms(parent_args, pos, from_headword, def)
	local params = {
		[1] = {},
		footnote_impers = {list = true},
		footnote_pers = {list = true},
		title = {},
	}

	if from_headword then
		params["lemma"] = {list = true}
		params["id"] = {}
	end

	local args = m_para.process(parent_args, params)
	local PAGENAME = mw.title.getCurrentTitle().text

	if not args[1] then
		if PAGENAME == "hi-conj" then
			args[1] = def or "करना"
		else
			args[1] = PAGENAME
			-- If pagename has spaces in it, add links around each word
			if args[1]:find(" ") then
				args[1] = "[[" .. rsub(args[1], " ", "]] [[") .. "]]"
			end
		end
	end
	local parse_props = {
		parse_indicator_spec = parse_indicator_spec,
		lang = lang,
		transliterate_respelling = com.transliterate_respelling,
		allow_default_indicator = 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.footnotes_impers = args.footnote_impers
	alternant_multiword_spec.footnotes_pers = args.footnote_pers
	alternant_multiword_spec.pos = pos or "verbs"
	alternant_multiword_spec.args = args
	com.normalize_all_lemmas(alternant_multiword_spec)
	detect_all_indicator_specs(alternant_multiword_spec)
	local inflect_props = {
		slot_table = all_verb_slots,
		lang = lang,
		inflect_word_spec = conjugate_verb,
		-- Return the variant code that was added to ensure only parallel variants in
		-- multiword expressions like [[हिलना-डुलना]] get generated. See com.add_variant_codes()
		-- for more information.
		get_variants = alternant_multiword_spec.multiword and com.get_variants or nil,
		-- 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)
	compute_categories_and_annotation(alternant_multiword_spec)
	return alternant_multiword_spec
end


-- Entry point for {{hi-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


-- Concatenate all forms of all slots into a single string of the form
-- "SLOT=FORM,FORM,...|SLOT=FORM,FORM,...|...". Each FORM is either a string in Devanagari or
-- (if manual translit is present) a specification of the form "FORM//TRANSLIT" where FORM is the
-- Devanagari representation of the form and TRANSLIT its manual transliteration. Embedded pipe symbols
-- (as might occur in embedded links) are converted to <!>. If INCLUDE_PROPS is given, also include
-- additional properties (currently, g= for headword genders). This is for use by bots.
local function concat_forms(alternant_spec, include_props)
	local ins_text = {}
	for slot, _ in pairs(verb_slots_with_linked) do
		local formtext = iut.concat_forms_in_slot(alternant_spec.forms[slot])
		if formtext then
			table.insert(ins_text, slot .. "=" .. formtext)
		end
	end
	if include_props then
		table.insert(ins_text, "g=" .. table.concat(alternant_spec.genders, ","))
	end
	return table.concat(ins_text, "|")
end


-- Template-callable function to parse and conjugate a verb given user-specified arguments and return
-- the forms as a string of the same form as documented in concat_forms() above.
function export.generate_forms(frame)
	local include_props = frame.args["include_props"]
	local parent_args = frame:getParent().args
	local alternant_spec = export.do_generate_forms(parent_args)
	return concat_forms(alternant_spec, include_props)
end

return export