Modul:ar-verb

Daripada Wiktionary

Pendokumenan untuk modul ini boleh diciptakan di Modul:ar-verb/doc

local m_headword = require("Module:headword")
local m_utilities = require("Module:utilities")
local m_links = require("Module:links")
local ar_translit = require("Module:ar-translit")
local ar_utilities = require("Module:ar-utilities")
local ar_nominals = require("Module:ar-nominals")

local lang = require("Module:languages").getByCode("ar")
local curtitle = mw.title.getCurrentTitle().fullText
local yesno = require("Module:yesno")

local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local u = mw.ustring.char

-- don't display i3rab in nominal forms (verbal nouns, participles)
local no_nominal_i3rab = true

local export = {}

-- Within this module, conjugations are the functions that do the actual
-- conjugating by creating the parts of a basic verb.
-- They are defined further down.
local conjugations = {}
-- hamza variants
local HAMZA            = u(0x0621) -- hamza on the line (stand-alone hamza) = ء
local HAMZA_ON_ALIF    = u(0x0623)
local HAMZA_ON_W       = u(0x0624)
local HAMZA_UNDER_ALIF = u(0x0625)
local HAMZA_ON_Y       = u(0x0626)
local HAMZA_ANY        = "[" .. HAMZA .. HAMZA_ON_ALIF .. HAMZA_UNDER_ALIF .. HAMZA_ON_W .. HAMZA_ON_Y .. "]"
local HAMZA_PH         = u(0xFFF0) -- hamza placeholder

-- diacritics
local A  = u(0x064E) -- fatḥa
local AN = u(0x064B) -- fatḥatān (fatḥa tanwīn)
local U  = u(0x064F) -- ḍamma
local UN = u(0x064C) -- ḍammatān (ḍamma tanwīn)
local I  = u(0x0650) -- kasra
local IN = u(0x064D) -- kasratān (kasra tanwīn)
local SK = u(0x0652) -- sukūn = no vowel
local SH = u(0x0651) -- šadda = gemination of consonants
local DAGGER_ALIF = u(0x0670)
local DIACRITIC_ANY_BUT_SH = "[" .. A .. I .. U .. AN .. IN .. UN .. SK .. DAGGER_ALIF .. "]"
-- Pattern matching short vowels
local AIU = "[" .. A .. I .. U .. "]"
-- Pattern matching short vowels or sukūn
local AIUSK = "[" .. A .. I .. U .. SK .. "]"
-- Pattern matching any diacritics that may be on a consonant
local DIACRITIC = SH .. "?" .. DIACRITIC_ANY_BUT_SH
-- Suppressed UN; we don't show -un i3rab any more, but this can be changed to show it
local UNS = no_nominal_i3rab and "" or UN

local dia = {a = A, i = I, u = U}

-- various letters and signs
local ALIF   = u(0x0627) -- ʾalif = ا
local AMAQ   = u(0x0649) -- ʾalif maqṣūra = ى
local AMAD   = u(0x0622) -- ʾalif madda = آ
local TAM    = u(0x0629) -- tāʾ marbūṭa = ة
local T      = u(0x062A) -- tāʾ = ت
local HYPHEN = u(0x0640)
local N      = u(0x0646) -- nūn = ن
local W      = u(0x0648) -- wāw = و
local Y      = u(0x064A) -- yā = ي
local S      = "س"
local M      = "م"
local LRM    = u(0x200e) -- left-to-right mark

-- common combinations
local AH    = A .. TAM
local AT    = A .. T
local AA    = A .. ALIF
local AAMAQ = A .. AMAQ
local AAH   = AA .. TAM
local AAT   = AA .. T
local II    = I .. Y
local IY    = II
local UU    = U .. W
local AY    = A .. Y
local AW    = A .. W
local AYSK  = AY .. SK
local AWSK  = AW .. SK
local NA    = N .. A
local NI    = N .. I
local AAN   = AA .. N
local AANI  = AA .. NI
local AYNI  = AYSK .. NI
local AWNA  = AWSK .. NA
local AYNA  = AYSK .. NA
local AYAAT = AY .. AAT
local UNU   = "[" .. UN .. U .. "]"
local MA    = M .. A
local MU    = M .. U

--------------------
-- Utility functions
--------------------

-- "if not empty" -- convert empty strings to nil; also strip quotes around
-- strings, to allow embedded spaces to be included
local function ine(x)
	if x == nil then
		return nil
	elseif rfind(x, '^".*"$') then
		local ret = rmatch(x, '^"(.*)"$')
		return ret
	elseif rfind(x, "^'.*'$") then
		local ret = rmatch(x, "^'(.*)'$")
		return ret
	elseif x == "" then
		return nil
	else
		return x
	end
end

-- true if array contains item
local function contains(tab, item)
	for _, value in pairs(tab) do
		if value == item then
			return true
		end
	end
	return false
end

-- append array to array
local function append_array(tab, items)
	for _, item in ipairs(items) do
		table.insert(tab, item)
	end
end

-- append to array if element not already present
local function insert_if_not(tab, item)
	if not contains(tab, item) then
		table.insert(tab, item)
	end
end

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

local function links(text, face, id)
	if text == "" or text == "?" or text == "—" or text == "—" then --mdash
		return text
	else
		return m_links.full_link({lang = lang, term = text, tr = "-", id = id}, face)
	end
end

local function tag_text(text, tag, class)
	return m_links.full_link({lang = lang, alt = text, tr = "-"})
end

function track(page)
	require("Module:debug").track("ar-verb/" .. page)
	return true
end

function reorder_shadda(word)
	-- shadda+short-vowel (including tanwīn vowels, i.e. -an -in -un) gets
	-- replaced with short-vowel+shadda during NFC normalisation, which
	-- MediaWiki does for all Unicode strings; however, it makes various
	-- processes inconvenient, so undo it.
	word = rsub(word, "(" .. DIACRITIC_ANY_BUT_SH .. ")" .. SH, SH .. "%1")
	return word
end

-- synthesize a frame so that exported functions meant to be called from
-- templates can be called from the debug console.
function debug_frame(parargs, args)
	return {args = args, getParent = function() return {args = parargs} end}
end

---------------------------------------
-- Properties of different verbal forms
---------------------------------------

-- no longer supported
--local numeric_to_roman_form = {
--	["1"] = "I", ["2"] = "II", ["3"] = "III", ["4"] = "IV", ["5"] = "V",
--	["6"] = "VI", ["7"] = "VII", ["8"] = "VIII", ["9"] = "IX", ["10"] = "X",
--	["11"] = "XI", ["12"] = "XII", ["13"] = "XIII", ["14"] = "XIV", ["15"] = "XV",
--	["1q"] = "Iq", ["2q"] = "IIq", ["3q"] = "IIIq", ["4q"] = "IVq"
--}
--
---- convert numeric form to roman-numeral form
--local function canonicalize_form(form)
--	return numeric_to_roman_form[form] or form
--end

local allowed_forms = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX",
	"X", "XI", "XII", "XIII", "XIV", "XV", "Iq", "IIq", "IIIq", "IVq"}

local function form_supports_final_weak(form)
	return form ~= "XI" and form ~= "XV" and form ~= "IVq"
end

local function form_supports_geminate(form)
	return form == "I" or form == "III" or form == "IV" or
		form == "VI" or form == "VII" or form == "VIII" or form == "X"
end

local function form_supports_hollow(form)
	return form == "I" or form == "IV" or form == "VII" or form == "VIII" or
		form == "X"
end

local function form_probably_impersonal_passive(form)
	return form == "VI"
end

local function form_probably_no_passive(form, weakness, past_vowel, nonpast_vowel)
	return form == "I" and weakness ~= "hollow" and contains(past_vowel, "u") or
		form == "VII" or form == "IX" or form == "XI" or form == "XII" or
		form == "XIII" or form == "XIV" or form == "XV" or form == "IIq" or
		form == "IIIq" or form == "IVq"
end

local function form_is_quadriliteral(form)
	return form == "Iq" or form == "IIq" or form == "IIIq" or form == "IVq"
end

-- Active forms II, III, IV, Iq use non-past prefixes in -u- instead of -a-.
function prefix_vowel_from_form(form)
	if form == "II" or form == "III" or form == "IV" or form == "Iq" then
		return "u"
	else
		return "a"
	end
end

-- true if the active non-past takes a-vowelling rather than i-vowelling
-- in its last syllable
function form_nonpast_a_vowel(form)
	return form == "V" or form == "VI" or form == "XV" or form == "IIq"
end

---------------------------------------------------
-- Radicals associated with various irregular verbs
---------------------------------------------------

-- Form-I verb أخذ or form-VIII verb اتخذ
local function axadh_radicals(rad1, rad2, rad3)
	return rad1 == HAMZA and rad2 == "خ" and rad3 == "ذ"
end

-- Form-I verb whose imperative has a reduced form: أكل and أخذ and أمر
local function reduced_imperative_verb(rad1, rad2, rad3)
	return axadh_radicals(rad1, rad2, rad3) or rad1 == HAMZA and (
		rad2 == "ك" and rad3 == "ل" or
		rad2 == "م" and rad3 == "ر")
end

-- Form-I verb رأى and form-IV verb أرى
local function raa_radicals(rad1, rad2, rad3)
	return rad1 == "ر" and rad2 == HAMZA and rad3 == Y
end

-- Form-I verb سأل
local function saal_radicals(rad1, rad2, rad3)
	return rad1 == "س" and rad2 == HAMZA and rad3 == "ل"
end

-- Form-I verb حيّ or حيي and form-X verb استحيا or استحى
local function hayy_radicals(rad1, rad2, rad3)
	return rad1 == "ح" and rad2 == Y and rad3 == Y
end

-----------------------
-- sets of past endings
-----------------------

-- the 13 endings of the sound/hollow/geminate past tense
local past_endings = {
	-- singular
	SK .. "تُ", SK .. "تَ", SK .. "تِ", A, A .. "تْ",
	--dual
	SK .. "تُمَا", AA, A .. "تَا",
	-- plural
	SK .. "نَا", SK .. "تُمْ",
	-- two Arabic diacritics don't work together in Wikimedia
	--SK .. "تُنَّ",
	SK .. "تُن" .. SH .. A, UU .. ALIF, SK .. "نَ"
}

-- make endings for final-weak past in -aytu or -awtu. AYAW is AY or AW
-- as appropriate. Note that AA and AW are global variables.
local function make_past_endings_ay_aw(ayaw, third_sg_masc)
	return {
	-- singular
	ayaw .. SK .. "تُ", ayaw ..  SK .. "تَ", ayaw .. SK .. "تِ",
	third_sg_masc, A .. "تْ",
	--dual
	ayaw .. SK .. "تُمَا", ayaw .. AA, A .. "تَا",
	-- plural
	ayaw .. SK .. "نَا", ayaw .. SK .. "تُمْ",
	-- two Arabic diacritics don't work together in Wikimedia
	--ayaw .. SK .. "تُنَّ",
	ayaw .. SK .. "تُن" .. SH .. A, AW .. SK .. ALIF, ayaw .. SK .. "نَ"
	}
end

-- past final-weak -aytu endings
local past_endings_ay = make_past_endings_ay_aw(AY, AAMAQ)
-- past final-weak -awtu endings
local past_endings_aw = make_past_endings_ay_aw(AW, AA)

-- Make endings for final-weak past in -ītu or -ūtu. IIUU is ī or ū as
-- appropriate. Note that AA and UU are global variables.
local function make_past_endings_ii_uu(iiuu)
	return {
	-- singular
	iiuu .. "تُ", iiuu .. "تَ", iiuu .. "تِ", iiuu .. A, iiuu .. A .. "تْ",
	--dual
	iiuu .. "تُمَا", iiuu .. AA, iiuu .. A .. "تَا",
	-- plural
	iiuu .. "نَا", iiuu .. "تُمْ",
	-- two Arabic diacritics don't work together in Wikimedia
	--iiuu .. "تُنَّ",
	iiuu .. "تُن" .. SH .. A, UU .. ALIF, iiuu .. "نَ"
	}
end

-- past final-weak -ītu endings
local past_endings_ii = make_past_endings_ii_uu(II)
-- past final-weak -ūtu endings
local past_endings_uu = make_past_endings_ii_uu(UU)

----------------------------------------
-- sets of non-past prefixes and endings
----------------------------------------

-- prefixes for non-past forms in -a-
local nonpast_prefixes_a = {
	-- singular
	HAMZA .. A, "تَ", "تَ", "يَ", "تَ",
	--dual
	"تَ", "يَ", "تَ",
	-- plural
	"نَ", "تَ", "تَ", "يَ", "يَ"
}

-- prefixes for non-past forms in -u- (passive; active forms II, III, IV, Iq)
local nonpast_prefixes_u = {
	-- singular
	HAMZA .. U, "تُ", "تُ", "يُ", "تُ",
	--dual
	"تُ", "يُ", "تُ",
	-- plural
	"نُ", "تُ", "تُ", "يُ", "يُ"
}

-- There are only five distinct endings in all non-past verbs. Make any set of
-- non-past endings given these five distinct endings.
local function make_nonpast_endings(null, fem, dual, pl, fempl)
	return {
	-- singular
	null, null, fem, null, null,
	-- dual
	dual, dual, dual,
	-- plural
	null, pl, fempl, pl, fempl
	}
end

-- endings for non-past indicative
local indic_endings = make_nonpast_endings(
	U,
	II .. NA,
	AANI,
	UU .. NA,
	SK .. NA
)

-- make the endings for non-past subjunctive/jussive, given the vowel diacritic
-- used in "null" endings (1s/2sm/3sm/3sf/1p)
local function make_subj_juss_endings(dia_null)
	return make_nonpast_endings(
	dia_null,
	II,
	AA,
	UU .. ALIF,
	SK .. NA
	)
end

-- endings for non-past subjunctive
local subj_endings = make_subj_juss_endings(A)

-- endings for non-past jussive
local juss_endings = make_subj_juss_endings(SK)

-- endings for alternative geminate non-past jussive in -a; same as subjunctive
local juss_endings_alt_a = subj_endings

-- endings for alternative geminate non-past jussive in -i
local juss_endings_alt_i = make_subj_juss_endings(I)

-- endings for final-weak non-past indicative in -ā. Note that AY, AW and
-- AAMAQ are global variables.
local indic_endings_aa = make_nonpast_endings(
	AAMAQ,
	AYSK .. NA,
	AY .. AANI,
	AWSK .. NA,
	AYSK .. NA
)

-- make endings for final-weak non-past indicative in -ī or -ū; IIUU is
-- ī or ū as appropriate. Note that II and UU are global variables.
local function make_indic_endings_ii_uu(iiuu)
	return make_nonpast_endings(
	iiuu,
	II .. NA,
	iiuu .. AANI,
	UU .. NA,
	iiuu .. NA
	)
end

-- endings for final-weak non-past indicative in -ī
local indic_endings_ii = make_indic_endings_ii_uu(II)

-- endings for final-weak non-past indicative in -ū
local indic_endings_uu = make_indic_endings_ii_uu(UU)

-- endings for final-weak non-past subjunctive in -ā. Note that AY, AW, ALIF,
-- AAMAQ are global variables.
local subj_endings_aa = make_nonpast_endings(
	AAMAQ,
	AYSK,
	AY .. AA,
	AWSK .. ALIF,
	AYSK .. NA
)

-- make endings for final-weak non-past subjunctive in -ī or -ū. IIUU is
-- ī or ū as appropriate. Note that AA, II, UU, ALIF are global variables.
local function make_subj_endings_ii_uu(iiuu)
	return make_nonpast_endings(
	iiuu .. A,
	II,
	iiuu .. AA,
	UU .. ALIF,
	iiuu .. NA
	)
end

-- endings for final-weak non-past subjunctive in -ī
local subj_endings_ii = make_subj_endings_ii_uu(II)

-- endings for final-weak non-past subjunctive in -ū
local subj_endings_uu = make_subj_endings_ii_uu(UU)

-- endings for final-weak non-past jussive in -ā
local juss_endings_aa = make_nonpast_endings(
	A,
	AYSK,
	AY .. AA,
	AWSK .. ALIF,
	AYSK .. NA
)

-- Make endings for final-weak non-past jussive in -ī or -ū. IU is short i or u,
-- IIUU is long ī or ū as appropriate. Note that AA, II, UU, ALIF are global
-- variables.
local function make_juss_endings_ii_uu(iu, iiuu)
	return make_nonpast_endings(
	iu,
	II,
	iiuu .. AA,
	UU .. ALIF,
	iiuu .. NA
	)
end

-- endings for final-weak non-past jussive in -ī
local juss_endings_ii = make_juss_endings_ii_uu(I, II)

-- endings for final-weak non-past jussive in -ū
local juss_endings_uu = make_juss_endings_ii_uu(U, UU)

-----------------------------
-- sets of imperative endings
-----------------------------

-- extract the second person jussive endings to get corresponding imperative
-- endings
local function imperative_endings_from_jussive(endings)
	return {endings[2], endings[3], endings[6], endings[10], endings[11]}
end

-- normal imperative endings
local impr_endings = imperative_endings_from_jussive(juss_endings)
-- alternative geminate imperative endings in -a
local impr_endings_alt_a = imperative_endings_from_jussive(juss_endings_alt_a)
-- alternative geminate imperative endings in -i
local impr_endings_alt_i = imperative_endings_from_jussive(juss_endings_alt_i)
-- final-weak imperative endings in -ā
local impr_endings_aa = imperative_endings_from_jussive(juss_endings_aa)
-- final-weak imperative endings in -ī
local impr_endings_ii = imperative_endings_from_jussive(juss_endings_ii)
-- final-weak imperative endings in -ū
local impr_endings_uu = imperative_endings_from_jussive(juss_endings_uu)

-----------------------------
-- Main conjugation functions
-----------------------------

function get_args(args)
	local origargs = args
	local args = {}
	-- Convert empty arguments to nil, and "" or '' arguments to empty
	for k, v in pairs(origargs) do
		args[k] = ine(v)
	end
	return origargs, args
end

function get_frame_args(frame)
	return get_args(frame:getParent().args)
end

-- Implement {{ar-conj}}.
function export.show(frame)
	local origargs, args = get_frame_args(frame)

	local data, form, weakness, past_vowel, nonpast_vowel = conjugate(args, 1)

	-- if the value is "yes" or variants, the verb is intransitive;
	-- if the value is "no" or variants, the verb is transitive.
	-- If not specified, default is intransitive if passive == false or
	-- passive == "impers", else transitive.
	local intrans = args["intrans"]
	if not intrans then
		intrans = data.passive == false or data.passive == "impers" or data.passive == "only-impers"
	else
		intrans = yesno(intrans, "unknown")
		if intrans == "unknown" then
			error("Unrecognized value '" .. args["intrans"] ..
				"' to argument intrans=; use 'yes'/'y'/'true'/'1' or 'no'/'n'/'false'/'0'")
		end
	end
	if intrans then
		table.insert(data.categories, "Arabic intransitive verbs")
	else
		table.insert(data.categories, "Arabic transitive verbs")
	end

	-- initialize title, with weakness indicated by conjugation
	-- (FIXME should it be by form?)
	local title = "form-" .. form .. " " .. weakness

	if data.passive == "only" or data.passive == "only-impers" then
		title = title .. " passive"
	end

	if data.irregular then
		table.insert(data.categories, "Arabic irregular verbs")
		title = title .. " irregular"
	end

	return make_table(data, title, form, intrans) ..
		m_utilities.format_categories(data.categories, lang)
end

-- Version of main entry point meant for calling from the debug console.
-- An example call might be as follows:
--
-- =p.show2({'I', 'a', 'u', 'ك', 'ت', 'ب'})
--
-- This is equivalent to the following template call:
--
-- {{ar-conj|I|a|u|ك|ت|ب}}
--
-- Note that the radicals were actually typed in with ك first, followed by ت
-- and then ب, but they appear in opposite order due to right-to-left
-- display issues. It's necessary to specify the radicals in the call,
-- unlike in a template call where they are inferred from the page name,
-- because there is (currently) no way to control the page name used in
-- the radical-inference code, where it will appear as 'Module:ar-verb'.
function export.show2(parargs, args)
	return export.show(debug_frame(parargs, args))
end

-- Implement {{ar-verb}}.
-- TODO: Move this into [[Module:ar-headword]]
function export.headword(frame)
	local origargs, args = get_frame_args(frame)

	local data, form, weakness, past_vowel, nonpast_vowel = conjugate(args, 1)
	local use_params = form == "I" or args["useparam"]

	local arabic_3sm_perf, latin_3sm_perf
	local arabic_3sm_imperf, latin_3sm_imperf
	if data.passive == "only" or data.passive == "only-impers" then
		arabic_3sm_perf, latin_3sm_perf = get_spans(data.forms["3sm-ps-perf"])
		arabic_3sm_imperf, latin_3sm_imperf = get_spans(data.forms["3sm-ps-impf"])
	else
		arabic_3sm_perf, latin_3sm_perf = get_spans(data.forms["3sm-perf"])
		arabic_3sm_imperf, latin_3sm_imperf = get_spans(data.forms["3sm-impf"])
	end
	
	title = mw.title.getCurrentTitle()
	NAMESPACE = title.nsText
	PAGENAME = ( NAMESPACE == "Appendix" and title.subpageText ) or title.text
	
	-- set to PAGENAME if left empty
	local head, tr
	if use_params and form ~= "I" then
		table.insert(data.headword_categories, "Arabic augmented verbs with parameter override")
	end
	if use_params and args["head"] then
		head = {args["head"], args["head2"], args["head3"]}
		tr = {args["tr"], args["tr2"], args["tr3"]}
		if form == "I" then
			table.insert(data.headword_categories, "Arabic form-I verbs with headword perfect determined through param, not past vowel")
		end
	elseif use_params and args["tr"] then
		head = PAGENAME
		tr = args["tr"]
		if form == "I" then
			table.insert(data.headword_categories, "Arabic form-I verbs with headword perfect determined through param, not past vowel")
		end
	elseif form == "I" and #past_vowel == 0 then
		head = PAGENAME
		table.insert(data.headword_categories, "Arabic form-I verbs with missing past vowel in headword")
	else
		-- Massive hack to get the order correct. It gets reversed for some
		-- reason, possibly due to being surrounded by lang=ar or
		-- class=Arabic headword, so reverse the order before concatenating.
		headrev = {}
		for _, ar in ipairs(arabic_3sm_perf) do
			table.insert(headrev, 1, ar)
		end
		head = table.concat(headrev, " <small style=\"color: #888\">or</small> ")
		tr = table.concat(latin_3sm_perf, " <small style=\"color: #888\">or</small> ")
	end

	local form_text = ' <span class="gender">[[Appendix:Arabic verbs#Form ' .. form .. '|<abbr title="Bentuk kata kerja ' ..
	  form .. '">' .. form .. '</abbr>]]</span>'

	local impf_arabic, impf_tr
	if use_params and args["impf"] then
		impf_arabic = args["impfhead"] or args["impf"]
		impf_tr = args["impftr"] or ar_translit.tr(impf_arabic, nil, nil, nil)
		impf_arabic = {impf_arabic}
		if form == "I" then
			table.insert(data.headword_categories, "Arabic form-I verbs with headword imperfect determined through param, not non-past vowel")
		end
	elseif form == "I" and #nonpast_vowel == 0 then
		impf_arabic = {}
		table.insert(data.headword_categories, "Arabic form-I verbs with missing non-past vowel in headword")
	else
		impf_arabic = arabic_3sm_imperf
		impf_tr = table.concat(latin_3sm_imperf, " <small style=\"color: #888\">or</small> ")
	end

	-- convert Arabic terms to bolded links
	for i, entry in ipairs(impf_arabic) do
		impf_arabic[i] = links(entry, "bold")
	end
	-- create non-past text by concatenating Arabic spans and adding
	-- transliteration, but only if we can do it non-ambiguously (either we're
	-- not form I or the non-past vowel was specified)
	local impf_text = ""
	if #impf_arabic > 0 then
		impf_text = ', <i>kala tak lampau</i> ' .. table.concat(impf_arabic, " <small style=\"color: #888\">or</small> ")
		if impf_tr then
			impf_text = impf_text .. LRM .. " (" .. impf_tr .. ")"
		end
	end

	return
		m_headword.full_headword({lang = lang, pos_category = "kata kerja", categories = {}, heads = head, translits = tr, sort_key = args["sort"]}) ..
		form_text .. impf_text ..
		m_utilities.format_categories(data.headword_categories, lang)
end

-- Version of headword entry point meant for calling from the debug console.
-- See export.show2().
function export.headword2(parargs, args)
	return export.headword(debug_frame(parargs, args))
end

-- Implementation of export.past3sm() and export.past3sm_all().
function past3sm(frameargs, doall)
	local origargs, args = get_args(frameargs)

	local data, form, weakness, past_vowel, nonpast_vowel =	conjugate(args, 1)

	local arabic_3sm_perf, latin_3sm_perf
	if data.passive == "only" or data.passive == "only-impers" then
		arabic_3sm_perf, latin_3sm_perf = get_spans(data.forms["3sm-ps-perf"])
	else
		arabic_3sm_perf, latin_3sm_perf = get_spans(data.forms["3sm-perf"])
	end

	if doall then
		return table.concat(arabic_3sm_perf, ",")
	else
		return arabic_3sm_perf[1]
	end
end

-- Implement {{ar-past3sm}}.
--
-- Generate the 3rd singular masculine past tense (the dictionary form), given
-- the form, radicals and (for form I) past/non-past vowels (the non-past
-- vowel is ignored, but specified for compatibility with export.headword()
-- and export.show()). Form, radicals, past/non-past vowel arguments are the
-- same as for export.show(). Note that the form returned may be active or
-- passive depending on the passive= param (some values specify that the
-- verb is a passive-only verb). If there are multiple alternatives,
-- return only the first one.
function export.past3sm(frame)
	return past3sm(frame:getParent().args, false)
end

-- Version of past3sm entry point meant for calling from the debug console.
-- See export.show2().
function export.past3sm2(parargs, args)
	return export.past3sm(debug_frame(parargs, args))
end

-- Implement {{ar-past3sm-all}}.
--
-- Same as export.past3sm() but return all possible values, separated by
-- a comma. Multiple values largely come from alternative hamza seats.
function export.past3sm_all(frame)
	return past3sm(frame:getParent().args, true)
end

-- Version of past3sm_all entry point meant for calling from the debug console.
-- See export.show2().
function export.past3sm_all2(parargs, args)
	return export.past3sm_all(debug_frame(parargs, args))
end

-- Implementation of export.verb_part() and export.verb_part_all().
function verb_part(frameargs, doall)
	local origargs, args = get_args(frameargs)
	local part = args[1]
	local data, form, weakness, past_vowel, nonpast_vowel =	conjugate(args, 2)
	local arabic, latin = get_spans(data.forms[part])

	if doall then
		return table.concat(arabic, ",")
	else
		return arabic[1]
	end
end

-- Implement {{ar-verb-part}}.
--
-- TODO: Move this into [[Module:ar-headword]]
-- Generate an arbitrary part of the verbal paradigm. If there are multiple
-- possible alternatives, return only the first one.
function export.verb_part(frame)
	return verb_part(frame:getParent().args, false)
end

-- Version of verb_part entry point meant for calling from the debug console.
-- See export.show2().
function export.verb_part2(parargs, args)
	return export.verb_part(debug_frame(parargs, args))
end

-- Implement {{ar-verb-part-all}}.
--
-- TODO: Move this into [[Module:ar-headword]]
-- Generate an arbitrary part of the verbal paradigm. If there are multiple
-- possible alternatives, return all, separated by commas.
function export.verb_part_all(frame)
	return verb_part(frame:getParent().args, true)
end

-- Version of verb_part_all entry point meant for calling from the debug
-- console. See export.show2().
function export.verb_part_all2(parargs, args)
	return export.verb_part_all(debug_frame(parargs, args))
end

-- Return a property of the conjugation other than a verb part.
function verb_prop(frameargs)
	local origargs, args = get_args(frameargs)

	local prop = args[1]
	local data, form, weakness, past_vowel, nonpast_vowel = conjugate(args, 2)
	if prop == "form" then
		return form
	elseif prop == "weakness" then
		return weakness
	elseif prop == "form-weakness" then
		return form .. "-" .. weakness
	elseif prop == "past-vowel" then
		return table.concat(past_vowel, ",")
	elseif prop == "nonpast-vowel" then
		return table.concat(nonpast_vowel, ",")
	elseif prop == "rad1" then
		return data.rad1 or ""
	elseif prop == "rad2" then
		return data.rad2 or ""
	elseif prop == "rad3" then
		return data.rad3 or ""
	elseif prop == "rad4" then
		return data.rad4 or ""
	elseif prop == "unreg-rad1" then
		return data.unreg_rad1 or ""
	elseif prop == "unreg-rad2" then
		return data.unreg_rad2 or ""
	elseif prop == "unreg-rad3" then
		return data.unreg_rad3 or ""
	elseif prop == "unreg-rad4" then
		return data.unreg_rad4 or ""
	elseif prop == "radicals" then
		return table.concat({data.rad1, data.rad2, data.rad3, data.rad4}, ",")
	elseif prop == "unreg-radicals" then
		return table.concat({data.unreg_rad1, data.unreg_rad2,
		data.unreg_rad3, data.unreg_rad4}, ",")
	elseif prop == "passive" then
		return data.passive == true and "yes" or not data.passive and "no"
			or data.passive
	elseif prop == "passive-uncertain" then
		return data.passive_uncertain and "yes" or "no"
	elseif prop == "vn-uncertain" then
		return data.vn_uncertain and "yes" or "no"
	elseif prop == "irregular" then
		return data.irregular and "yes" or "no"
	--elseif prop == "intrans" then
	--	return data.intrans
	else
		error("Unrecognized property '" .. prop .. "'")
	end
end

-- Return a property of the conjugation other than a verb part.
function export.verb_prop(frame)
	return verb_prop(frame:getParent().args)
end

-- Version of verb_prop entry point meant for calling from the debug console.
-- See export.show2().
function export.verb_prop2(parargs, args)
	return export.verb_prop(debug_frame(parargs, args))
end

function export.verb_forms(frame)
	local origargs, args = get_frame_args(frame)
	local i = 1
	local past_vowel_re = "^[aui,]*$"
	local combined_root = nil
	if not args[i] or rfind(args[i], past_vowel_re) then
		combined_root = mw.title.getCurrentTitle().text
		if not rfind(combined_root, " ") then
			error("When inferring roots from page title, need spaces in page title: " .. combined_root)
		end
	elseif rfind(args[i], " ") then
		combined_root = args[i]
		i = i + 1
	else
		local separate_roots = {}
		while args[i] and not rfind(args[i], past_vowel_re) do
			table.insert(separate_roots, args[i])
			i = i + 1
		end
		combined_root = table.concat(separate_roots, " ")
	end
	local past_vowel = args[i]
	i = i + 1
	if past_vowel and not rfind(past_vowel, past_vowel_re) then
		error("Unrecognized past vowel, should be 'a', 'i', 'u', 'a,u', etc. or empty: " .. past_vowel)
	end
	if not past_vowel then
		past_vowel = ""
	end

	local split_root = rsplit(combined_root, " ")
	-- Map from verb forms (I, II, etc.) to a table of verb properties,
	-- which has entries e.g. for "verb" (either true to autogenerate the verb
	-- head, or an explicitly specified verb head using e.g. argument "I-head"),
	-- and for "verb-gloss" (which comes from e.g. the argument "I" or "I-gloss"),
	-- and for "vn" and "vn-gloss", "ap" and "ap-gloss", "pp" and "pp-gloss".
	local verb_properties = {}
	for _, form in ipairs(allowed_forms) do
		local formpropslist = {}
		local derivs = {{"verb", ""}, {"vn", "-vn"}, {"ap", "-ap"}, {"pp", "-pp"}}
		local index = 1
		while true do
			local formprops = {}
			local prefix = index == 1 and form or form .. index
			if prefix == "I" then
				formprops["pv"] = past_vowel
			end
			if args[prefix .. "-pv"] then
				formprops["pv"] = args[prefix .. "-pv"]
			end
			for _, deriv in ipairs(derivs) do
				local prop = deriv[1]
				local extn = deriv[2]
				if args[prefix .. extn] == "+" then
					formprops[prop] = true
				elseif args[prefix .. extn] == "-" then
					formprops[prop] = false
				elseif args[prefix .. extn] then
					formprops[prop] = true
					formprops[prop .. "-gloss"] = args[prefix .. extn]
				end
				if args[prefix .. extn .. "-head"] then
					if formprops[prop] == nil then
						formprops[prop] = true
					end
					formprops[prop] = args[prefix .. extn .. "-head"]
				end
				if args[prefix .. extn .. "-gloss"] then
					if formprops[prop] == nil then
						formprops[prop] = true
					end
					formprops[prop .. "-gloss"] = args[prefix .. extn .. "-gloss"]
				end
			end
			if formprops["verb"] then
				-- If a verb form specified, also turn on vn (unless form I, with
				-- unpredictable vn) and ap, and maybe pp, according to form,
				-- weakness and past vowel. But don't turn these on if there's
				-- an explicit on/off specification for them (e.g. I-pp=-).
				if form ~= "I" and formprops["vn"] == nil then
					formprops["vn"] = true
				end
				if formprops["ap"] == nil then
					formprops["ap"] = true
				end
				local weakness = weakness_from_radicals(form, split_root[1],
					split_root[2], split_root[3], split_root[4])
				if formprops["pp"] == nil and not form_probably_no_passive(form,
						weakness, rsplit(formprops["pv"] or "", ","), {}) then
					formprops["pp"] = true
				end
				table.insert(formpropslist, formprops)
				index = index + 1
			else
				break
			end
		end
		table.insert(verb_properties, {form, formpropslist})
	end

	-- Go through and create the verb form derivations as necessary, when
	-- they haven't been explicitly given
	for _, vplist in ipairs(verb_properties) do
		local form = vplist[1]
		for _, props in ipairs(vplist[2]) do
			local args = {}
			function append_form_and_root()
				table.insert(args, form)
				if form == "I" then
					table.insert(args, props["pv"]) -- past vowel
					table.insert(args, "")
				end
				append_array(args, split_root)
			end
			if props["verb"] == true then
				args = {}
				append_form_and_root()
				props["verb"] = past3sm(args, true)
			end
			for _, deriv in ipairs({"vn", "ap", "pp"}) do
				if props[deriv] == true then
					args = {deriv}
					append_form_and_root()
					props[deriv] = verb_part(args, true)
				end
			end
		end
	end

    -- Go through and output the result
	local formtextarr = {}
	for _, vplist in ipairs(verb_properties) do
		local form = vplist[1]
		for _, props in ipairs(vplist[2]) do
			local textarr = {}
			if props["verb"] then
				local text = "* '''[[Appendix:Arabic verbs#Form " .. form .. "|Form " .. form .. "]]''': "
				local linktext = {}
				local splitheads = rsplit(props["verb"], "[,،]")
				for _, head in ipairs(splitheads) do
					table.insert(linktext, m_links.full_link({lang = lang, term = head, gloss = ine(props["verb-gloss"])}))
				end
				text = text .. table.concat(linktext, ", ")
				table.insert(textarr, text)
				for _, derivengl in ipairs({{"vn", "Verbal noun"}, {"ap", "Active participle"}, {"pp", "Passive participle"}}) do
					local deriv = derivengl[1]
					local engl = derivengl[2]
					if props[deriv] then
						local text = "** " .. engl .. ": "
						local linktext = {}
						local splitheads = rsplit(props[deriv], "[,،]")
						for _, head in ipairs(splitheads) do
							table.insert(linktext, m_links.full_link({lang = lang, term = head, gloss = ine(props[deriv .. "-gloss"])}))
						end
						text = text .. table.concat(linktext, ", ")
						table.insert(textarr, text)
					end
				end
				table.insert(formtextarr, table.concat(textarr, "\n"))
			end
		end
	end

	return table.concat(formtextarr, "\n")
end

-- Version of verb_forms entry point meant for calling from the debug console.
-- See export.show2().
function export.verb_forms2(parargs, args)
	return export.verb_forms(debug_frame(parargs, args))
end

-- Guts of conjugation functions. Shared between {{temp|ar-conj}} and
-- {{temp|ar-verb}}, among others. ARGS is the frame parent arguments,
-- as returned by get_frame_args() (i.e. with arguments passed through ine()).
-- ARGIND is the numbered argument holding the verb form (either 1 or 2);
-- if form is I, the next two arguments are the past and non-past vowels;
-- afterwards are the (optional) radicals. Return five values:
-- DATA, FORM, WEAKNESS, PAST_VOWEL, NONPAST_VOWEL. The last two are
-- arrays of vowels (each one 'a', 'i' or 'u'), since there may be more
-- than one, or none in the case of non-form-I verbs.
function conjugate(args, argind)
	local data = {forms = {}, categories = {}, headword_categories = {}}

	title = mw.title.getCurrentTitle()
	NAMESPACE = title.nsText
	PAGENAME = ( NAMESPACE == "Appendix" and title.subpageText ) or title.text

	local conj_type = args[argind] or
		error("Form (conjugation type) has not been specified. " ..
			"Please pass a value to parameter " .. argind ..
			" in the template invocation.")

	-- derive form and weakness from conj type
	local form, weakness
	if rfind(conj_type, "%-") then
		local form_weakness = rsplit(conj_type, "%-")
		form = form_weakness[1]
		table.remove(form_weakness, 1)
		weakness = table.concat(form_weakness, "-")
	else
		form = conj_type
		weakness = nil
	end

	-- convert numeric forms to Roman numerals
	-- no longer supported
	-- form = canonicalize_form(form)

	-- check for quadriliteral form (Iq, IIq, IIIq, IVq)
	local quadlit = rmatch(form, "q$")

	-- get radicals and past/non-past vowel
	local rad1, rad2, rad3, rad4, past_vowel, nonpast_vowel
	if form == "I" then
		past_vowel = args[argind + 1]
		nonpast_vowel = args[argind + 2]
		local function splitvowel(vowelspec)
			if vowelspec == nil then
				vowelspec = {}
			else
				vowelspec = rsplit(vowelspec, ",")
			end
			return vowelspec
		end
		-- allow multiple past or non-past vowels separated by commas, e.g.
		-- in farada/faruda yafrudu "to be single"
		past_vowel = splitvowel(past_vowel)
		nonpast_vowel = splitvowel(nonpast_vowel)
		rad1 = args[argind + 3] or args["I"]
		rad2 = args[argind + 4] or args["II"]
		rad3 = args[argind + 5] or args["III"]
	else
		rad1 = args[argind + 1] or args["I"]
		rad2 = args[argind + 2] or args["II"]
		rad3 = args[argind + 3] or args["III"]
		if quadlit then
			rad4 = args[argind + 4] or args["IV"]
		end
	end

	-- Default any unspecified radicals to radicals determined from the
	-- headword. The return radicals may have Latin letters in them (w, t, y)
	-- to indicate ambiguous radicals that should be converted to the
	-- corresponding Arabic letters.
	--
	-- Only call infer_radicals() if at least one radical unspecified,
	-- because infer_radicals() will throw an error if the headword is
	-- malformed for the form, and we don't want that to happen (e.g. we might
	-- be called from a test page).
	if not rad1 or not rad2 or not rad3 or quadlit and not rad4 then
		local wkness, r1, r2, r3, r4 =
			export.infer_radicals(PAGENAME, form)
		-- Use the inferred weakness if we don't override any of the inferred
		-- radicals with something else, i.e. for each user-specified radical,
		-- either it's nil (was not specified) or same as inferred radical.
		-- That way we will correctly set the weakness to sound in cases like
		-- layisa "to be valiant", 'aḥwaja "to need", istahwana "to consider easy",
		-- izdawaja "to be in pairs", etc.
		local use_wkness = (not rad1 or rad1 == r1) and (not rad2 or rad2 == r2) and
			(not rad3 or rad3 == r3) and (not rad4 or rad4 == r4)
		rad1 = rad1 or r1
		rad2 = rad2 or r2
		rad3 = rad3 or r3
		rad4 = rad4 or r4

		-- For most ambiguous radicals, the choice of radical doesn't matter
		-- because it doesn't affect the conjugation one way or another.
		-- For form I hollow verbs, however, it definitely does. In fact, the
		-- choice of radical is critical even beyond the past and non-past
		-- vowels because it affects the form of the passive participle.
		-- So, check for this, try to guess if necessary from non-past vowel,
		-- else signal an error, requiring that the radical be specified
		-- explicitly. This will happen when the non-past vowel isn't specified
		-- and also when it's "a", from which the radical cannot be inferred.
		-- Do this check here rather than in infer_radicals() so that we don't
		-- get an error if the appropriate radical is given but not others.
		if form == "I" and (rad2 == "w" or rad2 == "y") then
			if contains(nonpast_vowel, "i") then
				rad2 = Y
			elseif contains(nonpast_vowel, "u") then
				rad2 = W
			else
				error("Unable to guess middle radical of hollow form I verb; " ..
					"need to specify radical explicitly")
			end
		end

		-- If weakness unspecified, then maybe default to weakness determined
		-- from headword. We do this specifically when some radicals are
		-- unspecified and all specified radicals are the same as the
		-- corresponding inferred radicals, i.e. the specified radicals (if any)
		-- don't provide any new information. When this isn't the case, and
		-- the specified radicals override the inferred radicals with something
		-- else, the inferred weakness may be wrong, so we figure out
		-- the weakness below by ourselves, based on the combination of any
		-- user-specified and inferred radicals.
		--
		-- The reason for using the inferred weakness when possible is that
		-- it may be more accurate than the weakness we derive below, in
		-- particular with verbs like layisa "to be courageous",
		-- `awira "to be one-eyed", 'aḥwaja "to need", istajwaba "to interrogate",
		-- izdawaja "to be in pairs", with a weak vowel in a sound conjugation.
		-- The weakness derived below from the radicals would be hollow but the
		-- weakness inferred in infer_radicals() is (correctly) sound.
		if use_wkness then
			weakness = weakness or wkness
		end
	end

	-- Store unregularized radicals for later use in creating categories
	data.unreg_rad1 = rad1
	data.unreg_rad2 = rad2
	data.unreg_rad3 = rad3
	data.unreg_rad4 = rad4

	-- Convert the Latin radicals indicating ambiguity into the corresponding
	-- Arabic radicals.
	local function regularize_inferred_radical(rad)
		if rad == "t" then
			return T
		elseif rad == "w" then
			return W
		elseif rad == "y" then
			return Y
		else
			return rad
		end
	end

	rad1 = regularize_inferred_radical(rad1)
	rad2 = regularize_inferred_radical(rad2)
	rad3 = regularize_inferred_radical(rad3)
	rad4 = regularize_inferred_radical(rad4)

	data.rad1 = rad1
	data.rad2 = rad2
	data.rad3 = rad3
	data.rad4 = rad4

	-- Old code, default radicals to ف-ع-ل or variants.

	--if not quadlit then
	--	-- default radicals to ف-ع-ل (or ف-ل-ل for geminate, or with the
	--	-- appropriate radical replaced by wāw for assimilated/hollow/final-weak)
	--	rad1 = rad1 or
	--		(weakness == "assimilated" or weakness == "assimilated+final-weak") and W or "ف"
	--	rad2 = rad2 or weakness == "hollow" and W or
	--		weakness == "geminate" and "ل" or "ع"
	--	rad3 = rad3 or (weakness == "final-weak" or weakness == "assimilated+final-weak") and W or
	--		weakness == "geminate" and rad2 or "ل"
	--else
	--	-- default to ف-ع-ل-ق (or ف-ع-ل-و for final-weak)
	--	rad1 = rad1 or "ف"
	--	rad2 = rad2 or "ع"
	--	rad3 = rad3 or "ل"
	--	rad4 = rad4 or weakness == "final-weak" and W or "ق"
	--end

	if weakness == nil then
		weakness = weakness_from_radicals(form, rad1, rad2, rad3, rad4)
	end
	
	-- Error if radicals are wrong given the weakness. More likely to happen
	-- if the weakness is explicitly given rather than inferred. Will also
	-- happen if certain incorrect letters are included as radicals e.g.
	-- hamza on top of various letters, alif maqṣūra, tā' marbūṭa.
	check_radicals(form, weakness, rad1, rad2, rad3, rad4)

	-- check to see if an argument ends in a ?. If so, strip the ? and return
	-- true. Otherwise, return false.
	local function check_for_uncertainty(arg)
		if args[arg] and rfind(args[arg], "%?$") then
			args[arg] = rsub(args[arg], "%?$", "")
			if args[arg] == "" then
				args[arg] = nil
			end
			return true
		else
			return false
		end
	end

	-- allow a ? at the end of vn= and passive=; if so, putting the page into
	-- special categories indicating the need to check the property in
	-- question, and remove the ?. Also put into category for vn= if empty
	-- and form is I.
	if check_for_uncertainty("vn") or form == "I" and not args["vn"] then
		table.insert(data.categories, "Arabic verbs needing verbal noun checked")
		data.vn_uncertain = true
	end
	if check_for_uncertainty("passive") then
		table.insert(data.categories, "Arabic verbs needing passive checked")
		data.passive_uncertain = true
	end

	-- parse value of passive.
	--
	-- if the value is "impers", the verb has only impersonal passive;
	-- if the value is "only", the verb has only a passive, no active;
	-- if the value is "only-impers", the verb has only an impersonal passive;
	-- if the value is "yes" or variants, verb has a passive;
	-- if the value is "no" or variants, the verb has no passive.
	-- If not specified, default is yes, but no for forms VII, IX,
	-- XII - XV and IIq - IVq, and "impers" for form VI.
	local passive = args["passive"]
	if passive == "impers" or passive == "only" or passive == "only-impers" then
	elseif not passive then
		passive = form_probably_impersonal_passive(form) and "impers" or
			not form_probably_no_passive(form, weakness, past_vowel,
				nonpast_vowel) and true or false
	else
		passive = yesno(passive, "unknown")
		if passive == "unknown" then
			error("Unrecognized value '" .. args["passive"] ..
				"' to argument passive=; use 'impers', 'only', 'only-impers', " ..
				"'yes'/'y'/'true'/'1' or 'no'/'n'/'false'/'0'")
		end
	end
	data.passive = passive

	data.noimp = yesno(args["noimp"], false)
	if data.noimp then
		table.insert(data.categories, "Arabic verbs lacking imperative forms")
	end

	-- Initialize categories related to form and weakness.
	initialize_categories(data,	form, weakness, rad1, rad2, rad3, rad4)

	-- Reconstruct conjugation type from form and (possibly inferred) weakness.
	conj_type = form .. "-" .. weakness

	-- Check that the conjugation type is recognized.
	if not conjugations[conj_type] then
		error("Unknown conjugation type '" .. conj_type .. "'")
	end

	-- Actually conjugate the verb. The signature of the conjugation function
	-- is different for form-I verbs, non-form-I triliteral verbs, and
	-- quadriliteral verbs.
	--
	-- The way the conjugation functions work is they always add entries to the
	-- appropriate parts of the paradigm (each of which is an array), rather
	-- than setting the values. This makes it possible to call more than one
	-- conjugation function and essentially get a paradigm of the "either
	-- A or B" kind. Doing this may insert duplicate entries into a particular
	-- paradigm part, but this is not a problem because we remove duplicate
	-- entries (in get_spans()) before generating the actual table.
	if quadlit then
		conjugations[conj_type](data, args, rad1, rad2, rad3, rad4)
	elseif form ~= "I" then
		conjugations[conj_type](data, args, rad1, rad2, rad3)
	else
		-- For Form-I verbs, we also pass in the past and non-past vowels.
		-- There may be more than one of each in case of alternative possible
		-- conjugations. In such cases, we loop over the sets of vowels,
		-- calling the appropriate conjugation function for each combination
		-- of past and non-past vowel.

		-- If the past or non-past vowel is unspecified, its value will be
		-- an empty array. In such a case, we still want to iterate once,
		-- passing in nil. Ideally, we'd convert empty arrays into one-element
		-- arrays holding the value nil, but Lua doesn't let you put the
		-- value nil into an array. To work around this we convert each array
		-- to an array of one-element arrays and fetch the first item of the
		-- inner array when we encounter it. Corresponding to nil will
		-- be an empty array, and fetching its first item will indeed
		-- return nil.
		local function convert_to_nested_array(array)
			if #array == 0 then
				return {{}}
			else
				local retval = {}
				for _, el in ipairs(array) do
					table.insert(retval, {el})
				end
				return retval
			end
		end
		local pv_nested = convert_to_nested_array(past_vowel)
		local npv_nested = convert_to_nested_array(nonpast_vowel)
		for i, pv in ipairs(pv_nested) do
			for j, npv in ipairs(npv_nested) do
				-- items were made into 1-element arrays so undo this
				conjugations[conj_type](data, args, rad1, rad2, rad3, pv[1], npv[1])
			end
		end
	end

	return data, form, weakness, past_vowel, nonpast_vowel
end

-- Determine weakness from radicals
function weakness_from_radicals(form, rad1, rad2, rad3, rad4)
	local weakness = nil
	local quadlit = rmatch(form, "q$")
	-- If weakness unspecified, derive from radicals.
	if not quadlit then
		if is_waw_ya(rad3) and rad1 == W and form == "I" then
			weakness = "assimilated+final-weak"
		elseif is_waw_ya(rad3) and form_supports_final_weak(form) then
			weakness = "final-weak"
		elseif rad2 == rad3 and form_supports_geminate(form) then
			weakness = "geminate"
		elseif is_waw_ya(rad2) and form_supports_hollow(form) then
			weakness = "hollow"
		elseif rad1 == W and form == "I" then
			weakness = "assimilated"
		else
			weakness = "sound"
		end
	else
		if is_waw_ya(rad4) then
			weakness = "final-weak"
		else
			weakness = "sound"
		end
	end
	return weakness
end

-- Infer radicals from headword and form. Throw an error if headword is
-- malformed. Returned radicals may contain Latin letters "t", "w" or "y"
-- indicating ambiguous radicals guessed to be tāʾ, wāw or yāʾ respectively.
function export.infer_radicals(headword, form)
	local letters = {}
	-- sub out alif-madda for easier processing
	headword = rsub(headword, AMAD, HAMZA .. ALIF)

	local len = ulen(headword)

	-- extract the headword letters into an array
	for i = 1, len do
		table.insert(letters, usub(headword, i, i))
	end

	-- check that the letter at the given index is the given string, or
	-- is one of the members of the given array
	local function check(index, must)
		local letter = letters[index]
		if type(must) == "string" then
			if letter == nil then
				error("Letter " .. index .. " is nil", 2)
			end
			if letter ~= must then
				error("For form " .. form .. ", letter " .. index ..
					" must be " .. must .. ", not " .. letter, 2)
			end
		elseif not contains(must, letter) then
			error("For form " .. form .. ", radical " .. index ..
				" must be one of " .. table.concat(must, " ") .. ", not " .. letter, 2)
		end
	end

	-- Check that length of headword is within [min, max]
	local function check_len(min, max)
		if len < min then
			error("Not enough letters in headword " .. headword ..
				" for form " .. form .. ", expected at least " .. min)
		elseif len > max then
			error("Too many letters in headword " .. headword ..
				" for form " .. form .. ", expected at most " .. max)
		end
	end

	local quadlit = rmatch(form, "q$")

	-- find first radical, start of second/third radicals, check for
	-- required letters
	local radstart, rad1, rad2, rad3, rad4
	local weakness
	if form == "I" or form == "II" then
		rad1 = letters[1]
		radstart = 2
	elseif form == "III" then
		rad1 = letters[1]
		check(2, {ALIF, WAW}) -- WAW occurs in passive-only verbs
		radstart = 3
	elseif form == "IV" then
		-- this would be alif-madda but we replaced it with hamza-alif above.
		if letters[1] == HAMZA and letters[2] == ALIF then
			rad1 = HAMZA
		else
			check(1, HAMZA_ON_ALIF)
			rad1 = letters[2]
		end
		radstart = 3
	elseif form == "V" then
		check(1, T)
		rad1 = letters[2]
		radstart = 3
	elseif form == "VI" then
		check(1, T)
		if letters[2] == AMAD then
			rad1 = HAMZA
			radstart = 3
		else
			rad1 = letters[2]
			check(3, {ALIF, WAW}) -- WAW occurs in passive-only verbs
			radstart = 4
		end
	elseif form == "VII" then
		check(1, ALIF)
		check(2, N)
		rad1 = letters[3]
		radstart = 4
	elseif form == "VIII" then
		check(1, ALIF)
		rad1 = letters[2]
		if rad1 == T or rad1 == "د" or rad1 == "ث" or rad1 == "ذ" or rad1 == "ط" or rad1 == "ظ" then
			radstart = 3
		elseif rad1 == "ز" then
			check(3, "د")
			radstart = 4
		elseif rad1 == "ص" or rad1 == "ض"  then
			check(3, "ط")
			radstart = 4
		else
			check(3, T)
			radstart = 4
		end
		if rad1 == T then
			-- radical is ambiguous, might be ت or و or ي but doesn't affect
			-- conjugation
			rad1 = "t"
		end
	elseif form == "IX" then
		check(1, ALIF)
		rad1 = letters[2]
		radstart = 3
	elseif form == "X" then
		check(1, ALIF)
		check(2, S)
		check(3, T)
		rad1 = letters[4]
		radstart = 5
	elseif form == "Iq" then
		rad1 = letters[1]
		rad2 = letters[2]
		radstart = 3
	elseif form == "IIq" then
		check(1, T)
		rad1 = letters[2]
		rad2 = letters[3]
		radstart = 4
	elseif form == "IIIq" then
		check(1, ALIF)
		rad1 = letters[2]
		rad2 = letters[3]
		check(4, N)
		radstart = 5
	elseif form == "IVq" then
		check(1, ALIF)
		rad1 = letters[2]
		rad2 = letters[3]
		radstart = 4
	elseif form == "XI" then
		check_len(5, 5)
		check(1, ALIF)
		rad1 = letters[2]
		rad2 = letters[3]
		check(4, ALIF)
		rad3 = letters[5]
		weakness = "sound"
	elseif form == "XII" then
		check(1, ALIF)
		rad1 = letters[2]
		if letters[3] ~= letters[5] then
			error("For form XII, letters 3 and 5 of headword " .. headword ..
				" should be the same")
		end
		check(4, W)
		radstart = 5
	elseif form == "XIII" then
		check_len(5, 5)
		check(1, ALIF)
		rad1 = letters[2]
		rad2 = letters[3]
		check(4, W)
		rad3 = letters[5]
		if rad3 == AMAQ then
			weakness = "final-weak"
		else
			weakness = "sound"
		end
	elseif form == "XIV" then
		check_len(6, 6)
		check(1, ALIF)
		rad1 = letters[2]
		rad2 = letters[3]
		check(4, N)
		rad3 = letters[5]
		if letters[6] == AMAQ then
			check_waw_ya(rad3)
			weakness = "final-weak"
		else
			if letters[5] ~= letters[6] then
				error("For form XIV, letters 5 and 6 of headword " .. headword ..
					" should be the same")
			end
			weakness = "sound"
		end
	elseif form == "XV" then
		check_len(6, 6)
		check(1, ALIF)
		rad1 = letters[2]
		rad2 = letters[3]
		check(4, N)
		rad3 = letters[5]
		if rad3 == Y then
			check(6, ALIF)
		else
			check(6, AMAQ)
		end
		weakness = "sound"
	else
		error("Don't recognize form " .. form)
	end

	-- Process the last two radicals. RADSTART is the index of the
	-- first of the two. If it's nil then all radicals have already been
	-- processed above, and we don't do anything.
	if radstart ~= nil then
		-- there must be one or two letters left
		check_len(radstart, radstart + 1)
		if len == radstart then
			-- if one letter left, then it's a geminate verb
			if form_supports_geminate(form) then
				weakness = "geminate"
				rad2 = letters[len]
				rad3 = letters[len]
			else
				-- oops, geminate verbs not allowed in this form; signal
				-- an error
				check_len(radstart + 1, radstart + 1)
			end
		elseif quadlit then
			-- process last two radicals of a quadriliteral form
			rad3 = letters[radstart]
			rad4 = letters[radstart + 1]
			if rad4 == AMAQ or rad4 == ALIF and rad3 == Y or rad4 == Y then
				-- rad4 can be Y in passive-only verbs
				if form_supports_final_weak(form) then
					weakness = "final-weak"
					-- ambiguous radical; randomly pick wāw as radical (but avoid
					-- two wāws in a row); it could be wāw or yāʾ, but doesn't
					-- affect the conjugation
					rad4 = rad3 == W and "y" or "w"
				else
					error("For headword " .. headword ..
						", last radical is " .. rad4 .. " but form " .. form ..
						" doesn't support final-weak verbs")
				end
			else
				weakness = "sound"
			end
		else
			-- process last two radicals of a triliteral form
			rad2 = letters[radstart]
			rad3 = letters[radstart + 1]
			if form == "I" and (is_waw_ya(rad3) or rad3 == ALIF or rad3 == AMAQ) then
				-- check for final-weak form I verb. It can end in tall alif
				-- (rad3 = wāw) or alif maqṣūra (rad3 = yāʾ) or a wāw or yāʾ
				-- (with a past vowel of i or u, e.g. nasiya/yansā "forget"
				-- or with a passive-only verb).
				if rad1 == W then
					weakness = "assimilated+final-weak"
				else
					weakness = "final-weak"
				end
				if rad3 == ALIF then
					rad3 = W
				elseif rad3 == AMAQ then
					rad3 = Y
				else
					-- ambiguous radical; randomly pick wāw as radical (but
					-- avoid two wāws); it could be wāw or yāʾ, but doesn't
					-- affect the conjugation
					rad3 = (rad1 == W or rad2 == W) and "y" or "w" -- ambiguous
				end
		elseif rad3 == AMAQ or rad2 == Y and rad3 == ALIF or rad3 == Y then
				-- rad3 == Y happens in passive-only verbs
				if form_supports_final_weak(form) then
					weakness = "final-weak"
				else
					error("For headword " .. headword ..
						", last radical is " .. rad3 .. " but form " .. form ..
						" doesn't support final-weak verbs")
				end
				-- ambiguous radical; randomly pick wāw as radical (but avoid
				-- two wāws); it could be wāw or yāʾ, but doesn't affect the
				-- conjugation
				rad3 = (rad1 == W or rad2 == W) and "y" or "w"
			elseif rad2 == ALIF then
				if form_supports_hollow(form) then
					weakness = "hollow"
					-- ambiguous radical; could be wāw or yāʾ; if form I,
					-- it's critical to get this right, and the caller checks
					-- for this situation, attempts to infer radical from
					-- non-past vowel, and if that fails, signals an error
					rad2 = "w"
				else
					error("For headword " .. headword ..
						", second radical is alif but form " .. form ..
						" doesn't support hollow verbs")
				end
			elseif form == "I" and rad1 == W then
				weakness = "assimilated"
			elseif rad2 == rad3 and (form == "III" or form == "VI") then
				weakness = "geminate"
			else
				weakness = "sound"
			end
		end
	end

	-- convert radicals to canonical form (handle various hamza varieties and
	-- check for misplaced alif or alif maqṣūra; legitimate cases of these
	-- letters are handled above)
	local function convert(rad, index)
		if rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or
			rad == HAMZA_ON_W or rad == HAMZA_ON_Y then
			return HAMZA
		elseif rad == AMAQ then
			error("For form " .. form .. ", headword " .. headword ..
				", radical " .. index .. " must not be alif maqṣūra")
		elseif rad == ALIF then
			error("For form " .. form .. ", headword " .. headword ..
				", radical " .. index .. " must not be alif")
		else
			return rad
		end
	end
	rad1 = convert(rad1, 1)
	rad2 = convert(rad2, 2)
	rad3 = convert(rad3, 3)
	rad4 = convert(rad4, 4)

	return weakness, rad1, rad2, rad3, rad4
end

-- given form, weakness and radicals, check to make sure the radicals present
-- are allowable for the weakness. Hamzas on alif/wāw/yāʾ seats are never
-- allowed (should always appear as hamza-on-the-line), and various weaknesses
-- have various strictures on allowable consonants.
function check_radicals(form, weakness, rad1, rad2, rad3, rad4)
	local function hamza_check(index, rad)
		if rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or
			rad == HAMZA_ON_W or rad == HAMZA_ON_Y then
			error("Radical " .. index .. " is " .. rad .. " but should be ء (hamza on the line)")
		end
	end
	local function check_waw_ya(index, rad)
		if rad ~= W and rad ~= Y then
			error("Radical " .. index .. " is " .. rad .. " but should be و or ي")
		end
	end
	local function check_not_waw_ya(index, rad)
		if rad == W or rad == Y then
			error("In a sound verb, radical " .. index .. " should not be و or ي")
		end
	end
	hamza_check(rad1)
	hamza_check(rad2)
	hamza_check(rad3)
	hamza_check(rad4)
	if weakness == "assimilated" or weakness == "assimilated+final-weak" then
		if rad1 ~= W then
			error("Radical 1 is " .. rad1 .. " but should be و")
		end
	-- don't check that non-assimilated form I verbs don't have wāw as their
	-- first radical because some form-I verbs exist where a first-radical wāw
	-- behaves as sound, e.g. wajuha yawjuhu "to be distinguished".
	end
	if weakness == "final-weak" or weakness == "assimilated+final-weak" then
		if rad4 then
			check_waw_ya(4, rad4)
		else
			check_waw_ya(3, rad3)
		end
	elseif form_supports_final_weak(form) then
		-- non-final-weak verbs cannot have weak final radical if there's a corresponding
		-- final-weak verb category. I think this is safe. We may have problems with
		-- ḥayya/ḥayiya yaḥyā if we treat it as a geminate verb.
		if rad4 then
			check_not_waw_ya(4, rad4)
		else
			check_not_waw_ya(3, rad3)
		end
	end
	if weakness == "hollow" then
		check_waw_ya(2, rad2)
	-- don't check that non-hollow verbs in forms that support hollow verbs
	-- don't have wāw or yāʾ as their second radical because some verbs exist
	-- where a middle-radical wāw/yāʾ behaves as sound, e.g. form-VIII izdawaja
	-- "to be in pairs".
	end
	if weakness == "geminate" then
		if rad4 then
			error("Internal error. No geminate quadrilaterals, should not be seen.")
		end
		if rad2 ~= rad3 then
			error("Weakness is geminate; radical 3 is " .. rad3 .. " but should be same as radical 2 " .. rad2 .. ".")
		end
	elseif form_supports_geminate(form) then
		-- non-geminate verbs cannot have second and third radical same if there's
		-- a corresponding geminate verb category. I think this is safe. We
		-- don't fuss over double wāw or double yāʾ because this could legitimately
		-- be a final-weak verb with middle wāw/yāʾ, treated as sound.
		if rad4 then
			error("Internal error. No quadrilaterals should support geminate verbs.")
		end
		if rad2 == rad3 and not is_waw_ya(rad2) then
			error("Weakness is '" .. weakness .. "'; radical 2 and 3 are same at " .. rad2 .. " but should not be; consider making weakness 'geminate'")
		end
	end
end

function initialize_categories(data, form, weakness, rad1, rad2, rad3, rad4)
	-- We have to distinguish weakness by form and weakness by conjugation.
	-- Weakness by form merely indicates the presence of weak letters in
	-- certain positions in the radicals. Weakness by conjugation is related
	-- to how the verbs are conjugated. For example, form-II verbs that are
	-- "hollow by form" (middle radical is wāw or yāʾ) are conjugated as sound
	-- verbs. Another example: form-I verbs with initial wāw are "assimilated
	-- by form" and most are assimilated by conjugation as well, but a few
	-- are sound by conjugation, e.g. wajuha yawjuhu "to be distinguished"
	-- (rather than wajuha yajuhu); similarly for some hollow-by-form verbs
	-- in various forms, e.g. form VIII izdawaja yazdawiju "to be in pairs"
	-- (rather than izdāja yazdāju). When most references say just plain
	-- "hollow" or "assimilated" or whatever verbs, they mean by form, so
	-- we name the categories appropriately, where e.g. "Arabic hollow verbs"
	-- means by form, "Arabic hollow verbs by conjugation" means by
	-- conjugation.
	table.insert(data.categories, "Arabic form-" .. form .. " verbs")
	table.insert(data.headword_categories, "Arabic form-" .. form .. " verbs")
	table.insert(data.categories, "Arabic " .. weakness .. " verbs by conjugation")
	table.insert(data.headword_categories, "Arabic " .. weakness .. " verbs by conjugation")
	if form_is_quadriliteral(form) then
		table.insert(data.categories, "Arabic verbs with quadriliteral roots")
		table.insert(data.headword_categories, "Arabic verbs with quadriliteral roots")
	end
	local formweak = {}
	if is_waw_ya(rad1) then
		table.insert(formweak, "assimilated")
	end
	if is_waw_ya(rad2) and rad4 == nil then
		table.insert(formweak, "hollow")
	end
	if is_waw_ya(rad4) or rad4 == nil and is_waw_ya(rad3) then
		table.insert(formweak, "final-weak")
	end
	if rad4 == nil and rad2 == rad3 then
		table.insert(formweak, "geminate")
	end
	if rad1 == HAMZA or rad2 == HAMZA or rad3 == HAMZA or rad4 == HAMZA then
		table.insert(formweak, "hamzated")
	end
	if #formweak == 0 then
		table.insert(formweak, "sound")
	end
	for _, fw in ipairs(formweak) do
		table.insert(data.categories, "Arabic " .. fw .. " form-" .. form .. " verbs")
		table.insert(data.categories, "Arabic " .. fw .. " verbs")
		table.insert(data.headword_categories, "Arabic " .. fw .. " form-" .. form .. " verbs")
		table.insert(data.headword_categories, "Arabic " .. fw .. " verbs")
	end


	local function radical_is_ambiguous(rad)
		return rad == "t" or rad == "w" or rad == "y"
	end
	local function radical_is_weak(rad)
		return rad == W or rad == Y or rad == HAMZA
	end

	local ur1, ur2, ur3, ur4 =
		data.unreg_rad1, data.unreg_rad2, data.unreg_rad3, data.unreg_rad4
	-- Create headword categories based on the radicals. Do the following before
	-- converting the Latin radicals into Arabic ones so we distinguish
	-- between ambiguous and non-ambiguous radicals.
	if radical_is_ambiguous(ur1) or radical_is_ambiguous(ur2) or
			radical_is_ambiguous(ur3) or radical_is_ambiguous(ur4) then
		table.insert(data.headword_categories,
			"Arabic verbs with ambiguous radicals")
	end
	if radical_is_weak(ur1) then
		table.insert(data.headword_categories, "Arabic form-" .. form ..
			" verbs with " .. ur1 .. " as first radical")
	end
	if radical_is_weak(ur2) then
		table.insert(data.headword_categories, "Arabic form-" .. form ..
			" verbs with " .. ur2 .. " as second radical")
	end
	if radical_is_weak(ur3) then
		table.insert(data.headword_categories, "Arabic form-" .. form ..
			" verbs with " .. ur3 .. " as third radical")
	end
	if radical_is_weak(ur4) then
		table.insert(data.headword_categories, "Arabic form-" .. form ..
			" verbs with " .. ur4 .. " as fourth radical")
	end

	if data.passive == "only" then
		table.insert(data.categories, "Arabic passive verbs")
		table.insert(data.categories, "Arabic verbs with full passive")
	elseif data.passive == "only-impers" then
		table.insert(data.categories, "Arabic passive verbs")
		table.insert(data.categories, "Arabic verbs with impersonal passive")
	elseif data.passive == "impers" then
		table.insert(data.categories, "Arabic verbs with impersonal passive")
	elseif data.passive then
		table.insert(data.categories, "Arabic verbs with full passive")
	else
		table.insert(data.categories, "Arabic verbs lacking passive forms")
	end
end

-------------------------------------------------------
-- Conjugation functions for specific conjugation types
-------------------------------------------------------

-- Check that the past or non-past vowel is a, i, or u. VOWEL is the vowel to
-- check and VTYPE indicates whether it's past or non-past and is used in
-- the error message.
function check_aiu(vtype, vowel)
	if vowel ~= "a" and vowel ~= "i" and vowel ~= "u" then
		error(vtype .. " vowel '" .. vowel .. "' should be a, i, or u")
	end
end

-- Is radical wāw (و) or yāʾ (ي)?
function is_waw_ya(rad)
	return rad == W or rad == Y
end

-- Check that radical is wāw (و) or yāʾ (ي), error if not
function check_waw_ya(rad)
	if not is_waw_ya(rad) then
		error("Expecting weak radical: '" .. rad .. "' should be " .. W .. " or " .. Y)
	end
end

-- Is radical guttural? This favors a non-past vowel of "a"
function is_guttural(rad)
	return rad == HAMZA or rad == "ه" or rad == "ع" or rad == "ح"
end

-- Derive default non-past vowel from past vowel. Most common possibilities are
-- a/u, a/i, a/a if rad2 or rad3 are guttural, i/a, u/u. We choose a/u over a/i.
function nonpast_from_past_vowel(past_vowel, rad2, rad3)
	return past_vowel == "i" and "a" or past_vowel == "u" and "u" or
		(is_guttural(rad2) or is_guttural(rad3)) and "a" or "u"
end

-- determine the imperative vowel based on non-past vowel
function imper_vowel_from_nonpast(nonpast_vowel)
	if nonpast_vowel == "a" or nonpast_vowel == "i" then
		return "i"
	elseif nonpast_vowel == "u" then
		return "u"
	else
		error("Non-past vowel '" .. nonpast_vowel .. "' isn't a, i, or u, should have been caught earlier")
	end
end

-- Convert short vowel to equivalent long vowel (a -> alif, u -> wāw, i -> yāʾ).
function short_to_long_vowel(vowel)
	if vowel == A then return ALIF
	elseif vowel == I then return Y
	elseif vowel == U then return W
	else
		error("Vowel '" .. vowel .. "' isn't a, i, or u, should have been caught earlier")
	end
end

-- Implement form-I sound or assimilated verb. ASSIMILATED is true for
-- assimilated verbs.
local function make_form_i_sound_assimilated_verb(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel, assimilated)
	-- need to provide two vowels - past and non-past
	past_vowel = past_vowel or "a"
	nonpast_vowel = nonpast_vowel or nonpast_from_past_vowel(past_vowel, rad2, rad3)
	check_aiu("past", past_vowel)
	check_aiu("non-past", nonpast_vowel)

	-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied
	insert_verbal_noun(data, args, {})

	-- past and non-past stems, active and passive
	local past_stem = rad1 .. A .. rad2 .. dia[past_vowel] .. rad3
	local nonpast_stem = assimilated and rad2 .. dia[nonpast_vowel] .. rad3 or
		rad1 .. SK .. rad2 .. dia[nonpast_vowel] .. rad3
	local ps_past_stem = rad1 .. U .. rad2 .. I .. rad3
	local ps_nonpast_stem = rad1 .. SK .. rad2 .. A .. rad3

	-- determine the imperative vowel based on non-past vowel
	local imper_vowel = imper_vowel_from_nonpast(nonpast_vowel)

	-- imperative stem
	-- check for irregular verb with reduced imperative (أَخَذَ or أَكَلَ or أَمَرَ)
	local reducedimp = reduced_imperative_verb(rad1, rad2, rad3)
	if reducedimp then
		data.irregular = true
	end
	local imper_stem_suffix = rad2 .. dia[nonpast_vowel] .. rad3
	local imper_stem_base = (assimilated or reducedimp) and "" or
		ALIF .. dia[imper_vowel] ..
		(rad1 == HAMZA and short_to_long_vowel(dia[imper_vowel]) or rad1 .. SK)
	local imper_stem = imper_stem_base .. imper_stem_suffix

	-- make parts
	make_sound_verb(data, past_stem, ps_past_stem, nonpast_stem,
		ps_nonpast_stem, imper_stem, "a")

	-- Check for irregular verb سَأَلَ with alternative jussive and imperative.
	-- Calling this after make_sound_verb() adds additional entries to the
	-- paradigm parts.
	if saal_radicals(rad1, rad2, rad3) then
		data.irregular = true
		nonpast_1stem_conj(data, "juss", "a", "سَل")
		nonpast_1stem_conj(data, "ps-juss", "u", "سَل")
		make_1stem_imperative(data, "سَل")
	end

	-- active participle
	insert_part(data, "ap", rad1 .. AA .. rad2 .. I .. rad3 .. UNS)
	-- passive participle
	insert_part(data, "pp", MA .. rad1 .. SK .. rad2 .. U .. "و" .. rad3 .. UNS)
end

conjugations["I-sound"] = function(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel)
	make_form_i_sound_assimilated_verb(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel, false)
end

conjugations["I-assimilated"] = function(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel)
	make_form_i_sound_assimilated_verb(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel, "assimilated")
end

local function make_form_i_hayy_verb(data, args)
	-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied
	insert_verbal_noun(data, args, {})

	data.irregular = true

	-- past and non-past stems, active and passive, and imperative stem
	local past_c_stem = "حَيِي"
	local past_v_stem_long = past_c_stem
	local past_v_stem_short = "حَيّ"
	local ps_past_c_stem = "حُيِي"
	local ps_past_v_stem_long = ps_past_c_stem
	local ps_past_v_stem_short = "حُيّ"

	local nonpast_stem = "حْي"
	local ps_nonpast_stem = nonpast_stem
	local imper_stem = "اِ" .. nonpast_stem

	-- make parts

	past_2stem_conj(data, "perf", "-", past_c_stem)
	past_2stem_conj(data, "ps-perf", "-", ps_past_c_stem)
	local variant = args["variant"]
	if variant == "short" or variant == "both" then
		past_2stem_conj(data, "perf", past_v_stem_short, "-")
		past_2stem_conj(data, "ps-perf", ps_past_v_stem_short, "-")
	end
	function inflect_long_variant(tense, long_stem, short_stem)
		inflect_tense_1(data, tense, "",
			{long_stem, long_stem, long_stem, long_stem, short_stem},
			{past_endings[4], past_endings[5], past_endings[7], past_endings[8],
			 past_endings[12]},
			{"3sm", "3sf", "3dm", "3df", "3pm"})
	end
	if variant == "long" or variant == "both" then
		inflect_long_variant("perf", past_v_stem_long, past_v_stem_short)
		inflect_long_variant("ps-perf", ps_past_v_stem_long, ps_past_v_stem_short)
	end

	nonpast_1stem_conj(data, "impf", "a", nonpast_stem, indic_endings_aa)
	nonpast_1stem_conj(data, "subj", "a", nonpast_stem, subj_endings_aa)
	nonpast_1stem_conj(data, "juss", "a", nonpast_stem, juss_endings_aa)
	nonpast_1stem_conj(data, "ps-impf", "u", ps_nonpast_stem, indic_endings_aa)
	nonpast_1stem_conj(data, "ps-subj", "u", ps_nonpast_stem, subj_endings_aa)
	nonpast_1stem_conj(data, "ps-juss", "u", ps_nonpast_stem, juss_endings_aa)
	inflect_tense_impr(data, imper_stem, impr_endings_aa)

	-- active participle apparently does not exist for this verb
	insert_part(data, "ap", {})
	-- passive participle apparently does not exist for this verb
	insert_part(data, "pp", {})
end

-- Implement form-I final-weak assimilated+final-weak verb. ASSIMILATED is true
-- for assimilated verbs.
local function make_form_i_final_weak_verb(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel, assimilated)

	-- حَيَّ or حَيِيَ is weird enough that we handle it as a separate function
	if hayy_radicals(rad1, rad2, rad3) then
		make_form_i_hayy_verb(data, args)
		return
	end

	-- need to provide two vowels - past and non-past
	local past_vowel = past_vowel or "a"
	local nonpast_vowel = nonpast_vowel or past_vowel == "i" and "a" or
		past_vowel == "u" and "u" or rad3 == Y and "i" or "u"
	check_aiu("past", past_vowel)
	check_aiu("non-past", nonpast_vowel)

	-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied
	insert_verbal_noun(data, args, {})

	-- past and non-past stems, active and passive, and imperative stem
	local past_stem = rad1 .. A .. rad2
	local ps_past_stem = rad1 .. U .. rad2
	local nonpast_stem, ps_nonpast_stem, imper_stem
	if raa_radicals(rad1, rad2, rad3) then
		data.irregular = true
		nonpast_stem = rad1
		ps_nonpast_stem = rad1
		imper_stem = rad1
	else
		ps_nonpast_stem = rad1 .. SK .. rad2
		if assimilated then
			nonpast_stem = rad2
			imper_stem = rad2
		else
			nonpast_stem = ps_nonpast_stem
			-- determine the imperative vowel based on non-past vowel
			local imper_vowel = imper_vowel_from_nonpast(nonpast_vowel)
			imper_stem =  ALIF .. dia[imper_vowel] ..
				(rad1 == HAMZA and short_to_long_vowel(dia[imper_vowel])
					or rad1 .. SK) ..
				rad2
		end
	end

	-- make parts
	local past_suffs =
		rad3 == Y and past_vowel == "a" and past_endings_ay or
		rad3 == W and past_vowel == "a" and past_endings_aw or
		past_vowel == "i" and past_endings_ii or
		past_endings_uu
	local indic_suffs, subj_suffs, juss_suffs, impr_suffs
	if nonpast_vowel == "a" then
		indic_suffs = indic_endings_aa
		subj_suffs = subj_endings_aa
		juss_suffs = juss_endings_aa
		impr_suffs = impr_endings_aa
	elseif nonpast_vowel == "i" then
		indic_suffs = indic_endings_ii
		subj_suffs = subj_endings_ii
		juss_suffs = juss_endings_ii
		impr_suffs = impr_endings_ii
	else
		assert(nonpast_vowel == "u")
		indic_suffs = indic_endings_uu
		subj_suffs = subj_endings_uu
		juss_suffs = juss_endings_uu
		impr_suffs = impr_endings_uu
	end
	make_final_weak_verb(data, past_stem, ps_past_stem, nonpast_stem,
		ps_nonpast_stem, imper_stem, past_suffs, indic_suffs,
		subj_suffs, juss_suffs, impr_suffs, "a")

	-- active participle
	insert_part(data, "ap", rad1 .. AA .. rad2 .. IN)
	-- passive participle
	insert_part(data, "pp", MA .. rad1 .. SK .. rad2 ..
		(rad3 == Y and II or UU) .. SH .. UNS)
end

conjugations["I-final-weak"] = function(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel)
	make_form_i_final_weak_verb(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel, false)
end

conjugations["I-assimilated+final-weak"] = function(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel)
	make_form_i_final_weak_verb(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel, "assimilated")
end

conjugations["I-hollow"] = function(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel)
	-- need to specify up to two vowels, past and non-past
	-- default past vowel to short equivalent of middle radical
	local past_vowel = past_vowel or rad2 == Y and "i" or "u"
	-- default non-past vowel to past vowel, unless it's "a", in which case
	-- we default to short equivalent of middle radical
	local nonpast_vowel = nonpast_vowel or past_vowel ~= "a" and past_vowel or
		rad2 == Y and "i" or "u"
	check_aiu("past", past_vowel)
	check_aiu("non-past", nonpast_vowel)
	-- Formerly we signaled an error when past_vowel is "a" but that seems
	-- too harsh. We can interpret a past vowel of "a" as meaning to use the
	-- non-past vowel in forms requiring a short vowel. If the non-past vowel
	-- is "a" then the past vowel can only be "i" (e.g. in nāma yanāmu with
	-- first singular past of nimtu).
	if past_vowel == "a" then
		-- error("For form I hollow, past vowel cannot be 'a'")
		past_vowel = nonpast_vowel == "a" and "i" or nonpast_vowel
	end
	local lengthened_nonpast = nonpast_vowel == "u" and UU or
		nonpast_vowel == "i" and II or AA

	-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied
	insert_verbal_noun(data, args, {})

	-- active past stems - vowel (v) and consonant (c)
	local past_v_stem = rad1 .. AA .. rad3
	local past_c_stem = rad1 .. dia[past_vowel] .. rad3

	-- active non-past stems - vowel (v) and consonant (c)
	local nonpast_v_stem = rad1 .. lengthened_nonpast .. rad3
	local nonpast_c_stem = rad1 .. dia[nonpast_vowel] .. rad3

	-- passive past stems - vowel (v) and consonant (c)
	-- 'ufīla, 'ufiltu
	local ps_past_v_stem = rad1 .. II .. rad3
	local ps_past_c_stem = rad1 .. I .. rad3

	-- passive non-past stems - vowel (v) and consonant (c)
	-- yufāla/yufalna
	-- stem is built differently but conjugation is identical to sound verbs
	local ps_nonpast_v_stem = rad1 .. AA .. rad3
	local ps_nonpast_c_stem = rad1 .. A .. rad3

	-- imperative stem
	local imper_v_stem = nonpast_v_stem
	local imper_c_stem = nonpast_c_stem

	-- make parts
	make_hollow_geminate_verb(data, past_v_stem, past_c_stem, ps_past_v_stem,
		ps_past_c_stem, nonpast_v_stem, nonpast_c_stem, ps_nonpast_v_stem,
		ps_nonpast_c_stem, imper_v_stem, imper_c_stem, "a", false)

	-- active participle
	insert_part(data, "ap", rad3 == HAMZA and rad1 .. AA .. HAMZA .. IN or
		rad1 .. AA .. HAMZA .. I .. rad3 .. UNS)
	-- passive participle
	insert_part(data, "pp", MA .. rad1 .. (rad2 == Y and II or UU) .. rad3 .. UNS)
end

conjugations["I-geminate"] = function(data, args, rad1, rad2, rad3,
		past_vowel, nonpast_vowel)
	-- need to specify two vowels, past and non-past
	local past_vowel = past_vowel or "a"
	local nonpast_vowel = nonpast_vowel or nonpast_from_past_vowel(past_vowel, rad2, rad3)

	-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied
	insert_verbal_noun(data, args, {})

	-- active past stems - vowel (v) and consonant (c)
	local past_v_stem = rad1 .. A .. rad2 .. SH
	local past_c_stem = rad1 .. A .. rad2 .. dia[past_vowel] .. rad2

	-- active non-past stems - vowel (v) and consonant (c)
	local nonpast_v_stem = rad1 .. dia[nonpast_vowel] .. rad2 .. SH
	local nonpast_c_stem = rad1 .. SK .. rad2 .. dia[nonpast_vowel] .. rad2

	-- passive past stems - vowel (v) and consonant (c)
	-- dulla/dulilta
	local ps_past_v_stem = rad1 .. U .. rad2 .. SH
	local ps_past_c_stem = rad1 .. U .. rad2 .. I .. rad2

	-- passive non-past stems - vowel (v) and consonant (c)
	--yudallu/yudlalna
	-- stem is built differently but conjugation is identical to sound verbs
	local ps_nonpast_v_stem = rad1 .. A .. rad2 .. SH
	local ps_nonpast_c_stem = rad1 .. SK .. rad2 .. A .. rad2

	-- determine the imperative vowel based on non-past vowel
	local imper_vowel = imper_vowel_from_nonpast(nonpast_vowel)

	-- imperative stem
	local imper_v_stem = rad1 .. dia[nonpast_vowel] .. rad2 .. SH
	local imper_c_stem = ALIF .. dia[imper_vowel] ..
		(rad1 == HAMZA and short_to_long_vowel(dia[imper_vowel]) or rad1 .. SK) ..
		rad2 .. dia[nonpast_vowel] .. rad2

	-- make parts
	make_hollow_geminate_verb(data, past_v_stem, past_c_stem, ps_past_v_stem,
		ps_past_c_stem, nonpast_v_stem, nonpast_c_stem, ps_nonpast_v_stem,
		ps_nonpast_c_stem, imper_v_stem, imper_c_stem, "a", "geminate")

	-- active participle
	insert_part(data, "ap", rad1 .. AA .. rad2 .. SH .. UNS)
	-- passive participle
	insert_part(data, "pp", MA .. rad1 .. SK .. rad2 .. U .. "و" .. rad2 .. UNS)
end

-- Make form II or V sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil, and FORM distinguishes II from V.
function make_form_ii_v_sound_final_weak_verb(data, args, rad1, rad2, rad3, form)
	local final_weak = rad3 == nil
	local vn = form == "V" and
		"تَ" .. rad1 .. A .. rad2 .. SH ..
			(final_weak and IN or U .. rad3 .. UNS) or
		"تَ" .. rad1 .. SK .. rad2 .. I .. "ي" ..
			(final_weak and AH or rad3) .. UNS
	local ta_pref = form == "V" and "تَ" or ""
	local tu_pref = form == "V" and "تُ" or ""

	-- various stem bases
	local past_stem_base = ta_pref .. rad1 .. A .. rad2 .. SH
	local nonpast_stem_base = past_stem_base
	local ps_past_stem_base = tu_pref .. rad1 .. U .. rad2 .. SH

	-- make parts
	make_augmented_sound_final_weak_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)
end

conjugations["II-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_ii_v_sound_final_weak_verb(data, args, rad1, rad2, rad3, "II")
end

conjugations["II-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_ii_v_sound_final_weak_verb(data, args, rad1, rad2, nil, "II")
end

-- Make form III or VI sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil, and FORM distinguishes III from VI.
function make_form_iii_vi_sound_final_weak_verb(data, args, rad1, rad2, rad3, form)
	local final_weak = rad3 == nil
	local vn = form == "VI" and
		"تَ" .. rad1 .. AA .. rad2 ..
			(final_weak and IN or U .. rad3 .. UNS) or
		{MU .. rad1 .. AA .. rad2 .. (final_weak and AAH or A .. rad3 .. AH) .. UNS,
			rad1 .. I .. rad2 .. AA .. (final_weak and HAMZA or rad3) .. UNS}
	local ta_pref = form == "VI" and "تَ" or ""
	local tu_pref = form == "VI" and "تُ" or ""

	-- various stem bases
	local past_stem_base = ta_pref .. rad1 .. AA .. rad2
	local nonpast_stem_base = past_stem_base
	local ps_past_stem_base = tu_pref .. rad1 .. UU .. rad2

	-- make parts
	make_augmented_sound_final_weak_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)
end

conjugations["III-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_iii_vi_sound_final_weak_verb(data, args, rad1, rad2, rad3, "III")
end

conjugations["III-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_iii_vi_sound_final_weak_verb(data, args, rad1, rad2, nil, "III")
end

-- Make form III or VI geminate verb. FORM distinguishes III from VI.
function make_form_iii_vi_geminate_verb(data, args, rad1, rad2, form)
	-- alternative verbal noun فِعَالٌ will be inserted when we add sound parts below
	local vn = form == "VI" and
		{"تَ" .. rad1 .. AA .. rad2 .. SH .. UNS} or
		{MU .. rad1 .. AA .. rad2 .. SH .. AH .. UNS}
	local ta_pref = form == "VI" and "تَ" or ""
	local tu_pref = form == "VI" and "تُ" or ""

	-- various stem bases
	local past_stem_base = ta_pref .. rad1 .. AA
	local nonpast_stem_base = past_stem_base
	local ps_past_stem_base = tu_pref .. rad1 .. UU

	-- make parts
	make_augmented_geminate_verb(data, args, rad2,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)

	-- Also add alternative sound (non-compressed) parts. This will lead to
	-- some duplicate entries, but they are removed in get_spans().
	make_form_iii_vi_sound_final_weak_verb(data, args, rad1, rad2, rad2, form)
end

conjugations["III-geminate"] = function(data, args, rad1, rad2, rad3)
	make_form_iii_vi_geminate_verb(data, args, rad1, rad2, "III")
end

-- Make form IV sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil.
function make_form_iv_sound_final_weak_verb(data, args, rad1, rad2, rad3)
	local final_weak = rad3 == nil

	-- core of stem base, minus stem prefixes
	local stem_core

	-- check for irregular verb أَرَى
	if raa_radicals(rad1, rad2, final_weak and Y or rad3) then
		data.irregular = true
		stem_core = rad1
	else
		stem_core =	rad1 .. SK .. rad2
	end

	-- verbal noun
	local vn = HAMZA .. I .. stem_core .. AA ..
		(final_weak and HAMZA or rad3) .. UNS

	-- various stem bases
	local past_stem_base = HAMZA .. A .. stem_core
	local nonpast_stem_base = stem_core
	local ps_past_stem_base = HAMZA .. U .. stem_core

	-- make parts
	make_augmented_sound_final_weak_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "IV")
end

conjugations["IV-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_iv_sound_final_weak_verb(data, args, rad1, rad2, rad3)
end

conjugations["IV-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_iv_sound_final_weak_verb(data, args, rad1, rad2, nil)
end

conjugations["IV-hollow"] = function(data, args, rad1, rad2, rad3)
	-- verbal noun
	local vn = HAMZA .. I .. rad1 .. AA .. rad3 .. AH .. UNS

	-- various stem bases
	local past_stem_base = HAMZA .. A .. rad1
	local nonpast_stem_base = rad1
	local ps_past_stem_base = HAMZA .. U .. rad1

	-- make parts
	make_augmented_hollow_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "IV")
end

conjugations["IV-geminate"] = function(data, args, rad1, rad2, rad3)
	local vn = HAMZA .. I .. rad1 .. SK .. rad2 .. AA .. rad2 .. UNS

	-- various stem bases
	local past_stem_base = HAMZA .. A .. rad1
	local nonpast_stem_base = rad1
	local ps_past_stem_base = HAMZA .. U .. rad1

	-- make parts
	make_augmented_geminate_verb(data, args, rad2,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "IV")
end

conjugations["V-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_ii_v_sound_final_weak_verb(data, args, rad1, rad2, rad3, "V")
end

conjugations["V-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_ii_v_sound_final_weak_verb(data, args, rad1, rad2, nil, "V")
end

conjugations["VI-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_iii_vi_sound_final_weak_verb(data, args, rad1, rad2, rad3, "VI")
end

conjugations["VI-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_iii_vi_sound_final_weak_verb(data, args, rad1, rad2, nil, "VI")
end

conjugations["VI-geminate"] = function(data, args, rad1, rad2, rad3)
	make_form_iii_vi_geminate_verb(data, args, rad1, rad2, "VI")
end

-- Make a verbal noun of the general form that applies to forms VII and above.
-- RAD12 is the first consonant cluster (after initial اِ) and RAD34 is the
-- second consonant cluster. RAD5 is the final consonant, or nil for final-weak
-- verbs.
function high_form_verbal_noun(rad12, rad34, rad5)
	return "اِ" .. rad12 .. I .. rad34 .. AA ..
		(rad5 == nil and HAMZA or rad5) .. UNS
end

-- Populate a sound or final-weak verb for any of the various high-numbered
-- augmented forms (form VII and up) that have up to 5 consonants in two
-- clusters in the stem and the same pattern of vowels between.
-- Some of these consonants in certain verb parts are w's, which leads to apparent
-- anomalies in certain stems of these parts, but these anomalies are handled
-- automatically in postprocessing, where we resolve sequences of iwC -> īC,
-- uwC -> ūC, w + sukūn + w -> w + shadda.
--
-- RAD12 is the first consonant cluster (after initial اِ) and RAD34 is the
-- second consonant cluster. RAD5 is the final consonant, or nil for final-weak
-- verbs.
function make_high_form_sound_final_weak_verb(data, args, rad12, rad34, rad5, form)
	local final_weak = rad5 == nil
	local vn = high_form_verbal_noun(rad12, rad34, rad5)

	-- various stem bases
	local nonpast_stem_base = rad12 .. A .. rad34
	local past_stem_base = "اِ" .. nonpast_stem_base
	local ps_past_stem_base = "اُ" .. rad12 .. U .. rad34

	-- make parts
	make_augmented_sound_final_weak_verb(data, args, rad5,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)
end

-- Make form VII sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil.
function make_form_vii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
	make_high_form_sound_final_weak_verb(data, args, "نْ" .. rad1, rad2, rad3, "VII")
end

conjugations["VII-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_vii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
end

conjugations["VII-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_vii_sound_final_weak_verb(data, args, rad1, rad2, nil)
end

conjugations["VII-hollow"] = function(data, args, rad1, rad2, rad3)
	local nrad1 = "نْ" .. rad1
	local vn = high_form_verbal_noun(nrad1, Y, rad3)

	-- various stem bases
	local nonpast_stem_base = nrad1
	local past_stem_base = "اِ" ..nonpast_stem_base
	local ps_past_stem_base = "اُ" .. nrad1

	-- make parts
	make_augmented_hollow_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "VII")
end

conjugations["VII-geminate"] = function(data, args, rad1, rad2, rad3)
	local nrad1 = "نْ" .. rad1
	local vn = high_form_verbal_noun(nrad1, rad2, rad2)

	-- various stem bases
	local nonpast_stem_base = nrad1 .. A
	local past_stem_base = "اِ" .. nonpast_stem_base
	local ps_past_stem_base = "اُ" .. nrad1 .. U

	-- make parts
	make_augmented_geminate_verb(data, args, rad2,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "VII")
end

-- Join the infixed tā' (ت) to the first radical in form VIII verbs. This may
-- cause assimilation of the tā' to the radical or in some cases the radical to
-- the tā'.
function join_ta(rad)
	if rad == W or rad == Y or rad == "ت" then return "تّ"
	elseif rad == "د" then return "دّ"
	elseif rad == "ث" then return "ثّ"
	elseif rad == "ذ" then return "ذّ"
	elseif rad == "ز" then return "زْد"
	elseif rad == "ص" then return "صْط"
	elseif rad == "ض" then return "ضْط"
	elseif rad == "ط" then return "طّ"
	elseif rad == "ظ" then return "ظّ"
	else return rad .. SK .. "ت"
	end
end

-- Return Form VIII verbal noun. RAD3 is nil for final-weak verbs. If RAD1 is
-- hamza, there are two alternatives.
function form_viii_verbal_noun(rad1, rad2, rad3)
	local vn = high_form_verbal_noun(join_ta(rad1), rad2, rad3)
	if rad1 == HAMZA then
		return {vn, high_form_verbal_noun(Y .. T, rad2, rad3)}
	else
		return {vn}
	end
end

-- Make form VIII sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil.
function make_form_viii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
	-- check for irregular verb اِتَّخَذَ
	if axadh_radicals(rad1, rad2, rad3) then
		data.irregular = true
		rad1 = T
	end
	make_high_form_sound_final_weak_verb(data, args, join_ta(rad1), rad2, rad3,
		"VIII")

	-- Add alternative parts if verb is first-hamza. Any duplicates are
	-- removed in get_spans().
	if rad1 == HAMZA then
		local vn = form_viii_verbal_noun(rad1, rad2, rad3)
		local past_stem_base2 = "اِيتَ" .. rad2
		local nonpast_stem_base2 = join_ta(rad1) .. A .. rad2
		local ps_past_stem_base2 = "اُوتُ" .. rad2
		make_augmented_sound_final_weak_verb(data, args, rad3,
			past_stem_base2, nonpast_stem_base2, ps_past_stem_base2, vn, "VIII")
	end
end

conjugations["VIII-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_viii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
end

conjugations["VIII-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_viii_sound_final_weak_verb(data, args, rad1, rad2, nil)
end

conjugations["VIII-hollow"] = function(data, args, rad1, rad2, rad3)
	local vn = form_viii_verbal_noun(rad1, Y, rad3)

	-- various stem bases
	local nonpast_stem_base = join_ta(rad1)
	local past_stem_base = "اِ" .. nonpast_stem_base
	local ps_past_stem_base = "اُ" .. nonpast_stem_base

	-- make parts
	make_augmented_hollow_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "VIII")

	-- Add alternative parts if verb is first-hamza. Any duplicates are
	-- removed in get_spans().
	if rad1 == HAMZA then
		local past_stem_base2 = "اِيت"
		local nonpast_stem_base2 = nonpast_stem_base
		local ps_past_stem_base2 = "اُوت"
		make_augmented_hollow_verb(data, args, rad3,
			past_stem_base2, nonpast_stem_base2, ps_past_stem_base2, vn, "VIII")
	end
end

conjugations["VIII-geminate"] = function(data, args, rad1, rad2, rad3)
	local vn = form_viii_verbal_noun(rad1, rad2, rad2)

	-- various stem bases
	local nonpast_stem_base = join_ta(rad1) .. A
	local past_stem_base = "اِ" .. nonpast_stem_base
	local ps_past_stem_base = "اُ" .. join_ta(rad1) .. U

	-- make parts
	make_augmented_geminate_verb(data, args, rad2,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "VIII")

	-- Add alternative parts if verb is first-hamza. Any duplicates are
	-- removed in get_spans().
	if rad1 == HAMZA then
		local past_stem_base2 = "اِيتَ"
		local nonpast_stem_base2 = nonpast_stem_base
		local ps_past_stem_base2 = "اُوتُ"
		make_augmented_geminate_verb(data, args, rad2,
			past_stem_base2, nonpast_stem_base2, ps_past_stem_base2, vn, "VIII")
	end
end

conjugations["IX-sound"] = function(data, args, rad1, rad2, rad3)
	local ipref = "اِ"
	local vn = ipref .. rad1 .. SK .. rad2 .. I .. rad3 .. AA .. rad3 .. UNS

	-- various stem bases
	local nonpast_stem_base = rad1 .. SK .. rad2 .. A
	local past_stem_base = ipref .. nonpast_stem_base
	local ps_past_stem_base = "اُ" .. rad1 .. SK .. rad2 .. U

	-- make parts
	make_augmented_geminate_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "IX")
end

conjugations["IX-final-weak"] = function(data, args, rad1, rad2, rad3)
	error("FIXME: Not yet implemented")
end

-- Populate a sound or final-weak verb for any of the various high-numbered
-- augmented forms that have 5 consonants in the stem and the same pattern of
-- vowels. Some of these consonants in certain verb parts are w's, which leads to
-- apparent anomalies in certain stems of these parts, but these anomalies
-- are handled automatically in postprocessing, where we resolve sequences of
-- iwC -> īC, uwC -> ūC, w + sukūn + w -> w + shadda.
function make_high5_form_sound_final_weak_verb(data, args, rad1, rad2, rad3, rad4, rad5, form)
	make_high_form_sound_final_weak_verb(data, args, rad1 .. SK .. rad2,
		rad3 .. SK .. rad4, rad5, form)
end

-- Make form X sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil.
function make_form_x_sound_final_weak_verb(data, args, rad1, rad2, rad3)
	make_high5_form_sound_final_weak_verb(data, args, S, T, rad1, rad2, rad3, "X")
	-- check for irregular verb اِسْتَحْيَا (also اِسْتَحَى)
	if hayy_radicals(rad1, rad2, rad3 or Y) then
		data.irregular = true
		-- Add alternative entries to the verbal paradigms. Any duplicates are
		-- removed in get_spans().
		make_high_form_sound_final_weak_verb(data, args, S .. SK .. T, rad1, rad3, "X")
	end
end

conjugations["X-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_x_sound_final_weak_verb(data, args, rad1, rad2, rad3)
end

conjugations["X-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_x_sound_final_weak_verb(data, args, rad1, rad2, nil)
end

conjugations["X-hollow"] = function(data, args, rad1, rad2, rad3)
	local vn = "اِسْتِ" .. rad1 .. AA .. rad3 .. AH .. UNS

	-- various stem bases
	local past_stem_base = "اِسْتَ" .. rad1
	local nonpast_stem_base = "سْتَ" .. rad1
	local ps_past_stem_base = "اُسْتُ" .. rad1

	-- make parts
	make_augmented_hollow_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "X")
end

conjugations["X-geminate"] = function(data, args, rad1, rad2, rad3)
	local vn = "اِسْتِ" .. rad1 .. SK .. rad2 .. AA .. rad2 .. UNS

	-- various stem bases
	local past_stem_base = "اِسْتَ" .. rad1
	local nonpast_stem_base = "سْتَ" .. rad1
	local ps_past_stem_base = "اُسْتُ" .. rad1

	-- make parts
	make_augmented_geminate_verb(data, args, rad2,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "X")
end

conjugations["XI-sound"] = function(data, args, rad1, rad2, rad3)
	local ipref = "اِ"
	local vn = ipref .. rad1 .. SK .. rad2 .. II .. rad3 .. AA .. rad3 .. UNS

	-- various stem bases
	local nonpast_stem_base = rad1 .. SK .. rad2 .. AA
	local past_stem_base = ipref .. nonpast_stem_base
	local ps_past_stem_base = "اُ" .. rad1 .. SK .. rad2 .. UU

	-- make parts
	make_augmented_geminate_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "XI")
end

-- probably no form XI final-weak, since already geminate in form; would behave as XI-sound

-- Make form XII sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil.
function make_form_xii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
	make_high5_form_sound_final_weak_verb(data, args, rad1, rad2, W, rad2, rad3, "XII")
end

conjugations["XII-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_xii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
end

conjugations["XII-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_xii_sound_final_weak_verb(data, args, rad1, rad2, nil)
end

-- Make form XIII sound or final-weak verb. Final-weak verbs are identified
-- by RAD3 = nil.
function make_form_xiii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
	make_high5_form_sound_final_weak_verb(data, args, rad1, rad2, W, W, rad3, "XIII")
end

conjugations["XIII-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_xiii_sound_final_weak_verb(data, args, rad1, rad2, rad3)
end

conjugations["XIII-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_xiii_sound_final_weak_verb(data, args, rad1, rad2, nil)
end

-- Make a form XIV or XV sound or final-weak verb. Last radical appears twice
-- (if`anlala / yaf`anlilu) so if it were w or y you'd get if`anwā / yaf`anwī
-- or if`anyā / yaf`anyī, i.e. we need the identity of the radical, so the
-- normal trick of passing nil as rad3 into these types of functions won't work.
-- Instead we pass the full radical as well as a flag indicating whether the
-- verb is final-weak. The last radical need not be w or y; in fact this is
-- exactly what form XV is about.
function make_form_xiv_xv_sound_final_weak_verb(data, args, rad1, rad2, rad3, final_weak, form)
	local lastrad = not final_weak and rad3 or nil
	make_high5_form_sound_final_weak_verb(data, args, rad1, rad2, N, rad3, lastrad, form)
end

conjugations["XIV-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_xiv_xv_sound_final_weak_verb(data, args, rad1, rad2, rad3, false, "XIV")
end

conjugations["XIV-final-weak"] = function(data, args, rad1, rad2, rad3)
	make_form_xiv_xv_sound_final_weak_verb(data, args, rad1, rad2, rad3, true, "XIV")
end

conjugations["XV-sound"] = function(data, args, rad1, rad2, rad3)
	make_form_xiv_xv_sound_final_weak_verb(data, args, rad1, rad2, rad3, true, "XV")
end

-- probably no form XV final-weak, since already final-weak in form; would behave as XV-sound

-- Make form Iq or IIq sound or final-weak verb. Final-weak verbs are identified
-- by RAD4 = nil. FORM distinguishes Iq from IIq.
function make_form_iq_iiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, rad4, form)
	local final_weak = rad4 == nil
	local vn = form == "IIq" and
		"تَ" .. rad1 .. A .. rad2 .. SK .. rad3 ..
			(final_weak and IN or U .. rad4 .. UNS) or
		rad1 .. A .. rad2 .. SK .. rad3 ..
			(final_weak and AAH or A .. rad4 .. AH) .. UNS
	local ta_pref = form == "IIq" and "تَ" or ""
	local tu_pref = form == "IIq" and "تُ" or ""

	-- various stem bases
	local past_stem_base = ta_pref .. rad1 .. A .. rad2 .. SK .. rad3
	local nonpast_stem_base = past_stem_base
	local ps_past_stem_base = tu_pref .. rad1 .. U .. rad2 .. SK .. rad3

	-- make parts
	make_augmented_sound_final_weak_verb(data, args, rad4,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)
end

conjugations["Iq-sound"] = function(data, args, rad1, rad2, rad3, rad4)
	make_form_iq_iiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, rad4, "Iq")
end

conjugations["Iq-final-weak"] = function(data, args, rad1, rad2, rad3, rad4)
	make_form_iq_iiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, nil, "Iq")
end

conjugations["IIq-sound"] = function(data, args, rad1, rad2, rad3, rad4)
	make_form_iq_iiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, rad4, "IIq")
end

conjugations["IIq-final-weak"] = function(data, args, rad1, rad2, rad3, rad4)
	make_form_iq_iiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, nil, "IIq")
end

-- Make form IIIq sound or final-weak verb. Final-weak verbs are identified
-- by RAD4 = nil.
function make_form_iiiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, rad4)
	make_high5_form_sound_final_weak_verb(data, args, rad1, rad2, N, rad3, rad4, "IIIq")
end

conjugations["IIIq-sound"] = function(data, args, rad1, rad2, rad3, rad4)
	make_form_iiiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, rad4)
end

conjugations["IIIq-final-weak"] = function(data, args, rad1, rad2, rad3, rad4)
	make_form_iiiq_sound_final_weak_verb(data, args, rad1, rad2, rad3, nil)
end

conjugations["IVq-sound"] = function(data, args, rad1, rad2, rad3, rad4)
	local ipref = "اِ"
	local vn = ipref .. rad1 .. SK .. rad2 .. I .. rad3 .. SK .. rad4 .. AA .. rad4 .. UNS

	-- various stem bases
	local past_stem_base = ipref .. rad1 .. SK .. rad2 .. A .. rad3
	local nonpast_stem_base = rad1 .. SK .. rad2 .. A .. rad3
	local ps_past_stem_base = "اُ" .. rad1 .. SK .. rad2 .. U .. rad3

	-- make parts
	make_augmented_geminate_verb(data, args, rad4,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, "IVq")
end

-- probably no form IVq final-weak, since already geminate in form; would behave as IVq-sound

------------------------------------
-- Basic functions to inflect tenses
------------------------------------

-- Implementation of inflect_tense(). See that function. Also used directly
-- to add the imperative, which has only five parts.
function inflect_tense_1(data, tense, prefixes, stems, endings, pnums)
	if prefixes == nil then
		error("For tense '" .. tense .. "', prefixes = nil")
	end
	if stems == nil then
		error("For tense '" .. tense .. "', stems = nil")
	end
	if endings == nil then
		error("For tense '" .. tense .. "', endings = nil")
	end
	if type(prefixes) == "table" and #pnums ~= #prefixes then
		error("For tense '" .. tense .. "', found " .. #prefixes .. " prefixes but expected " .. #pnums)
	end
	if type(stems) == "table" and #pnums ~= #stems then
		error("For tense '" .. tense .. "', found " .. #stems .. " stems but expected " .. #pnums)
	end
	if #pnums ~= #endings then
		error("For tense '" .. tense .. "', found " .. #endings .. " endings but expected " .. #pnums)
	end

	-- First, initialize any nil entries to sequences.
	for i, pnum in ipairs(pnums) do
		if data.forms[pnum .. "-" .. tense] == nil then
			data.forms[pnum .. "-" .. tense] = {}
		end
	end

	-- Now add entries
	for i = 1, #pnums do
		-- Extract endings for this person-number combo
		local ends = endings[i]
		if type(ends) == "string" then ends = {ends} end
		-- Extract prefix for this person-number combo
		local prefix = prefixes
		if type(prefix) == "table" then prefix = prefix[i] end
		-- Extract stem for this person-number combo
		local stem = stems
		if type(stem) == "table" then stem = stem[i] end
		-- Add entries for stem + endings
		for j, ending in ipairs(ends) do
			-- allow some inflections to be skipped; useful for generating
			-- partly irregular inflections
			if prefix ~= "-" and stem ~= "-" then
				local part = prefix .. stem .. ending
				if ine(part) and part ~= "-"
						-- and (not data.impers or pnums[i] == "3sg")
						then
					table.insert(data.forms[pnums[i] .. "-" .. tense], part)
				end
			end
		end
	end
end

-- Add to DATA the inflections for the tense indicated by TENSE (the suffix
-- in the forms names, e.g. 'perf'), formed by combining the PREFIXES
-- (either a single string or a sequence of 13 strings), STEMS
-- (either a single string or a sequence of 13 strings) with the
-- ENDINGS (a sequence of 13 values, each of which is either a string
-- or a sequence of one or more possible endings). If existing
-- inflections already exist, they will be added to, not overridden.
-- If any value of PREFIXES or STEMS is the string "-", then the corresponding
-- inflection will be skipped.
function inflect_tense(data, tense, prefixes, stems, endings)
	local pnums = {"1s", "2sm", "2sf", "3sm", "3sf",
				   "2d", "3dm", "3df",
				   "1p", "2pm", "2pf", "3pm", "3pf"}
	inflect_tense_1(data, tense, prefixes, stems, endings, pnums)
end

-- Like inflect_tense() but for the imperative, which has only five parts
-- instead of 13.
function inflect_tense_impr(data, stems, endings)
	local pnums = {"2sm", "2sf", "2d", "2pm", "2pf"}
	if data.noimp then
		endings = {{}, {}, {}, {}, {}}
	end
	inflect_tense_1(data, "impr", "", stems, endings, pnums)
end

-- Add VALUE (a string or array) to the end of any entries in DATA.forms[NAME],
-- initializing it to an empty array if needed.
function insert_part(data, name, value)
	if data.forms[name] == nil then
		data.forms[name] = {}
	end
	
	if type(value) == "table" then
		data.forms[name] = value
	else
		table.insert(data.forms[name], value)
	end
end

-- Insert verbal noun VN into DATA.forms["vn"], but allow it to be overridden by
-- ARGS["vn"].
function insert_verbal_noun(data, args, vn)
	local vns = args["vn"] and rsplit(args["vn"], "[,،]") or vn
	if type(vns) ~= "table" then
		vns = {vns}
	end
	
	vns.ids = {}
	
	for i = 1, #vns do
		local id = args["vn-id" .. i]
		
		if id then
			vns.ids[i] = id
		end
	end

	-------------------- Begin verbal-noun i3rab tracking code ---------------
	-- Examples of what you can find by looking at what links to the given
	-- pages:
	--
	-- Template:tracking/ar-verb/vn/i3rab/un (pages with verbal nouns with -un
	--   i3rab, whether explicitly specified, i.e. using vn=, or auto-generated,
	--   as is normal for augmented forms)
	-- Template:tracking/ar-verb/explicit-vn/i3rab/u (pages with explicitly
	--   specified verbal nouns with -u i3rab)
	-- Template:tracking/ar-verb/explicit-vn/i3rab/an-tall (pages with
	--   explicitly specified verbal nouns with tall-alif -an i3rab)
	-- Template:tracking/ar-verb/auto-vn/i3rab (pages with auto-generated verbal
	--   nouns with any sort of i3rab)
	-- Template:tracking/ar-verb/vn/no-i3rab (pages with verbal nouns without
	--   i3rab)
	-- Template:tracking/ar-verb/vn/would-be-decl/di (pages with verbal nouns
	--   that would be detected as diptote without explicit i3rab)
	function vntrack(pagesuff)
		track("vn/" .. pagesuff)
		if args["vn"] then
			track("explicit-vn/" .. pagesuff)
		else
			track("auto-vn/" .. pagesuff)
		end
	end

	function track_i3rab(entry, arabic, tr)
		if rfind(entry, arabic .. "$") then
			vntrack("i3rab")
			vntrack("i3rab/" .. tr)
		end
	end

	for _, entry in ipairs(vns) do
		-- Need to do this else we will have problems with VN's whose stem ends
		-- in shadda.
		entry = reorder_shadda(entry)
		track_i3rab(entry, UN, "un")
		track_i3rab(entry, U, "u")
		track_i3rab(entry, IN, "in")
		track_i3rab(entry, I, "i")
		track_i3rab(entry, AN .. "[" .. ALIF .. AMAQ .. "]", "an")
		track_i3rab(entry, AN .. ALIF, "an-tall")
		track_i3rab(entry, A, "a")
		track_i3rab(entry, SK, "sk")
		if not rfind(entry, "[" .. A .. I .. U .. AN .. IN .. UN .. DAGGER_ALIF .. "]$") and
				not rfind(entry, AN .. "[" .. ALIF .. AMAQ .. "]") then
			vntrack("no-i3rab")
		end
		entry = rsub(entry, UNU .. "?$", "")
		-- Figure out what the decltype would be without any explicit -un
		-- or -u added, to see whether there are any nouns that would be
		-- detected as diptotes, e.g. of the فَعْلَان pattern.
		local decltype = ar_nominals.detect_type(entry, false, "sg", "noun")
		vntrack("would-be-decl/" .. decltype)
	end
	-------------------- End verbal-noun i3rab tracking code ---------------

	if no_nominal_i3rab then
		vns_no_i3rab = {}
		for _, entry in ipairs(vns) do
			entry = reorder_shadda(entry)
			entry = rsub(entry, UNU .. "?$", "")
			table.insert(vns_no_i3rab, entry)
		end
		if vns.ids then
			vns_no_i3rab.ids = vns.ids
		end
		insert_part(data, "vn", vns_no_i3rab)
	else
		insert_part(data, "vn", vns)
	end
end

--------------------------------------
-- functions to inflect the past tense
--------------------------------------

--generate past verbs using specified vowel and consonant stems; works for
--sound, assimilated, hollow, and geminate verbs, active and passive
function past_2stem_conj(data, tense, v_stem, c_stem)
	inflect_tense(data, tense, "", {
		-- singular
		c_stem, c_stem, c_stem, v_stem, v_stem,
		--dual
		c_stem, v_stem, v_stem,
		-- plural
		c_stem, c_stem, c_stem, v_stem, c_stem
	}, past_endings)
end

--generate past verbs using single specified stem; works for sound and
--assimilated verbs, active and passive
function past_1stem_conj(data, tense, stem)
	past_2stem_conj(data, tense, stem, stem)
end

---------------------------------------
-- functions to inflect non-past tenses
---------------------------------------

-- Generate non-past conjugation, with two stems, for vowel-initial and
-- consonant-initial endings, respectively. Useful for active and passive;
-- for all forms; for all weaknesses (sound, assimilated, hollow, final-weak
-- and geminate) and for all types of non-past (indicative, subjunctive,
-- jussive) except for the imperative. (There is a separate function below
-- for geminate jussives because they have three alternants.) Both stems may
-- be the same, e.g. for sound verbs.
--
-- PREFIXES will generally be either "a" (= 'nonpast_prefixes_a', for active
-- forms I and V - X) or "u" (= 'nonpast_prefixes_u', for active forms II - IV
-- and Iq and all passive forms). Otherwise, it should be either a single string
-- (often "") or an array (table) of 13 items. ENDINGS should similarly be an
-- array of 13 items. If ENDINGS is nil or omitted, infer the endings from
-- the tense. If JUSSIVE is true, or ENDINGS is nil and TENSE indicatives
-- jussive, use the jussive pattern of vowel/consonant stems (different from the
-- normal ones).
function nonpast_2stem_conj(data, tense, prefixes, v_stem, c_stem, endings, jussive)
	if prefixes == "a" then prefixes = nonpast_prefixes_a
	elseif prefixes == "u" then prefixes = nonpast_prefixes_u
	end
	if endings == nil then
		if tense == "impf" or tense == "ps-impf" then
			endings = indic_endings
		elseif tense == "subj" or tense == "ps-subj" then
			endings = subj_endings
		elseif tense == "juss" or tense == "ps-juss" then
			jussive = true
			endings = juss_endings
		else
			error("Unrecognized tense '" .. tense .."'")
		end
	end
	if not jussive then
		inflect_tense(data, tense, prefixes, {
			-- singular
			v_stem, v_stem, v_stem, v_stem, v_stem,
			--dual
			v_stem, v_stem, v_stem,
			-- plural
			v_stem, v_stem, c_stem, v_stem, c_stem
		}, endings)
	else
		inflect_tense(data, tense, prefixes, {
			-- singular
			-- 'adlul, tadlul, tadullī, yadlul, tadlul
			c_stem, c_stem, v_stem, c_stem, c_stem,
			--dual
			-- tadullā, yadullā, tadullā
			v_stem, v_stem, v_stem,
			-- plural
			-- nadlul, tadullū, tadlulna, yadullū, yadlulna
			c_stem, v_stem, c_stem, v_stem, c_stem
		}, endings)
	end
end

-- Generate non-past conjugation with one stem (no distinct stems for
-- vowel-initial and consonant-initial endings). See nonpast_2stem_conj().
function nonpast_1stem_conj(data, tense, prefixes, stem, endings, jussive)
	nonpast_2stem_conj(data, tense, prefixes, stem, stem, endings, jussive)
end

-- Generate active/passive jussive geminative. There are three alternants, two
-- with terminations -a and -i and one in a null termination with a distinct
-- pattern of vowel/consonant stem usage. See nonpast_2stem_conj() for a
-- description of the arguments.
function jussive_gem_conj(data, tense, prefixes, v_stem, c_stem)
	-- alternative in -a
	nonpast_2stem_conj(data, tense, prefixes, v_stem, c_stem, juss_endings_alt_a)
	-- alternative in -i
	nonpast_2stem_conj(data, tense, prefixes, v_stem, c_stem, juss_endings_alt_i)
	-- alternative in -null; requires different combination of v_stem and
	-- c_stem since the null endings require the c_stem (e.g. "tadlul" here)
	-- whereas the corresponding endings above in -a or -i require the v_stem
	-- (e.g. "tadulla, tadulli" above)
	nonpast_2stem_conj(data, tense, prefixes, v_stem, c_stem, juss_endings, "jussive")
end

--------------------------------------
-- functions to inflect the imperative
--------------------------------------

-- generate imperative parts for sound or assimilated verbs
function make_1stem_imperative(data, stem)
	inflect_tense_impr(data, stem, impr_endings)
end

-- generate imperative parts for two-stem verbs (hollow or geminate)
function make_2stem_imperative(data, v_stem, c_stem)
	inflect_tense_impr(data,
		{c_stem, v_stem, v_stem, v_stem, c_stem}, impr_endings)
end

-- generate imperative parts for geminate verbs form I (also IV, VII, VIII, X)
function make_gem_imperative(data, v_stem, c_stem)
	inflect_tense_impr(data,
		{v_stem, v_stem, v_stem, v_stem, c_stem}, impr_endings_alt_a)
	inflect_tense_impr(data,
		{v_stem, v_stem, v_stem, v_stem, c_stem}, impr_endings_alt_i)
	make_2stem_imperative(data, v_stem, c_stem)
end

------------------------------------
-- functions to inflect entire verbs
------------------------------------

-- generate finite parts of a sound verb (also works for assimilated verbs)
-- from five stems (past and non-past, active and passive, plus imperative)
-- plus the prefix vowel in the active non-past ("a" or "u")
function make_sound_verb(data, past_stem, ps_past_stem, nonpast_stem,
		ps_nonpast_stem, imper_stem, prefix_vowel)
	past_1stem_conj(data, "perf", past_stem)
	past_1stem_conj(data, "ps-perf", ps_past_stem)
	nonpast_1stem_conj(data, "impf", prefix_vowel, nonpast_stem)
	nonpast_1stem_conj(data, "subj", prefix_vowel, nonpast_stem)
	nonpast_1stem_conj(data, "juss", prefix_vowel, nonpast_stem)
	nonpast_1stem_conj(data, "ps-impf", "u", ps_nonpast_stem)
	nonpast_1stem_conj(data, "ps-subj", "u", ps_nonpast_stem)
	nonpast_1stem_conj(data, "ps-juss", "u", ps_nonpast_stem)
	make_1stem_imperative(data, imper_stem)
end

-- generate finite parts of a final-weak verb from five stems (past and
-- non-past, active and passive, plus imperative), five sets of
-- suffixes (past, non-past indicative/subjunctive/jussive, imperative)
-- and the prefix vowel in the active non-past ("a" or "u")
function make_final_weak_verb(data, past_stem, ps_past_stem, nonpast_stem,
		ps_nonpast_stem, imper_stem, past_suffs, indic_suffs,
		subj_suffs, juss_suffs, impr_suffs, prefix_vowel)
	inflect_tense(data, "perf", "", past_stem, past_suffs)
	inflect_tense(data, "ps-perf", "", ps_past_stem, past_endings_ii)
	nonpast_1stem_conj(data, "impf", prefix_vowel, nonpast_stem, indic_suffs)
	nonpast_1stem_conj(data, "subj", prefix_vowel, nonpast_stem, subj_suffs)
	nonpast_1stem_conj(data, "juss", prefix_vowel, nonpast_stem, juss_suffs)
	nonpast_1stem_conj(data, "ps-impf", "u", ps_nonpast_stem, indic_endings_aa)
	nonpast_1stem_conj(data, "ps-subj", "u", ps_nonpast_stem, subj_endings_aa)
	nonpast_1stem_conj(data, "ps-juss", "u", ps_nonpast_stem, juss_endings_aa)
	inflect_tense_impr(data, imper_stem, impr_suffs)
end

-- generate finite parts of an augmented (form II+) final-weak verb from five
-- stems (past and non-past, active and passive, plus imperative) plus the
-- prefix vowel in the active non-past ("a" or "u") and a flag indicating if
-- behave like a form V/VI verb in taking non-past endings in -ā instead of -ī
function make_augmented_final_weak_verb(data, past_stem, ps_past_stem,
		nonpast_stem, ps_nonpast_stem, imper_stem, prefix_vowel, form56)
	make_final_weak_verb(data, past_stem, ps_past_stem, nonpast_stem,
		ps_nonpast_stem, imper_stem, past_endings_ay,
		form56 and indic_endings_aa or indic_endings_ii,
		form56 and subj_endings_aa or subj_endings_ii,
		form56 and juss_endings_aa or juss_endings_ii,
		form56 and impr_endings_aa or impr_endings_ii,
		prefix_vowel)
end

-- generate finite parts of an augmented (form II+) sound or final-weak verb,
-- given the following:
--
-- DATA, ARGS = arguments from conjugation function
-- RAD3 = last radical; should be nil for final-weak verb
-- PAST_STEM_BASE = active past stem minus last syllable (= -al or -ā)
-- NONPAST_STEM_BASE = non-past stem minus last syllable (= -al/-il or -ā/-ī)
-- PS_PAST_STEM_BASE = passive past stem minus last syllable (= -il or -ī)
-- FORM -- form of verb (II to XV, Iq - IVq)
-- VN = verbal noun
function make_augmented_sound_final_weak_verb(data, args, rad3,
		past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)

	insert_verbal_noun(data, args, vn)

	local final_weak = rad3 == nil
	local prefix_vowel = prefix_vowel_from_form(form)
	local form56 = form_nonpast_a_vowel(form)
	local a_base_suffix = final_weak and "" or A .. rad3
	local i_base_suffix = final_weak and "" or I .. rad3

	-- past and non-past stems, active and passive
	local past_stem = past_stem_base .. a_base_suffix
	-- In forms 5 and 6, non-past has /a/ as last stem vowel in the non-past
	-- in both active and passive, but /i/ in the active participle and /a/
	-- in the passive participle. Elsewhere, consistent /i/ in active non-past
	-- and participle, consistent /a/ in passive non-past and participle.
	-- Hence, forms 5 and 6 differ only in the non-past active (but not
	-- active participle), so we have to split the finite non-past stem and
	-- active participle stem.
	local nonpast_stem = nonpast_stem_base ..
		(form56 and a_base_suffix or i_base_suffix)
	local ap_stem = nonpast_stem_base .. i_base_suffix
	local ps_past_stem = ps_past_stem_base .. i_base_suffix
	local ps_nonpast_stem = nonpast_stem_base .. a_base_suffix
	-- imperative stem
	local imper_stem = past_stem_base ..
		(form56 and a_base_suffix or i_base_suffix)

	-- make parts
	if final_weak then
		make_augmented_final_weak_verb(data, past_stem, ps_past_stem, nonpast_stem,
			ps_nonpast_stem, imper_stem, prefix_vowel, form56)
	else
		make_sound_verb(data, past_stem, ps_past_stem, nonpast_stem,
			ps_nonpast_stem, imper_stem, prefix_vowel)
	end

	-- active and passive participle
	if final_weak then
		insert_part(data, "ap", MU .. ap_stem .. IN)
		insert_part(data, "pp", MU .. ps_nonpast_stem .. AN .. AMAQ)
	else
		insert_part(data, "ap", MU .. ap_stem .. UNS)
		insert_part(data, "pp", MU .. ps_nonpast_stem .. UNS)
	end
end

-- generate finite parts of a hollow or geminate verb from ten stems (vowel and
-- consonant stems for each of past and non-past, active and passive, plus
-- imperative) plus the prefix vowel in the active non-past ("a" or "u"), plus
-- a flag indicating if we are a geminate verb
function make_hollow_geminate_verb(data, past_v_stem, past_c_stem, ps_past_v_stem,
	ps_past_c_stem, nonpast_v_stem, nonpast_c_stem, ps_nonpast_v_stem,
	ps_nonpast_c_stem, imper_v_stem, imper_c_stem, prefix_vowel, geminate)
	past_2stem_conj(data, "perf", past_v_stem, past_c_stem)
	past_2stem_conj(data, "ps-perf", ps_past_v_stem, ps_past_c_stem)
	nonpast_2stem_conj(data, "impf", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
	nonpast_2stem_conj(data, "subj", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
	nonpast_2stem_conj(data, "ps-impf", "u", ps_nonpast_v_stem, ps_nonpast_c_stem)
	nonpast_2stem_conj(data, "ps-subj", "u", ps_nonpast_v_stem, ps_nonpast_c_stem)
	if geminate then
		jussive_gem_conj(data, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
		jussive_gem_conj(data, "ps-juss", "u", ps_nonpast_v_stem, ps_nonpast_c_stem)
		make_gem_imperative(data, imper_v_stem, imper_c_stem)
	else
		nonpast_2stem_conj(data, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
		nonpast_2stem_conj(data, "ps-juss", "u", ps_nonpast_v_stem, ps_nonpast_c_stem)
		make_2stem_imperative(data, imper_v_stem, imper_c_stem)
	end
end

-- generate finite parts of an augmented (form II+) hollow verb,
-- given the following:
--
-- DATA, ARGS = arguments from conjugation function
-- RAD3 = last radical (after the hollowness)
-- PAST_STEM_BASE = invariable part of active past stem
-- NONPAST_STEM_BASE = invariable part of non-past stem
-- PS_PAST_STEM_BASE = invariable part of passive past stem
-- VN = verbal noun
-- FORM = the verb form ("IV", "VII", "VIII", "X")
function make_augmented_hollow_verb(data, args, rad3,
	past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)
	insert_verbal_noun(data, args, vn)

	local form410 = form == "IV" or form == "X"
	local prefix_vowel = prefix_vowel_from_form(form)

	local a_base_suffix_v, a_base_suffix_c
	local i_base_suffix_v, i_base_suffix_c

	a_base_suffix_v = AA .. rad3         -- 'af-āl-a, inf-āl-a
	a_base_suffix_c = A .. rad3      -- 'af-al-tu, inf-al-tu
	i_base_suffix_v = II .. rad3         -- 'uf-īl-a, unf-īl-a
	i_base_suffix_c = I .. rad3      -- 'uf-il-tu, unf-il-tu

	-- past and non-past stems, active and passive, for vowel-initial and
	-- consonant-initial endings
	local past_v_stem = past_stem_base .. a_base_suffix_v
	local past_c_stem = past_stem_base .. a_base_suffix_c
	-- yu-f-īl-u, ya-staf-īl-u but yanf-āl-u, yaft-āl-u
	local nonpast_v_stem = nonpast_stem_base ..
		(form410 and i_base_suffix_v or a_base_suffix_v)
	local nonpast_c_stem = nonpast_stem_base ..
		(form410 and i_base_suffix_c or a_base_suffix_c)
	local ps_past_v_stem = ps_past_stem_base .. i_base_suffix_v
	local ps_past_c_stem = ps_past_stem_base .. i_base_suffix_c
	local ps_nonpast_v_stem = nonpast_stem_base .. a_base_suffix_v
	local ps_nonpast_c_stem = nonpast_stem_base .. a_base_suffix_c

	-- imperative stem
	local imper_v_stem = past_stem_base ..
		(form410 and i_base_suffix_v or a_base_suffix_v)
	local imper_c_stem = past_stem_base ..
		(form410 and i_base_suffix_c or a_base_suffix_c)

	-- make parts
	make_hollow_geminate_verb(data, past_v_stem, past_c_stem, ps_past_v_stem,
		ps_past_c_stem, nonpast_v_stem, nonpast_c_stem, ps_nonpast_v_stem,
		ps_nonpast_c_stem, imper_v_stem, imper_c_stem, prefix_vowel, false)

	-- active participle
	insert_part(data, "ap", MU .. nonpast_v_stem .. UNS)
	-- passive participle
	insert_part(data, "pp", MU .. ps_nonpast_v_stem .. UNS)
end

-- generate finite parts of an augmented (form II+) geminate verb,
-- given the following:
--
-- DATA, ARGS = arguments from conjugation function
-- RAD3 = last radical (the one that gets geminated)
-- PAST_STEM_BASE = invariable part of active past stem; this and the stem
--   bases below will end with a consonant for forms IV, X, IVq, and a
--   short vowel for the others
-- NONPAST_STEM_BASE = invariable part of non-past stem
-- PS_PAST_STEM_BASE = invariable part of passive past stem
-- VN = verbal noun
-- FORM = the verb form ("III", "IV", "VI", "VII", "VIII", "IX", "X", "IVq")
function make_augmented_geminate_verb(data, args, rad3,
	past_stem_base, nonpast_stem_base, ps_past_stem_base, vn, form)
	insert_verbal_noun(data, args, vn)

	local prefix_vowel = prefix_vowel_from_form(form)

	local a_base_suffix_v, a_base_suffix_c
	local i_base_suffix_v, i_base_suffix_c

	if form == "IV" or form == "X" or form == "IVq" then
		a_base_suffix_v = A .. rad3 .. SH         -- 'af-all
		a_base_suffix_c = SK .. rad3 .. A .. rad3  -- 'af-lal
		i_base_suffix_v = I .. rad3 .. SH         -- yuf-ill
		i_base_suffix_c = SK .. rad3 .. I .. rad3  -- yuf-lil
	else
		a_base_suffix_v = rad3 .. SH         -- fā-ll, infa-ll
		a_base_suffix_c = rad3 .. A .. rad3  -- fā-lal, infa-lal
		i_base_suffix_v = rad3 .. SH         -- yufā-ll, yanfa-ll
		i_base_suffix_c = rad3 .. I .. rad3  -- yufā-lil, yanfa-lil
	end

	-- past and non-past stems, active and passive, for vowel-initial and
	-- consonant-initial endings
	local past_v_stem = past_stem_base .. a_base_suffix_v
	local past_c_stem = past_stem_base .. a_base_suffix_c
	local nonpast_v_stem = nonpast_stem_base ..
		(form_nonpast_a_vowel(form) and a_base_suffix_v or i_base_suffix_v)
	local nonpast_c_stem = nonpast_stem_base ..
		(form_nonpast_a_vowel(form) and a_base_suffix_c or i_base_suffix_c)
	-- form III and VI passive past do not have contracted parts, only
	-- uncontracted parts, which are added separately by those functions
	local ps_past_v_stem = (form == "III" or form == "VI") and "-" or
		ps_past_stem_base .. i_base_suffix_v
	local ps_past_c_stem = ps_past_stem_base .. i_base_suffix_c
	local ps_nonpast_v_stem = nonpast_stem_base .. a_base_suffix_v
	local ps_nonpast_c_stem = nonpast_stem_base .. a_base_suffix_c

	-- imperative stem
	local imper_v_stem = past_stem_base ..
		(form_nonpast_a_vowel(form) and a_base_suffix_v or i_base_suffix_v)
	local imper_c_stem = past_stem_base ..
		(form_nonpast_a_vowel(form) and a_base_suffix_c or i_base_suffix_c)

	-- make parts
	make_hollow_geminate_verb(data, past_v_stem, past_c_stem, ps_past_v_stem,
		ps_past_c_stem, nonpast_v_stem, nonpast_c_stem, ps_nonpast_v_stem,
		ps_nonpast_c_stem, imper_v_stem, imper_c_stem, prefix_vowel,
		"geminate")

	-- active participle
	insert_part(data, "ap", MU .. nonpast_v_stem .. UNS)
	-- passive participle
	insert_part(data, "pp", MU .. ps_nonpast_v_stem .. UNS)
end

----------------------------------------
-- functions to create inflection tables
----------------------------------------

-- array of substitutions; each element is a 2-entry array FROM, TO; do it
-- this way so the concatenations only get evaluated once
local postprocess_subs = {
	-- reorder short-vowel + shadda -> shadda + short-vowel for easier processing
	{"(" .. AIU .. ")" .. SH, SH .. "%1"},

	----------same letter separated by sukūn should instead use shadda---------
	------------happens e.g. in kun-nā "we were".-----------------
	{"(.)" .. SK .. "%1", "%1" .. SH},

	---------------------------- assimilated verbs ----------------------------
	-- iw, iy -> ī (assimilated verbs)
	{I .. W .. SK, II},
	{I .. Y .. SK, II},
	-- uw, uy -> ū (assimilated verbs)
	{U .. W .. SK, UU},
	{U .. Y .. SK, UU},

    -------------- final -yā uses tall alif not alif maqṣūra ------------------
	{"(" .. Y ..  SH .. "?" .. A .. ")" .. AMAQ, "%1" .. ALIF},

	----------------------- handle hamza assimilation -------------------------
	-- initial hamza + short-vowel + hamza + sukūn -> hamza + long vowel
	{HAMZA .. A .. HAMZA .. SK, HAMZA .. A .. ALIF},
	{HAMZA .. I .. HAMZA .. SK, HAMZA .. I .. Y},
	{HAMZA .. U .. HAMZA .. SK, HAMZA .. U .. W}
}

-- Post-process verb parts to eliminate phonological anomalies. Many of the changes,
-- particularly the tricky ones, involve converting hamza to have the proper
-- seat. The rules for this are complicated and are documented on the
-- [[w:Hamza]] Wikipedia page. In some cases there are alternatives allowed,
-- and we handle them below by returning multiple possibilities.
function postprocess_term(term)
	-- if term is regular hyphen/dash, ndash or mdash, return mdash
	if term == "-" or term == "–" or term == "—" then
		return {"—"} -- mdash
	elseif term == "?" then
		return {"?"}
	end
	-- do the main post-processing, based on the pattern substitutions in
	-- postprocess_subs
	for _, sub in ipairs(postprocess_subs) do
		term = rsub(term, sub[1], sub[2])
	end

	if not rfind(term, HAMZA) then
		return {term}
	end
	term = rsub(term, HAMZA, HAMZA_PH)
	return ar_utilities.process_hamza(term)
end

-- For each paradigm part, postprocess the entries, remove duplicates and
-- return the set of Arabic and transliterated Latin entries as two return
-- values.
function get_spans(part)
	if type(part) == "string" then
		part = {part}
	end
	local part_nondup = {}
	-- for each entry, postprocess it, which may potentially return
	-- multiple entries; insert each into an array, checking and
	-- omitting duplicates
	for _, entry in ipairs(part) do
		for _, e in ipairs(postprocess_term(entry)) do
			insert_if_not(part_nondup, e)
		end
	end
	-- convert each individual entry into Arabic and Latin span
	local arabic_spans = {}
	local latin_spans = {}
	for _, entry in ipairs(part_nondup) do
		table.insert(arabic_spans, entry)
		if entry ~= "—" and entry ~= "?" then
			-- multiple Arabic entries may map to the same Latin entry
			-- (happens particularly with variant ways of spelling hamza)
			insert_if_not(latin_spans, ar_translit.tr(entry, nil, nil, nil))
		end
	end
	return arabic_spans, latin_spans
end

-- Make the conjugation table. Called from export.show().
function make_table(data, title, form, intrans)

	local forms = data.forms
	local arabic_spans_3sm_perf, _
	if data.passive == "only" or data.passive == "only-impers" then
		arabic_spans_3sm_perf, _ = get_spans(forms["3sm-ps-perf"])
	else
		arabic_spans_3sm_perf, _ = get_spans(forms["3sm-perf"])
	end
	-- convert Arabic terms to spans
	for i, entry in ipairs(arabic_spans_3sm_perf) do
		arabic_spans_3sm_perf[i] = "<b lang=\"ar\" class=\"Arab\">" .. entry .. "</b>"
	end
	-- concatenate spans
	local part_3sm_perf = '<div style="display: inline-block">' ..
		table.concat(arabic_spans_3sm_perf, " <small style=\"color: #888\">or</small> ") .. "</div>"

	-- compute # of verbal nouns before we collapse them
	local num_vns = type(forms["vn"]) == "table" and #forms["vn"] or 1

	-- also extract list of verbal nouns and preceding text "verbal noun" or
	-- "verbal nouns", for the conjugation table title
	local vn_spans, _ = get_spans(forms["vn"])
	-- convert verbal nouns to spans
	for i, entry in ipairs(vn_spans) do
		vn_spans[i] = '<span lang="ar" class="Arab" style="font-weight: normal">' .. entry .. '</span>'
	end
	local vn_list = #vn_spans == 0 and "?" or
		table.concat(vn_spans, " <small style=\"color: #888\">or</small> ")
	local vn_prefix = #vn_spans > 1 and "verbal nouns" or "verbal noun"
	local vn_text = vn_prefix .. " " .. vn_list

	-- construct conjugation table title
	local title = 'Conjugation of ' .. part_3sm_perf
		.. (form == "I" and " (" .. title .. ", " .. vn_text .. ")" or " (" .. title .. ")")

	-- Format and add transliterations to all parts
	for key, part in pairs(forms) do
		-- check for empty array, size-one array holding a dash or ?
		if #part > 0 then
			local arabic_spans, latin_spans = get_spans(part)
			-- convert Arabic terms to links
			for i, entry in ipairs(arabic_spans) do
				local id
				if part.ids then
					id = part.ids[i]
				end
				
				arabic_spans[i] = links(entry, nil, id)
			end
			
			for i, translit in ipairs(latin_spans) do
				latin_spans[i] = require("Module:script utilities").tag_translit(translit, lang, "default")
			end
			
			-- concatenate spans
			forms[key] = '<div style="display: inline-block">' .. table.concat(arabic_spans, " <small style=\"color: #888\">or</small> ") .. "</div>" .. "<br/>" ..
				"<span style=\"color: #888\">" .. table.concat(latin_spans, " <small>or</small> ") .. "</span>"
		-- if no verbal nouns, it's normally because they're unknown or
		-- unspecified rather than non-existent. For an actually non-existent
		-- verbal noun, put a hyphen or mdash explicitly as the value of the
		-- paradigm part, and it will be handled appropriately (no attempt to
		-- transliterate).
		elseif key == "vn" then
			forms[key] = "?"
		else
			forms[key] = "—"
		end
	end

	local text = [=[<div class="NavFrame ar-conj">
<div class="NavHead" style="height:2.5em">]=] .. title  .. [=[</div>
<div class="NavContent">

{| class="inflection-table"
|-
! colspan="6" class="nonfinite-header" | verbal noun]=] .. (num_vns > 1 and "s" or "") .. "<br />" .. tag_text(num_vns > 1 and "الْمَصَادِر" or "الْمَصْدَر") .. [=[

| colspan="7" | {{{vn}}}
]=]

	if data.passive ~= "only" and data.passive ~= "only-impers" then
		text = text .. [=[
|-
! colspan="6" class="nonfinite-header" | active participle<br />{{{اِسْم الْفَاعِل}}}
| colspan="7" | {{{ap}}}
]=]
	end

	if data.passive then
		text = text .. [=[
|-
! colspan="6" class="nonfinite-header" | passive participle<br />{{{اِسْم الْمَفْعُول}}}
| colspan="7" | {{{pp}}}
]=]
	end

	if data.passive ~= "only" and data.passive ~= "only-impers" then
		text = text .. [=[
|-
! colspan="12" class="voice-header" | active voice<br />{{{الْفِعْل الْمَعْلُوم}}}
|-
! colspan="2" class="empty-header" | 
! colspan="3" class="number-header" | singular<br />{{{الْمُفْرَد}}}
! rowspan="12" class="divider" | 
! colspan="2" class="number-header" | dual<br />{{{الْمُثَنَّى}}}
! rowspan="12" class="divider" | 
! colspan="3" class="number-header" | plural<br />{{{الْجَمْع}}}
|-
! colspan="2" class="empty-header" | 
! class="person-header" | 1<sup>st</sup> person<br />{{{الْمُتَكَلِّم}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
! class="person-header" | 1<sup>st</sup> person<br />{{{الْمُتَكَلِّم}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
|-
! rowspan="2" class="tam-header" | past (perfect) indicative<br />{{{الْمَاضِي}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-perf}}}
| {{{2sm-perf}}}
| {{{3sm-perf}}}
| rowspan="2" | {{{2d-perf}}}
| {{{3dm-perf}}}
| rowspan="2" | {{{1p-perf}}}
| {{{2pm-perf}}}
| {{{3pm-perf}}}
|-
! class="gender-header" | f
| {{{2sf-perf}}}
| {{{3sf-perf}}}
| {{{3df-perf}}}
| {{{2pf-perf}}}
| {{{3pf-perf}}}
|-
! rowspan="2" class="tam-header" | non-past (imperfect) indicative<br />{{{الْمُضَارِع الْمَرْفُوع}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-impf}}}
| {{{2sm-impf}}}
| {{{3sm-impf}}}
| rowspan="2" | {{{2d-impf}}}
| {{{3dm-impf}}}
| rowspan="2" | {{{1p-impf}}}
| {{{2pm-impf}}}
| {{{3pm-impf}}}
|-
! class="gender-header" | f
| {{{2sf-impf}}}
| {{{3sf-impf}}}
| {{{3df-impf}}}
| {{{2pf-impf}}}
| {{{3pf-impf}}}
|-
! rowspan="2" class="tam-header" | subjunctive<br />{{{الْمُضَارِع الْمَنْصُوب}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-subj}}}
| {{{2sm-subj}}}
| {{{3sm-subj}}}
| rowspan="2" | {{{2d-subj}}}
| {{{3dm-subj}}}
| rowspan="2" | {{{1p-subj}}}
| {{{2pm-subj}}}
| {{{3pm-subj}}}
|-
! class="gender-header" | f
| {{{2sf-subj}}}
| {{{3sf-subj}}}
| {{{3df-subj}}}
| {{{2pf-subj}}}
| {{{3pf-subj}}}
|-
! rowspan="2" class="tam-header" | jussive<br />{{{الْمُضَارِع الْمَجْزُوم}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-juss}}}
| {{{2sm-juss}}}
| {{{3sm-juss}}}
| rowspan="2" | {{{2d-juss}}}
| {{{3dm-juss}}}
| rowspan="2" | {{{1p-juss}}}
| {{{2pm-juss}}}
| {{{3pm-juss}}}
|-
! class="gender-header" | f
| {{{2sf-juss}}}
| {{{3sf-juss}}}
| {{{3df-juss}}}
| {{{2pf-juss}}}
| {{{3pf-juss}}}
|-
! rowspan="2" class="tam-header" | imperative<br />{{{الْأَمْر}}}
! class="gender-header" | m
| rowspan="2" | 
| {{{2sm-impr}}}
| rowspan="2" | 
| rowspan="2" | {{{2d-impr}}}
| rowspan="2" | 
| rowspan="2" | 
| {{{2pm-impr}}}
| rowspan="2" | 
|-
! class="gender-header" | f
| {{{2sf-impr}}}
| {{{2pf-impr}}}
]=]
	end

	if data.passive == "impers" or data.passive == "only-impers" then
		text = text .. [=[
|-
! colspan="12" class="voice-header" | passive voice<br />{{{الْفِعْل الْمَجْهُول}}}
|-
| colspan="2" class="empty-header" | 
! colspan="3" class="number-header" | singular<br />{{{الْمُفْرَد}}}
| rowspan="10" class="divider" | 
! colspan="2" class="number-header" | dual<br />{{{الْمُثَنَّى}}}
| rowspan="10" class="divider" | 
! colspan="3" class="number-header" | plural<br />{{{الْجَمْع}}}
|-
| colspan="2" class="empty-header" | 
! class="person-header" | 1<sup>st</sup> person<br />{{{الْمُتَكَلِّم}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
! class="person-header" | 1<sup>st</sup> person<br />{{{الْمُتَكَلِّم}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
|-
! rowspan="2" class="tam-header" | past (perfect) indicative<br />{{{الْمَاضِي}}}
! class="gender-header" | m
| rowspan="2" | &mdash;
| &mdash;
| {{{3sm-ps-perf}}}
| rowspan="2" | &mdash;
| &mdash;
| rowspan="2" | &mdash;
| &mdash;
| &mdash;
|-
! class="gender-header" | f
| &mdash;
| &mdash;
| &mdash;
| &mdash;
| &mdash;
|-
! rowspan="2" class="tam-header" | non-past (imperfect) indicative<br />{{{الْمُضَارِع الْمَرْفُوع}}}
! class="gender-header" | m
| rowspan="2" | &mdash;
| &mdash;
| {{{3sm-ps-impf}}}
| rowspan="2" | &mdash;
| &mdash;
| rowspan="2" | &mdash;
| &mdash;
| &mdash;
|-
! class="gender-header" | f
| &mdash;
| &mdash;
| &mdash;
| &mdash;
| &mdash;
|-
! rowspan="2" class="tam-header" | subjunctive<br />{{{الْمُضَارِع الْمَنْصُوب}}}
! class="gender-header" | m
| rowspan="2" | &mdash;
| &mdash;
| {{{3sm-ps-subj}}}
| rowspan="2" | &mdash;
| &mdash;
| rowspan="2" | &mdash;
| &mdash;
| &mdash;
|-
! class="gender-header" | f
| &mdash;
| &mdash;
| &mdash;
| &mdash;
| &mdash;
|-
! rowspan="2" class="tam-header" | jussive<br />{{{الْمُضَارِع الْمَجْزُوم}}}
! class="gender-header" | m
| rowspan="2" | &mdash;
| &mdash;
| {{{3sm-ps-juss}}}
| rowspan="2" | &mdash;
| &mdash;
| rowspan="2" | &mdash;
| &mdash;
| &mdash;
|-
! class="gender-header" | f
| &mdash;
| &mdash;
| &mdash;
| &mdash;
| &mdash;
]=]

	elseif data.passive then
		text = text .. [=[
|-
! colspan="12" class="voice-header" | passive voice<br />{{{الْفِعْل الْمَجْهُول}}}
|-
| colspan="2" class="empty-header" | 
! colspan="3" class="number-header" | singular<br />{{{الْمُفْرَد}}}
| rowspan="10" class="divider" | 
! colspan="2" class="number-header" | dual<br />{{{الْمُثَنَّى}}}
| rowspan="10" class="divider" | 
! colspan="3" class="number-header" | plural<br />{{{الْجَمْع}}}
|-
| colspan="2" class="empty-header" | 
! class="person-header" | 1<sup>st</sup> person<br />{{{الْمُتَكَلِّم}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
! class="person-header" | 1<sup>st</sup> person<br />{{{الْمُتَكَلِّم}}}
! class="person-header" | 2<sup>nd</sup> person<br />{{{الْمُخَاطَب}}}
! class="person-header" | 3<sup>rd</sup> person<br />{{{الْغَائِب}}}
|-
! rowspan="2" class="tam-header" | past (perfect) indicative<br />{{{الْمَاضِي}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-ps-perf}}}
| {{{2sm-ps-perf}}}
| {{{3sm-ps-perf}}}
| rowspan="2" | {{{2d-ps-perf}}}
| {{{3dm-ps-perf}}}
| rowspan="2" | {{{1p-ps-perf}}}
| {{{2pm-ps-perf}}}
| {{{3pm-ps-perf}}}
|-
! class="gender-header" | f
| {{{2sf-ps-perf}}}
| {{{3sf-ps-perf}}}
| {{{3df-ps-perf}}}
| {{{2pf-ps-perf}}}
| {{{3pf-ps-perf}}}
|-
! rowspan="2" class="tam-header" | non-past (imperfect) indicative<br />{{{الْمُضَارِع الْمَرْفُوع}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-ps-impf}}}
| {{{2sm-ps-impf}}}
| {{{3sm-ps-impf}}}
| rowspan="2" | {{{2d-ps-impf}}}
| {{{3dm-ps-impf}}}
| rowspan="2" | {{{1p-ps-impf}}}
| {{{2pm-ps-impf}}}
| {{{3pm-ps-impf}}}
|-
! class="gender-header" | f
| {{{2sf-ps-impf}}}
| {{{3sf-ps-impf}}}
| {{{3df-ps-impf}}}
| {{{2pf-ps-impf}}}
| {{{3pf-ps-impf}}}
|-
! rowspan="2" class="tam-header" | subjunctive<br />{{{الْمُضَارِع الْمَنْصُوب}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-ps-subj}}}
| {{{2sm-ps-subj}}}
| {{{3sm-ps-subj}}}
| rowspan="2" | {{{2d-ps-subj}}}
| {{{3dm-ps-subj}}}
| rowspan="2" | {{{1p-ps-subj}}}
| {{{2pm-ps-subj}}}
| {{{3pm-ps-subj}}}
|-
! class="gender-header" | f
| {{{2sf-ps-subj}}}
| {{{3sf-ps-subj}}}
| {{{3df-ps-subj}}}
| {{{2pf-ps-subj}}}
| {{{3pf-ps-subj}}}
|-
! rowspan="2" class="tam-header" | jussive<br />{{{الْمُضَارِع الْمَجْزُوم}}}
! class="gender-header" | m
| rowspan="2" | {{{1s-ps-juss}}}
| {{{2sm-ps-juss}}}
| {{{3sm-ps-juss}}}
| rowspan="2" | {{{2d-ps-juss}}}
| {{{3dm-ps-juss}}}
| rowspan="2" | {{{1p-ps-juss}}}
| {{{2pm-ps-juss}}}
| {{{3pm-ps-juss}}}
|-
! class="gender-header" | f
| {{{2sf-ps-juss}}}
| {{{3sf-ps-juss}}}
| {{{3df-ps-juss}}}
| {{{2pf-ps-juss}}}
| {{{3pf-ps-juss}}}
]=]
	end

	text = text .. [=[
|}
</div>
</div>]=]

	-- Function used as replace arg of call to rsub(). Replace the
	-- specified param with its (HTML) value. The param references appear
	-- as {{{PARAM}}} in the wikicode. Also call tag_text on Arabic text
	-- appearing as {{{ARABIC-TEXT}}}.
	local function repl(param)
		if rfind(param, "^[A-Za-z0-9_-]+$") then
			return data.forms[param]
		else
			return tag_text(param)
		end
	end

	return rsub(text, "{{{([^{}]+)}}}", repl)
		.. mw.getCurrentFrame():extensionTag{
			name = "templatestyles", args = { src = "Template:ar-conj/style.css" }
		}
end

return export