Module:pa-verb

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

local export = {}


--[=[

Authorship: Aryaman Arora <AryamanA>, based on work by Ben Wing <benwing2>

]=]

--[=[
combine_stem_ending_tr
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 Punjabi form representing the value of a given slot.

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

--[=[

FIXME:
]=]

local lang = require("Module:languages").getByCode("pa")
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:inflection utilities")
local m_para = require("Module:parameters")
local com = require("Module:pa-common")

local u = require("Module:string/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 AA = u(0x0a3e)
local AI = u(0x0a48)
local AU = u(0x0a4c)
local E = u(0x0a47)
local I = u(0x0a3f)
local II = u(0x0a40)
local O = u(0x0a4b)
local U = u(0x0a41)
local UU = u(0x0a42)
local R = u(0x0a43) --nonexistent
local VIRAMA = u(0x0a4d)
local TILDE = u(0x0303)

local M = 'ੰ'
local N = u(0x0a02)
local EN = E .. N
local IIN = II .. N
local UUM = UU .. M


local function term_link(hi, tr)
    return m_links.full_link({term = hi, tr = tr, lang = lang}, "term")
end

local sii_note = "[Some dialects conjugate " .. term_link("ਸੀ") .. " for person and number (from left to right): " .. term_link("ਸਾਂ") .. ", " .. term_link("ਸੈ") .. ", " .. term_link("ਸੋ") .. ", " .. term_link("ਸਾਂ") .. ", " .. term_link("ਸਨ") .. "]"

-- 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 irreg_perf = {
	["ਦੇ"] = "ਦਿੱਤ*",
	["ਲੇ"] = "ਲਿੱਤ*",
	["ਸੌ"] = "ਸੁੱਤ*",
	["ਨਹਾਉ"] = "ਨਹਾਤ*",
	["ਪੀ"] = "ਪੀਤ*",
	["ਸੀ"] = "ਸੀਤ*",
	["ਕਰ"] = "ਕੀਤ*",
	["ਜਾ"] = "ਗ",
	["ਖਾ"] = "ਖਾਦ*",
	["ਮਰ"] = "ਮੋ",
	["ਧੋ"] = "ਧੋਤ*",
}

local irreg_subj = {
}

local irreg_polite_imp = {
}

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

local verb_slots_pers = {
}

-- Add entries for a slot with only gender/number variants.
-- `slot_prefix` is the prefix of the slot, typically specifying the tense/aspect;
-- `tag_suffix` is the set of inflection tags to add after the gender/number tags,
-- or "-" to use "-" as the inflection tags (which indicates that no accelerator entry
-- should be generated); and `verb_slots` is the table (personal or impersonal) to
-- add the entries to.
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

-- Same as add_slot_gendered() but specifically for participles. This changes the inflection
-- tags used, because the masculine singular entry is really only for direct masculine singular,
-- and the masculine plural entry is also for oblique masculine singular.
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

-- Compute the inflection tags associated with a given person/number/gender combination.
local function personal_tags(slot_prefix, tag_suffix, persnum, gender)
	gender = gender and gender .. "|" or ""
	local suffix = gender .. tag_suffix
	if persnum == "2s" then -- only for imperatives
		return "2|s|intim|" .. suffix
	elseif persnum == "23s" then
		return "2|s|intim|" .. suffix .. "|;|3|s|" .. suffix
	elseif persnum == "1p" then
		return "1|p|" .. suffix .. "|;|2|formal|" .. suffix
	elseif persnum == "2p" then
		return "2|fam|" .. suffix
	elseif persnum == "3p" then
		return "3|p|" .. suffix .. "|;|2|formal|" .. suffix
	else
		-- 1s, 3s or 1p
		return persnum:gsub("^(.)(.)$", "%1|%2") .. "|" .. suffix
	end
end

-- Return the possible person/number combinations given the slot prefix.
local function persnum_values(slot_prefix)
	if slot_prefix:find("^imp_") then
		return {"2s", "2p", "3p"}
	else
		return {"1s", "23s", "1p", "3p", "2p"}
	end
end

-- Add entries for a slot with only person/number variants. See `add_slot_gendered()`.
local function add_slot_personal(slot_prefix, tag_suffix, verb_slots)
	verb_slots = verb_slots or verb_slots_pers
	for _, persnum in ipairs(persnum_values(slot_prefix)) do
		local slot = slot_prefix .. "_" .. persnum
		if tag_suffix == "-" then
			verb_slots[slot] = "-"
		else
			verb_slots[slot] = personal_tags(slot_prefix, tag_suffix, persnum)
		end
	end
end

-- Add entries for a slot with person/number/gender variants. See `add_slot_gendered()`.
local function add_slot_gendered_personal(slot_prefix, tag_suffix, verb_slots)
	verb_slots = verb_slots or verb_slots_pers
	for _, persnum in ipairs(persnum_values(slot_prefix)) do
		for _, gender in ipairs({"m", "f"}) do
			local slot = slot_prefix .. "_" .. persnum .. gender
			if tag_suffix == "-" then
				verb_slots[slot] = "-"
			else
				verb_slots[slot] = personal_tags(slot_prefix, tag_suffix, persnum, gender)
			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_personal("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_pres", "pres|subj") -- only for होना
add_slot_personal("subj_fut", "fut|subj")
add_slot_gendered("cfact", "cfact")
add_slot_personal("imp_reg", "reg|imp")
add_slot_personal("imp_pol", "pol|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(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

-- Add one inflected form to `base.forms`, specifically to the list of forms associated with
-- the slot `slot` in the table in `base.forms`. Each element of the list is an object of the
-- form {form=FORM, translit=TRANSLIT, footnotes=FOOTNOTES}, where TRANSLIT is missing if no
-- manual translit needs to be given and FOOTNOTES is missing if there aren't any footnotes.
-- If FOOTNOTES is present it is a list of footnotes, where each footnote is e.g. "[rare or archaic]",
-- i.e. surrounded by brackets, with the first character lowercase and no final period.
-- (The brackets are automatically removed, the first character capitalized and a final period added.)
--
-- `stem` is the Devanagari stem to add the ending to.
-- `translit_stem` is the transliteration of `stem`, or nil to use the default transliteration.
-- `ending` is the Devanagari ending to add to the stem, possibly with sandhi changes to the
-- stem or ending.
-- `footnotes` is a list of associated footnotes in the same format as FOOTNOTES above, or nil.
-- `double_word` if given causes the resulting form to be doubled with a hyphen in between the two
-- parts, for use with the progressive form (e.g. करते-करते of verb करना).
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 rfind(new_ending, "ਹਨ$") then slot_footnotes = iut.combine_footnotes(slot_footnotes, {"[colloquially, " .. term_link("ਹਨ") .. " is always " .. term_link("ਨੇ") .. " in the Majhi dialect]"}) end
		if rfind(new_ending, ".ਸੀ$") then slot_footnotes = iut.combine_footnotes(slot_footnotes, sii_note) end
		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 indicative should be e.g. 1sg हिलूँ-डुलूँगा
			-- (3) the future imperative 3pl should be e.g. हिलिये-डुलियेगा
			-- (4) the agentive should be e.g. हिलने-डुलनेवाला
			if slot == "conj" then
				new_ending = ""
			elseif slot:find("^ind_pol") then
				new_ending = rsub(new_ending, "ਗ.*$", "") -- गा, गे or गी
			elseif slot == "imp_pol_3p" then
				new_ending = rsub(new_ending, "गा$", "")--??
			elseif slot:find("^agent") then
				new_ending = rsub(new_ending, "ਵਾਲ.*$", "") -- वाला, वाले, वाली, वालीं
			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, "^ਗ$") then -- irregular ga stem
			ending = rsubn(ending, "^" .. E, "ਏ")
			ending = rsubn(ending, "^" .. II, "ਈ")
			ending = rsubn(ending, "^" .. I .. "ਆਂ", "ਇਆਂ")
		end
		if rfind(stem, "*$") then -- some irregular stems should force -iā to turn into -ā
			stem = rsubn(stem, "*$", "")
			if rfind(ending, "^" .. I .. "ਆ$") then
				doadd(stem, nil, AA)
				return
			elseif rfind(ending, "^" .. I .. "ਆ ") then
				local m = rsubn(ending, "^" .. I .. "ਆ", AA)
				doadd(stem, nil, m)
				return
			end
		end
		if rfind(stem, "..[" .. U ..  "ਉ]$") then
			if rfind(ending, "^[" .. AA .. I .. II .. U .. UU .. E .. AI .. O .. AU .. "]") or rfind(ending, "^ ") or rfind(ending, "^$") then
				stem = rsubn(stem, "[" .. U ..  "ਉ]$", "")
			end
		end	
		if rfind(ending, "^ਦ") and rfind(stem, "[" .. AA .. I .. II .. U .. UU .. E .. AI .. O .. AU .. "ਅਆਇਈਉਊਏਐਓਔ]$") and stem ~= "ਧੋ" then
			if rfind(stem, "^ਹੋ$") then
				stem = "ਹੁ"
			end
			if rfind(stem, "[" .. AA .. I .. U .. UU .. "ਅਆਇ]$") then doadd(stem .. M)
			else doadd(stem .. N) end
			return
		end
		if rfind(ending, "^[" .. AA .. E .. "]") and rfind(stem, "[" .. AA .. "ਆ]$") and not (slot:find('perf') or slot:find('pfv')) then
			doadd(stem .. "ਵ")
			if rfind(ending, "^" .. E) then
				local m = rsubn(ending, "^" .. E, "ਏ")
				doadd(stem, nil, m, {"[usually less common]"})
			end
			return
		end
		if rfind(ending, "^[" .. O .. AA .. E .. "]") and rfind(stem, "[" .. O .. "ਓ]$") then
			doadd(stem .. "ਵ")
			return
		end
		if rfind(stem, "[" .. AI .. "ਐ]$") then
			if rfind(ending, "^" .. I .. "ਆ$") or rfind(ending, "^" .. I .. "ਆ ")  then
				local n = rsubn(stem, AI .. "$", I)
				n = rsubn(n, "ਐ" .. "$", "ਇ")
				doadd(n, nil, AA)
				return
			elseif rfind(ending, "^[" .. AA .. E .. "]") and not (slot:find('perf') or slot:find('pfv')) then
				local n = rsubn(stem, "[" .. AI .. "ਐ]$", "")
				doadd(n .. "ਵ")
				if rfind(ending, "^" .. E) then
					local m = rsubn(ending, "^" .. E, "ਏ")
					doadd(n, nil, m, {"[usually less common]"})
				end
				return
			elseif rfind(ending, "^[" .. AA .. I .. II .. U .. UU .. E .. AI .. O .. AU .. "]") then
				local n = rsubn(stem, "[" .. AI .. "ਐ]$", "")
				local m = rsubn(ending, "^[" .. AA .. I .. II .. U .. UU .. E .. AI .. O .. AU .. "]", com.diacritic_to_independent)
				doadd(n, nil, m)
				return
			end
		end
		if rfind(stem, ".ਹਿ$") then
			if rfind(ending, "^" .. I .. "ਆ$") then
				local n = rsubn(stem, "ਹਿ$", I .. "ਹ")
				doadd(n, nil, AA)
				return
			elseif rfind(ending, "^" .. I .. "ਆ ") then
				local n = rsubn(stem, "ਹਿ$", I .. "ਹ")
				local m = rsubn(ending, "^" .. I .. "ਆ", AA)
				doadd(n, nil, m)
				return
			elseif rfind(ending, "^[" .. AA .. I .. II .. U .. UU .. E .. AI .. O .. AU .. "]") then
				local n = rsubn(stem, "ਹਿ$", "ਹ")
				doadd(n)
				return
			end
		end
		if rfind(stem, "[ਰਣ]$") then
			if rfind(ending, "^ ?ਣ") then
				local n = rsubn(ending, "^ਣ", "ਨ")
				doadd(stem, nil, n)
				return
			end
		end
	end

	doadd()
end


-- Add the conjugation for a tense/aspect row with gender/number variants only.
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


-- Add the conjugation for a tense/aspect row with person/number variants only.
local function add_conj_personal(base, slot_prefix, stem, translit_stem, s1, s23, p1, p3, p2, 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 .. "_23s", s23, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1p", p1, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3p", p3, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2p", p2, footnotes)
end

-- Add the conjugation for a tense/aspect row with person/number/gender variants.
local function add_conj_gendered_personal(base, slot_prefix, stem, translit_stem,
	s1m, s23m, p1m, p3m, p2m, s1f, s23f, p1f, p3f, p2f, 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 .. "_23sm", s23m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1pm", p1m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3pm", p3m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2pm", p2m, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1sf", s1f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_23sf", s23f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_1pf", p1f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_3pf", p3f, footnotes)
	add(base, stem, translit_stem, slot_prefix .. "_2pf", p2f, 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.
	-- (NOTE: Not currently used by {{hi-verb}}.)
	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", "")
	if rfind(base.stem, "[ੜਰ](੍ਹ)?$") then
		add(base, base.stem, base.stem_translit, "inf", "ਨਾ")
		add(base, base.stem, base.stem_translit, "inf_obl", "ਨ")
	else
		add(base, base.stem, base.stem_translit, "inf", "ਣਾ")
		add(base, base.stem, base.stem_translit, "inf_obl", "ਣ")
	end
	add(base, base.stem, base.stem_translit, "conj", "ਕੇ")
	-- Normally progressive form is doubled (e.g. करते-करते), but not in multiword
	-- expressions like हिलना-डुलना.
	add(base, base.stem, base.stem_translit, "prog", "ਦੇ", nil, not base.multiword)

	-- Participles
	add_conj_gendered(base, "hab", nil, nil, "ਦਾ", "ਦੇ", "ਦੀ", "ਦਿਆਂ")
	add_conj_gendered(base, "pfv", perf, trperf, I .. "ਆ", E, II, I .. "ਆਂ")
	add_conj_gendered(base, "agent", nil, nil, "ਣ ਵਾਲ਼ਾ", "ਣ ਵਾਲ਼ੇ", "ਣ ਵਾਲ਼ੀ", "ਣ ਵਾਲ਼ਿਆਂ", "[The presence of the" .. term_link("ਲ਼") .. " phoneme can vary by dialect " .. term_link("ਲ") .. "]")
	add_conj_gendered(base, "adj_pfv", perf, trperf, I .. "ਆ ਹੋਇਆ", E .. " ਹੋਏ", II .. " ਹੋਈ", I .. "ਆਂ ਹੋਇਆਂ")
	add_conj_gendered(base, "adj_hab", nil, nil, "ਦਾ ਹੋਇਆ", "ਦੇ ਹੋਏ", "ਦੀ ਹੋਈ", "ਦਿਆਂ ਹੋਇਆਂ")

	-- Non-aspectual
	add_conj_gendered(base, "ind_perf", perf, trperf, I .. "ਆ", E, II, I .. "ਆਂ")
	add_conj_personal(base, "subj_fut", subj, trsubj, AA .. N, E, AA .. N, "ਣ", O)
	add_conj_gendered_personal(base, "ind_fut", subj, trsubj,
		AA .. N .. "ਗਾ", E .. "ਗਾ", AA .. N .. "ਗੇ", "ਣਗੇ", O .. "ਗੇ",
		AA .. N .. "ਗੀ", E .. "ਗੀ", AA .. N .. "ਗਿਆਂ", "ਣਗਿਆਂ", O .. "ਗਿਆਂ")
	add(base, subj, trsubj, "ind_fut_1sm", UU .. N .. "ਗਾ", "[dialectal]")
	add(base, subj, trsubj, "ind_fut_1sf", UU .. N .. "ਗੀ", "[dialectal]")
	add(base, subj, trsubj, "subj_fut_1s", UU .. N, "[dialectal]")
	if base.stem == "ਹੋ" then
		add_conj_gendered_personal(base, "presum", "", "", "ਹੋਵਾਂਗਾ", "ਹੋਵੇਗਾ", "ਹੋਵਾਂਗੇ", "ਹੋਣਗੇ", "ਹੋਵੋਗੇ", "ਹੋਵਾਂਗੀ", "ਹੋਵੇਗੀ", "ਹੋਵਾਂਗਿਆਂ", "ਹੋਣਗਿਆਂ", "ਹੋਵੋਗਿਆ")
		add_conj_personal(base, "ind_pres", "", "", "ਹਾਂ", "ਹੈ", "ਹਾਂ", "ਹਨ", "ਹੋ")
		add_conj_personal(base, "ind_impf", "", "", "ਸੀ", "ਸੀ", "ਸੀ", "ਸੀ", "ਸੀ")
		add_conj_personal(base, "ind_impf", "", "", "ਸਾਂ", "ਸੈ", "ਸਾਂ", "ਸਨ", "ਸੋ", "[dialectal]")
		add_conj_personal(base, "subj_pres", "", "", "ਹੋਵਾਂ", "ਹੋਵੇ", "ਹੋਇਏ", "ਹੋਣ", "ਹੋਵੋ")
	end
	add_conj_gendered(base, "cfact", nil, nil, "ਦਾ", "ਦੇ", "ਦੀ", "ਦਿਆਂ")

	add(base, base.stem, base.stem_translit, "imp_reg_2s", "")
	add(base, subj, trsubj, "imp_reg_2p", O)
	add(base, subj, trsubj, "imp_pol_2s", II .. N)
	add(base, subj, trsubj, "imp_pol_2p", I .. "ਓ")

	-- 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,
		I .. "ਆ ਹਾਂ", I .. "ਆ ਹੈ", E .. " ਹਾਂ", E .. " ਹਨ", E .. " ਹੋ",
		II .. " ਹਾਂ", II .. " ਹੈ", I .. "ਆਂ ਹਾਂ", I .. "ਆਂ ਹਨ", I .. "ਆਂ ਹੋ")
	add_conj_gendered(base, "pfv_ind_past", perf, trperf,
		I .. "ਆ ਸੀ", E .. " ਸੀ",
		II .. " ਸੀ", I .. "ਆਂ ਸੀ")
	add_conj_gendered_personal(base, "pfv_ind_fut", perf, trperf,
		I .. "ਆ ਹੋਵਾਂਗਾ", I .. "ਆ ਹੋਵੇਗਾ", E .. " ਹੋਵਾਂਗੇ", E .. " ਹੋਣਗੇ", E .. " ਹੋਵੋਗੇ",
		II .. " ਹੋਵਾਂਗਾ", II .. " ਹੋਵੇਗਾ", I .. "ਆਂ ਹੋਵਾਂਗੇ", I .. "ਆਂ ਹੋਣਗੇ", I .. "ਆਂ ਹੋਵੋਗੇ")
	add_conj_gendered_personal(base, "pfv_presum", perf, trperf,
		I .. "ਆ ਹੋਵਾਂਗਾ", I .. "ਆ ਹੋਵੇਗਾ", E .. " ਹੋਵਾਂਗੇ", E .. " ਹੋਣਗੇ", E .. " ਹੋਵੋਗੇ",
		II .. " ਹੋਵਾਂਗਾ", II .. " ਹੋਵੇਗਾ", I .. "ਆਂ ਹੋਵਾਂਗੇ", I .. "ਆਂ ਹੋਣਗੇ", I .. "ਆਂ ਹੋਵੋਗੇ")
	add_conj_gendered_personal(base, "pfv_subj_pres", perf, trperf,
		I .. "ਆ ਹੋਵਾਂ", I .. "ਆ ਹੋਵੇ", E .. " ਹੋਇਏ", E .. " ਹੋਣ", E .. " ਹੋਵੋ",
		II .. " ਹੋਵਾਂ", II .. " ਹੋਵੇ", I .. "ਆਂ ਹੋਇਏ", I .. "ਆਂ ਹੋਣ", I .. "ਆਂ ਹੋਵੋ")
	add_conj_gendered(base, "pfv_cfact", perf, trperf, I .. "ਆ ਹੁੰਦਾ", E .. " ਹੁੰਦੇ", II .. " ਹੁੰਦੀ", I .. "ਆਂ ਹੁੰਦਿਆਂ")

	-- 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(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" (the only one currently implemented)
}
]=]
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"
	if rfind(base.lemma, "ਨਾ$") then
		base.lemma = rsubn(base.lemma, "ਨਾ$", "ਣਾ")
	end
	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, "Punjabi " .. 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


-- Convert forms from their list/object form (see the comments to add() for how this works)
-- to strings that can be directly filled into the table. Approximately, each form is converted
-- to a formatted link with accelerators and the results are concatenated, followed by an newline
-- and then the formatted transliterations.
local function show_forms(alternant_multiword_spec)
	local lemmas = alternant_multiword_spec.forms.inf_ms or {}
	local props = {
		lemmas = lemmas,
		slot_table = verb_slots_impers,
		lang = lang,
		include_translit = true,
	}
	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(alternant_multiword_spec.forms, props)
	alternant_multiword_spec.forms.footnote_impers = alternant_multiword_spec.forms.footnote
	props.slot_table = verb_slots_pers
	iut.show_forms(alternant_multiword_spec.forms, props)
	alternant_multiword_spec.forms.footnote_pers = alternant_multiword_spec.forms.footnote
end


-- Generate the conjugation table. This should be called after show_forms() has converted
-- each form to a formatted string.
local function make_table(alternant_multiword_spec)
	local table_spec_impersonal = [=[
<div class="NavFrame" style="display: table;">
<div class="NavHead hi-table-title" style="background: #d9ebff;">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="pa" class="Guru">[[ਮੈਂ]]</span>
! '''2<sup>nd</sup> intimate'''<br><span lang="pa" class="Guru">[[ਤੂੰ]]</span>
! '''3<sup>rd</sup>'''<br><span lang="pa" class="Guru">[[ਇਹ]]/[[ਉਹ]]</span>
! '''2<sup>nd</sup> familiar'''<br><span lang="pa" class="Guru">[[ਤੁਸੀਂ]]</span>
! '''1<sup>st</sup>'''<br><span lang="pa" class="Guru">[[ਅਸੀਂ]]</span>
! '''2<sup>nd</sup> formal, 3<sup>rd</sup>'''<br><span lang="pa" class="Guru">[[ਇਹ]]/[[ਉਹ]]/[[ਆਪ]]</span>
]=]

	-- Regular person-number header used at the top of the table and in the middle.
	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
	-- Reversed person-number header used at the bottom of the table. "Reversed" means that
	-- the two rows are in reversed order; but internally we can't switch the order of everything
	-- (e.g. the double-row cells at the left side), so we need to break up the header into multiple
	-- parts and only reverse certain parts.
	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" style="display: table;>
<div class="NavHead hi-table-title" style="background: #d9ebff;">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=3 | {ind_perf_fs}
| colspan=3 | {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_23sm}
| {ind_fut_2pm}
| {ind_fut_1pm}
| {ind_fut_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {ind_fut_1sf}
| colspan=2 | {ind_fut_23sf}
| {ind_fut_2pf}
| {ind_fut_1pf}
| {ind_fut_3pf}
{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=3 | {cfact_fs}
| colspan=3 | {cfact_fp}
|-
! class="hi-tense-aspect-cell" rowspan=2 colspan=1 | ''Imperative''
! class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {REG}
! class="hi-mf-cell" | {mf}
|
| {imp_reg_2s}
|
| {imp_reg_2p}
|
| {imp_reg_2p}
|-
! class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {POL}
! class="hi-mf-cell" | {mf}
|
| {imp_pol_2s}
|
| {imp_pol_2p}
|
| {imp_pol_2p}
|-
! 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_23sm}
| {hab_ind_pres_2pm}
| {hab_ind_pres_1pm}
| {hab_ind_pres_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {hab_ind_pres_1sf}
| colspan=2 | {hab_ind_pres_23sf}
| {hab_ind_pres_2pf}
| {hab_ind_pres_1pf}
| {hab_ind_pres_3pf}
|- 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=3 | {hab_ind_past_fs}
| colspan=3 | {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_23sm}
| {hab_presum_2pm}
| {hab_presum_1pm}
| {hab_presum_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {hab_presum_1sf}
| colspan=2 | {hab_presum_23sf}
| {hab_presum_2pf}
| {hab_presum_1pf}
| {hab_presum_3pf}
|- 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_23sm}
| {hab_subj_2pm}
| {hab_subj_1pm}
| {hab_subj_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {hab_subj_1sf}
| colspan=2 | {hab_subj_23sf}
| {hab_subj_2pf}
| {hab_subj_1pf}
| {hab_subj_3pf}
|- 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=3 | {hab_cfact_fs}
| colspan=3 | {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_23sm}
| {pfv_ind_pres_2pm}
| {pfv_ind_pres_1pm}
| {pfv_ind_pres_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {pfv_ind_pres_1sf}
| colspan=2 | {pfv_ind_pres_23sf}
| {pfv_ind_pres_2pf}
| {pfv_ind_pres_1pf}
| {pfv_ind_pres_3pf}
|- 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=3 | {pfv_ind_past_fs}
| colspan=3 | {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_23sm}
| {pfv_ind_fut_2pm}
| {pfv_ind_fut_1pm}
| {pfv_ind_fut_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {pfv_ind_fut_1sf}
| colspan=2 | {pfv_ind_fut_23sf}
| {pfv_ind_fut_2pf}
| {pfv_ind_fut_1pf}
| {pfv_ind_fut_3pf}
|- 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_23sm}
| {pfv_presum_2pm}
| {pfv_presum_1pm}
| {pfv_presum_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {pfv_presum_1sf}
| colspan=2 | {pfv_presum_23sf}
| {pfv_presum_2pf}
| {pfv_presum_1pf}
| {pfv_presum_3pf}
|- 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}
| {pfv_subj_pres_1sm}
| colspan=2 | {pfv_subj_pres_23sm}
| {pfv_subj_pres_2pm}
| {pfv_subj_pres_1pm}
| {pfv_subj_pres_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {pfv_subj_pres_1sf}
| colspan=2 | {pfv_subj_pres_23sf}
| {pfv_subj_pres_2pf}
| {pfv_subj_pres_1pf}
| {pfv_subj_pres_3pf}
|- 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=3 | {pfv_cfact_fs}
| colspan=3 | {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_23sm}
| {prog_ind_pres_2pm}
| {prog_ind_pres_1pm}
| {prog_ind_pres_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {prog_ind_pres_1sf}
| colspan=2 | {prog_ind_pres_23sf}
| {prog_ind_pres_2pf}
| {prog_ind_pres_1pf}
| {prog_ind_pres_3pf}
|- 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=3 | {prog_ind_past_fs}
| colspan=3 | {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_23sm}
| {prog_ind_fut_2pm}
| {prog_ind_fut_1pm}
| {prog_ind_fut_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {prog_ind_fut_1sf}
| colspan=2 | {prog_ind_fut_23sf}
| {prog_ind_fut_2pf}
| {prog_ind_fut_1pf}
| {prog_ind_fut_3pf}
|- 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_23sm}
| {prog_presum_2pm}
| {prog_presum_1pm}
| {prog_presum_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {prog_presum_1sf}
| colspan=2 | {prog_presum_23sf}
| {prog_presum_2pf}
| {prog_presum_1pf}
| {prog_presum_3pf}
|- 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}
| {prog_subj_pres_1sm}
| colspan=2 | {prog_subj_pres_23sm}
| {prog_subj_pres_2pm}
| {prog_subj_pres_1pm}
| {prog_subj_pres_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {prog_subj_pres_1sf}
| colspan=2 | {prog_subj_pres_23sf}
| {prog_subj_pres_2pf}
| {prog_subj_pres_1pf}
| {prog_subj_pres_3pf}
|- 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=3 | {prog_cfact_fs}
| colspan=3 | {prog_cfact_fp}
{reversed_person_number_header}|{\cl}{notes_clause}</div></div>]=]

	local pres_impf_table = [=[
|-
! class="hi-tense-aspect-cell" rowspan=6 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_23s}
| {ind_pres_2p}
| {ind_pres_1p}
| {ind_pres_3p}
|- class="hi-row-m"
! class="hi-tense-aspect-cell" colspan=1 | {IMPF}
! class="hi-mf-cell" | {mf}
| {ind_impf_1s}
| colspan=2 | {ind_impf_23s}
| {ind_impf_2p}
| {ind_impf_1p}
| {ind_impf_3p}
|- 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_23sm}
| {presum_2pm}
| {presum_1pm}
| {presum_3pm}
|- class="hi-row-f"
! class="hi-mf-cell" | {f}
| {presum_1sf}
| colspan=2 | {presum_23sf}
| {presum_2pf}
| {presum_1pf}
| {presum_3pf}
]=]

	local combined_subj = [=[
|-
! class="hi-tense-aspect-cell" rowspan=1 colspan=1 | ''Subjunctive''
! class="hi-tense-aspect-cell" rowspan=1 colspan=1 | {FUT}
! class="hi-mf-cell" | {mf}
| {subj_fut_1s}
| colspan=2 | {subj_fut_23s}
| {subj_fut_2p}
| {subj_fut_1p}
| {subj_fut_3p}]=]

	local split_subj = [=[
|-
! class="hi-tense-aspect-cell" rowspan=1 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_23s}
| {subj_pres_2p}
| {subj_pres_1p}
| {subj_pres_3p}]=]

	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.REG = make_tense_aspect_abbr("Regular", "REG")
	forms.POL = make_tense_aspect_abbr("Polite", "POL")
	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:hi-verb/style.css") .. formatted_table_impers .. formatted_table_pers
end


-- Implementation of template 'pa-verb cat'.
-- NOTE: Not currently used.
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, "^Punjabi.- ([^ ]*)s ")
		if not pos then
			pos = rmatch(SUBPAGENAME, "^Punjabi.- ([^ ]*)s$")
		end
		if not pos then
			error("Invalid category name, should be e.g. \"Punjabi verbs with ...\" or \"Punjabi ... verbs\"")
		end
		return pos
	end

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

	local cats = {}, pos

	-- Insert the category CAT (a string) into the categories. String will
	-- have "Punjabi " prepended and ~ substituted for the plural part of speech.
	local function insert(cat, atbeg)
		local fullcat = "Punjabi " .. 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 Punjabi 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 Punjabi " .. 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 == "pa-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, none). 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
	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