Jump to content

Module:kab-verb

From Wiktionary, the free dictionary


-- DO NOT USE! Forked off of ang-verb, much work to be done.
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local strutils = require("Module:string utilities")
local ut = require("Module:utils")

local com = require("Module:ang-common")
local lang = require("Module:languages").getByCode("ang")

local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local rsubn = mw.ustring.gsub

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

-- like str:gsub() but discards all but the first return value
local function gsub(term, foo, bar, n)
	local retval = term:gsub(foo, bar, n)
	return retval
end

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

local export = {}

local vowel = "aiu"
local vowel_c = "[" .. vowel .. "]"
local long_vowel_c = "[" .. long_vowel .. "]"
local non_vowel_c = "[^" .. vowel .. "]"
local always_unvoiced_cons = "cċkptxz"
local always_unvoiced_cons_c = "[" .. always_unvoiced_cons .. "]"
local unvoiced_cons = always_unvoiced_cons .. "fsþð"
local unvoiced_cons_c = "[" .. unvoiced_cons .. "]"
local single_cons = "bcčdḍɛfgǧɣhḥjklmnprsṣtṭwxyzẓ"
local single_cons_c = "[" .. single_cons .. "]"
local cons = single_cons .. "xz"
local cons_c = "[" .. cons .. "]"

local slots_and_accel = {
	["1sg_inten_aor"] = "1|s|inten|aor",
	["2sg_inten_aor"] = "2|s|inten|aor",
	["3sg_m_inten_aor"] = "3|s|m|inten|aor",
	["3sg_f_inten_aor"] = "3|s|f|inten|aor",
	["1pl_inten_aor"] = "1|p|inten|aor",
	["2pl_m_inten_aor"] = "2|p|m|inten|aor",
	["2pl_f_inten_aor"] = "2|p|f|inten|aor",
	["3pl_m_inten_aor"] = "3|p|m|inten|aor",
	["3pl_f_inten_aor"] = "3|p|f|inten|aor",
	["1sg_aor"] = "1|s|inten|aor",
	["2sg_aor"] = "2|s|inten|aor",
	["3sg_m_aor"] = "3|s|m|inten|aor",
	["3sg_f_aor"] = "3|s|f|inten|aor",
	["1pl_aor"] = "1|p|inten|aor",
	["2pl_m_aor"] = "2|p|m|inten|aor",
	["2pl_f_aor"] = "2|p|f|inten|aor",
	["3pl_m_aor"] = "3|p|m|inten|aor",
	["3pl_f_aor"] = "3|p|f|inten|aor",
	["1sg_pret"] = "1|s|pret",
	["2sg_pret"] = "2|s|pret",
	["3sg_pret"] = "3|s|pret",
	["1pl_pret"] = "1|p|pret",
	["2pl_m_pret"] = "2|p|m|pret",
	["2pl_f_pret"] = "2|p|f|pret",
	["3pl_m_pret"] = "3|p|m|pret",
	["3pl_f_pret"] = "3|p|f|pret",
	["1sg_neg_pret"] = "1|s|neg|pret",
	["2sg_neg_pret"] = "2|s|neg|pret",
	["3sg_neg_pret"] = "3|s|neg|pret",
	["1pl_neg_pret"] = "1|p|neg|pret",
	["2pl_m_neg_pret"] = "2|p|m|neg|pret",
	["2pl_f_neg_pret"] = "2|p|f|neg|pret",
	["3pl_m_neg_pret"] = "3|p|m|neg|pret",
	["3pl_f_neg_pret"] = "3|p|f|neg|pret",
	["sg_impr"] = "2|s|impr",
	["pl_m_impr"] = "2|p|m|impr",
	["pl_f_impr"] = "2|p|f|impr",
	["inten_aor_ptc"] = "inten|aor|part",
	["neg_inten_aor_ptc"] = "negative|inten|aor|part",
	["aor_ptc"] = "aor|part",
	["past_ptc"] = "past|part",
	["neg_past_ptc"] = "neg|past|part",
}

local function ends_in_long_vowel(word)
	if rfind(word, long_vowel_c .. "$") then
		return true
	elseif rfind(word, "ē[ao]$") then
		return true
	elseif rfind(word, "ī[eo]$") then
		return true
	else
		return false
	end
end

local function begins_with_verbal_prefix(word)
	word = com.decompose(word)
	for _, prefixspec in ipairs(com.prefixes) do
		local prefix = prefixspec[1]
		local specs = prefixspec[2]
		if specs.verb then
			local main = rmatch(word, "^" .. prefix .. "(.-)$")
			if main and (not specs.restriction or rfind(main, specs.restriction)) then
				return true
			end
		end
	end

	return false
end

local function make_table(forms)
	local title = forms.title
	local accel_lemma = m_links.remove_links(forms.infinitive[1] or mw.title.getCurrentTitle().text)
	local function typetext()
		local typ = forms["type"]
		local class = forms["class"]
		if typ == "strong" then
			return "[[Appendix:Old English verbs|strong" .. (class and " class " .. class or "") .. "]]"
		elseif typ == "weak" then
			return "[[Appendix:Old English verbs|weak" .. (class and " class " .. class or "") .. "]]"
		elseif typ == "pretpres" then
			return "[[Appendix:Old English verbs|preterite-present]]"
		elseif typ == "irreg" or typ == "anomalous" then
			return "[[Appendix:Old English verbs|irregular]]"
		else
			error("Unrecognized verb type: " .. typ)
		end
	end
	if not title then
		title = "Conjugation of " .. m_links.full_link({lang = lang, alt = accel_lemma}, "term")
		if forms["type"] then
			title = title .. " (" .. typetext() .. ")"
		end
	end
	local table_args = {
		title = title,
		style = forms.style == "right" and "float:right; clear:right;" or "",
		width = forms.width or "30",
	}
	for slot, accel_form in pairs(slots_and_accel) do
		local table_arg = {}
		local slotforms = forms[slot] and #forms[slot] > 0 and forms[slot] or {"—"}
		for _, form in ipairs(slotforms) do
			table.insert(table_arg, form == "—" and form or m_links.full_link{
				lang = lang, term = form, accel = {
					form = accel_form ~= "" and accel_form or nil,
					lemma = accel_lemma,
				}
			})
			table_args[slot] = table.concat(table_arg, ", ")
		end
	end
	local table = [=[
<div class="NavFrame" style="width: 80em; border: 1px solid 2D6235">
<div class="NavHead" style="background:#F3E837">{title}</div>
<div class="NavContent">
{\op}| style="background: #F9F9F9; text-align:center; width:100%; line-height: 125%; border: 1px solid #CCCCFF;" cellpadding="3" cellspacing="1" class="inflection-table"
! style="width: 10%; background: #C0C5F7"|
! style="width: 10%; background: #C0C5F7"|
! style="width: 10%; background: #C0C5F7"| Intensive aorist
! style="width: 10%; background: #C0C5F7"| Preterite
! style="width: 10%; background: #C0C5F7"| Negative preterite
! style="width: 10%; background: #C0C5F7"| Aorist
! style="width: 10%; background: #C0C5F7"| Imperative
|-
! rowspan="4" style="background: #C0C5F7"| Singular ||style="background: #A0A9FF"| 1st person
| style="background: #FAFAFA"| {1sg_inten_aor}
| {1sg_pret} 
| {1sg_neg_pret} 
| {1sg_aor}
|-
! style="background: #A0A9FF"| 2nd person
| style="background: #FAFAFA"| {2sg_inten_aor} 
| {2sg_pret} 
| {2sg_neg_pret} 
| {2sg_aor}
| {sg_impr}
|-
! style="background: #A0A9FF"| 3rd person masculine
| style="background: #FAFAFA"| {3sg_m_inten_aor} 
| {3sg_m_pret} 
| {3sg_m_neg_pret} 
| {3sg_m_aor}
|-
! style="background: #A0A9FF"| 3rd person feminine
| style="background: #FAFAFA"| {3sg_f_inten_aor} 
| {3sg_f_pret} 
| {3sg_f_neg_pret} 
| {3sg_f_aor}
|-
! rowspan="5" style="background: #C0C5F7"| Plural || style="background: #A0A9FF"| 1st person
| style="background: #FAFAFA"| {1pl_inten_aor} 
| {1pl_pret} 
| {1pl_neg_pret} 
| {1pl_aor}
|-
! style="background: #A0A9FF"| 2nd person masculine
| style="background: #FAFAFA"| {2pl_m_inten_aor} 
| {2pl_m_pret} 
| {2pl_m_neg_pret} 
| {2pl_m_aor}  
| {pl_m_impr}
|-
! style="background: #A0A9FF"| 2nd person feminine
| style="background: #FAFAFA"| {2pl_f_inten_aor} 
| {2pl_f_pret} 
| {2pl_f_neg_pret} 
| {2pl_f_aor}
| {pl_f_impr}
|-
! style="background: #A0A9FF"| 3rd person masculine
| style="background: #FAFAFA"| {3pl_m_inten_aor} 
| {3pl_m_pret} 
| {3pl_m_neg_pret} 
| {3pl_m_aor}
|-
! style="background: #A0A9FF"| 3rd person feminine
| style="background: #FAFAFA"| {3pl_f_inten_aor} 
| {3pl_f_pret} 
| {3pl_f_neg_pret} 
| {3pl_f_aor}
|-
! style="background: #C0C5F7" colspan="2"| Participles 
| style="background: #FAFAFA" | {inten_aor_ptc} (''positive''), {neg_inten_aor_ptc} (''negative'') 
| {{l-self|kab|{past_ptc} 
| {{l-self|kab|{neg_past_ptc} 
| {{l-self|kab|{aor_ptc}
|{\cl}</div></div>]=]
	return strutils.format(table, table_args)
end

function export.make_table(frame)
	local parent_args = frame:getParent().args
	local params = {
		["title"] = {default = "—"},
		["type"] = {},
		["width"] = {},
		["style"] = {},
	}
	for slot, _ in pairs(slots_and_accel) do
		params[slot] = {}
	end
	local args = require("Module:parameters").process(parent_args, params)
	return make_table(args)
end

local function vernerize_cons(cons, verner)
	if not verner then
		return cons
	end
	if cons == "s" then
		return "r"
	elseif cons == "þ" or cons == "ð" then
		return "d"
	elseif cons == "h" then
		return "g"
	else
		return cons
	end
end

local function depalatalize_final_cons_before_cons(word)
	if word:find("sċ$") then
		return word
	elseif word:find("nċġ$") then
		-- crinċġan etc.
		return gsub(word, "nċġ$", "ng")
	else
		return rsub(word, "([nċġ]?[ċġ])$", {
			["ċ"] = "c",
			["nċ"] = "nc",
			["ċċ"] = "cc",
			["ġċ"] = "ġc",
			["nġ"] = "ng",
			["ċġ"] = "cg",
			-- ġ and ġġ stay as-is
		})
	end
end

local function depalatalize_final_cons_before_back_vowel(word)
	local word = depalatalize_final_cons_before_cons(word)
	return gsub(word, "ġ$", "g")
end

local function palatalize_final_cons(word)
	return rsub(word, "([cg]?[cg])$", {
		["c"] = "ċ",
		["cc"] = "ċċ",
		["cg"] = "ċġ",
		["g"] = "ġ",
		["gc"] = "gċ", -- ??
		["gg"] = "ċġ",
	})
end

-- Add an ending that begins with a consonant onto a stem, making adjustments
-- as necessary when two consonants come together. Currently recognizes only
-- the three endings "st", "þ" and "d".
local function construct_cons_stem_ending(stem, ending)
	-- If the stem ends in a geminate, reduce to single consonant
	-- (e.g. fyll-, sett-).
	local origstem = stem
	local base, final_geminated = rmatch(stem, "^(.-)(" .. non_vowel_c .. ")%2$")
	if base then
		stem = base .. final_geminated
	end
	stem = depalatalize_final_cons_before_cons(stem)
	if ending == "st" then
		-- For 2sg:
		-- cyssan + -st -> cyst, tǣsan + -st -> tǣst
		-- settan + -st -> setst, myntan + -st -> myn(t)st (?), restan + -st -> restst/rest (?)
		-- bǣdan + -st -> bǣtst, sendan + -st -> sen(t)st
		-- cȳþan + -st -> cȳst, cwiþ- + -st -> cwist
		-- wiex- + -st -> wiext
		-- drenċan + -st -> drencst
		-- senġan + -st -> sengst
		if stem:find("s$") then
			return stem .. "t"
		elseif stem:find("[dt]$") then
			if rfind(stem, vowel_c .. "[dt]$") then
				return gsub(stem, "[dt]$", "tst")
			elseif rfind(stem, "s[dt]$") then
				return gsub(stem, "s[dt]$", "st")
			else
				return {gsub(stem, "[dt]$", "st"), gsub(stem, "[dt]$", "tst")}
			end
		elseif rfind(stem, "[þð]$") then
			return rsub(stem, "[þð]$", "st")
		elseif stem:find("x$") then
			return stem .. "t"
		else
			return stem .. "st"
		end
	elseif ending == "þ" then
		-- For 3sg:
		-- cyssan + -þ -> cyst, tǣsan + -þ -> tǣst
		-- settan + -þ -> set(t), myntan + -þ -> mynt, restan + -þ -> rest
		-- bǣdan + -þ -> bǣt(t), sendan + -þ -> sent
		-- cȳþan + -þ -> cȳþ(þ), cwiþ- + -þ -> cwiþ(þ)
		-- wiex- + -þ -> wiext
		-- drenċan + -þ -> drencþ
		-- senġan + -þ -> sengþ
		if rfind(stem, vowel_c .. "[dt]$") then
			return {gsub(stem, "[dt]$", "tt"), gsub(stem, "[dt]$", "t")}
		elseif stem:find("[dt]$") then
			return gsub(stem, "[dt]$", "t")
		elseif stem:find("[sx]$") then
			return stem .. "t"
		elseif rfind(stem, "[þð]$") then
			return {rsub(stem, "[þð]$", "þþ"), stem}
		else
			return stem .. "þ"
		end
	elseif ending == "d" then
		-- For past:
		-- cyssan + -de -> cyste, tǣsan + -de -> tǣsde
		-- settan + -de -> sette, myntan + -de -> mynte, restan + -de -> reste, mētan + -de -> mētte
		-- bǣdan + -de -> bǣdde, sendan + -de -> sende, hreddan + -de -> hredde
		-- cȳþan + -de -> cȳþde (later cȳdde), ġeorwirþan + -de -> ġeorwirþde, sċeþþan + -de -> sċeþte
		--   (? Bosworth-Toller has only sċeþede, and has pæþde for *pæþte from pæþþan)
		-- līexan + -de -> līexte
		-- pyffan + -de -> pyfte, ġelīefan + -de -> ġelīefde
		-- drenċan + -de -> drencte
		-- senġan + -de -> sengde
		-- cīepan + -de -> cīepte, clyppan + -de -> clypte
		-- spillan + -de -> spilde, cierran + -de -> cierde, wemman + -de -> wemde, etc.
		if rfind(stem, vowel_c .. "t$") then
			return stem .. "t"
		elseif rfind(stem, vowel_c .. "d$") then
			return stem .. "d"
		elseif stem:find("[dt]$") then
			return stem
		elseif rfind(stem, always_unvoiced_cons_c .. "$") or rfind(origstem, unvoiced_cons_c .. "[sfþð]$") then
			return stem .. "t"
		else
			return stem .. "d"
		end
	else
		error("Unrecognized ending: '" .. ending .. "'")
	end
end

-- Default function for combining a stem and an ending. This makes no adjustments
-- to the result other than returning an optional variant ending in -h if the result
-- ends in -g. Example: stīgan preterite stāg or stāh. (Does not apply to a form
-- ending in -ġ.) If adjustments are necessary at the stem/ending boundary, use e.g.
-- construct_cons_stem_ending.
local function default_construct_stem_ending(stem, ending)
	local form = stem .. ending
	if form:find("g$") and not form:find("[ncg]g$") then
		return {form, gsub(form, "g$", "h")}
	else
		return form
	end
end

local function add_form_to_slot(args, slot, form)
	if not args[slot] then
		args[slot] = {}
	end
	if type(form) == "string" then
		ut.insert_if_not(args[slot], form)
	else
		for _, f in ipairs(form) do
			ut.insert_if_not(args[slot], f)
		end
	end
end

local function add_ending_with_prefix(args, slot, pref, stems, ending, construct_stem_ending)
	construct_stem_ending = construct_stem_ending or default_construct_stem_ending
	if type(stems) ~= "table" then
		stems = {stems}
	end
	for _, stem in ipairs(stems) do
		local form = construct_stem_ending(pref and pref .. stem or stem, ending)
		add_form_to_slot(args, slot, form)
	end
end

local function add_ending(args, slot, stems, ending, construct_stem_ending)
	add_ending_with_prefix(args, slot, nil, stems, ending, construct_stem_ending)
end

local function construct_optional_ge_stem_ending(stem, ending)
	return "([[ge" .. stem .. ending .. "|ġe]])[[" .. stem .. ending .. "]]"
end

local function make_non_past(args, presa, presefin, presenfin, pres23, impsg, pp, with_ge)
	add_ending(args, "1sg_inten_aor", intaor, "ɣ", construct_cons_stem_ending)
	add_ending(args, "2sg_inten_aor", intaor, "ḍ", construct_cons_stem_ending)
	add_ending(args, "3sg_m_inten_aor", intaor, "", construct_cons_stem_ending)
	add_ending(args, "3sg_f_inten_aor", intaor, "")
	add_ending(args, "1pl_inten_aor", intaor, "")
	add_ending(args, "2pl_m_inten_aor", intaor, "m", construct_cons_stem_ending)
	add_ending(args, "2pl_f_inten_aor", intaor, "mt", construct_cons_stem_ending)
	add_ending(args, "3pl_m_inten_aor", intaor, "n", construct_cons_stem_ending)
	add_ending(args, "3pl_f_inten_aor", intaor, "nt", construct_cons_stem_ending)
	add_ending(args, "sg_impr", impsg, "")
	add_ending(args, "pl_m_impr", impsg, "t")
	add_ending(args, "pl_f_impr", impsg, "emt")
	add_ending(args, "pres_ptc", presenfin, "nde")
	if with_ge then
		add_ending(args, "past_ptc", pp, "", construct_optional_ge_stem_ending)
	else
		add_ending(args, "past_ptc", pp, "")
	end
end

local function make_strong_past(args, pref, pastsg, pastpl)
	add_ending_with_prefix(args, "1sg_pret", pref, pastsg, "ɣ")
	add_ending_with_prefix(args, "2sg_pret", pref, pastpl, "ḍ")
	add_ending_with_prefix(args, "3sg_m_pret", pref, pastsg, "")
	add_ending_with_prefix(args, "3sg_f_pret", pref, pastsg, "")
	add_ending_with_prefix(args, "1pl_pret", pref, pastpl, "")
	add_ending_with_prefix(args, "2pl_m_pret", pref, pastsg, "m")
	add_ending_with_prefix(args, "2pl_f_pret", pref, pastsg, "mt")
	add_ending_with_prefix(args, "3pl_m_pret", pref, pastsg, "n")
	add_ending_with_prefix(args, "3pl_f_pret", pref, pastsg, "nt")
	add_ending_with_prefix(args, "sg_past_subj", pref, pastpl, "e")
	add_ending_with_prefix(args, "pl_past_subj", pref, pastpl, "en")
end

local function make_strong(presa, prese, pres23, impsg, pastsg, pastpl, pp, with_ge)
	local args = {}
	make_non_past(args, presa, prese, prese, pres23, impsg, pp, with_ge)
	make_strong_past(args, nil, pastsg, pastpl)
	return args
end

local function make_weak_past(args, pref, past)
	add_ending_with_prefix(args, "1sg_past_indc", pref, past, "e")
	add_ending_with_prefix(args, "2sg_past_indc", pref, past, "est")
	add_ending_with_prefix(args, "3sg_past_indc", pref, past, "e")
	add_ending_with_prefix(args, "pl_past_indc", pref, past, "on")
	add_ending_with_prefix(args, "sg_past_subj", pref, past, "e")
	add_ending_with_prefix(args, "pl_past_subj", pref, past, "en")
end

local function make_weak(presa, presefin, presenfin, pres23, impsg, past, pp, with_ge)
	local args = {}
	make_non_past(args, presa, presefin, presenfin, pres23, impsg, pp, with_ge)
	make_weak_past(args, nil, past)
	return args
end


local strong_verbs = {}

strong_verbs["1"] = function(data)
	local pref, suf = rmatch(data.inf, "^(.-)ī(" .. cons_c .. "+)an$")
	if pref then
		-- bīdan "await", ācwīnan "dwindle away", sċīnan "shine", spīwan "spew, spit", etc.;
		-- stīgan "ascend" (þū stīġst, stāg/stāh [handled by default_construct_stem_ending],
		-- stigon, stiġen); blīcan "shine" (þū blīcst/blīċest)
		data.pres23 = pref .. "ī" .. palatalize_final_cons(suf)
		if suf == "g" then
			data.impsg = pref .. "īġ"
		else
			data.impsg = pref .. "ī" .. suf
		end
		-- ġīnan "yawn" (gān, ġinon, ġinen)
		data.pastsg = depalatalize_final_cons_before_back_vowel(pref) .. "ā" .. suf
		-- snīþan "cut" -> snidon; sċrīþan "go, proceed" -> sċridon
		-- (but not mīþan "avoid", wrīþan "twist")
		local pastsuf = vernerize_cons(suf, data.verner)
		data.pastpl = pref .. "i" .. pastsuf
		if pastsuf == "g" then
			data.pp = pref .. "iġen"
		else
			data.pp = data.pastpl .. "en"
		end
		return
	end
	pref = rmatch(data.inf, "^(.-)[ēī]on$")
	if pref then
		-- tēon/tīon "accuse" < *tīhanan, similarly lēon "lend" < *līhwanan,
		-- sēon "strain" < *sīhwanan, þēon "thrive" < *þinhanan (originally strong 3),
		-- wrēon < *wrīhanan. Beware of homonyms e.g. tēon "pull, draw" (strong 2) < *teuhanan,
		-- tēon "make, create" (weak) ?< *tīhanan (strong);
		-- þēon/þēowan/þȳ(g)(a)n "press" (weak) < *þunhijanan;
		-- sēon "see" (strong 5) < *sehwanan
		data.pres23 = pref .. "īeh"
		data.impsg = gsub(data.inf, "n$", "h")
		data.pastsg = pref .. "āh"
		if rfind(pref, "[þð]$") then
			-- þēon "thrive", has remnant strong 3 parts
			data.pastpl = {pref .. "ig", pref .. "ung"}
			data.pp = {pref .. "iġen", pref .. "ungen"}
		else
			data.pastpl = pref .. "ig"
			if pref:find("s$") then
				-- sēon "strain" alt past part siwen
				data.pp = {pref .. "iġen", pref .. "iwen"}
			else
				data.pp = pref .. "iġen"
			end
		end
		return
	end
end

strong_verbs["2"] = function(data)
	local pref, suf = rmatch(data.inf, "^(.-)ēo(" .. cons_c .. "+)an$")
	if pref then
		-- bēodan "command", crēopan "creep"; rēocan "smoke, reek" (þū rīecst/rīeċest);
		-- drēogan "endure" (þū drīeġst, drēag/drēah [handled by default_construct_stem_ending],
		-- drugon, drogen)
		data.pres23 = pref .. "īe" .. palatalize_final_cons(suf)
		data.impsg = pref .. "ēo" .. suf
		data.pastsg = pref .. "ēa" .. suf
		-- ġēotan "pour" (ġēat, guton, goten); ċēosan "choose" (ċēas, curon, coren)
		local pastpref = depalatalize_final_cons_before_back_vowel(pref)
		-- ċēosan "choose" -> curon; forlēosan "lose" -> forluron; sēoþan "boil" -> sudon
		-- (but not ābrēoþan "perish, ruin")
		local pastsuf = vernerize_cons(suf, data.verner)
		data.pastpl = pastpref .. "u" .. pastsuf
		data.pp = pastpref .. "o" .. pastsuf .. "en"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)ū(" .. cons_c .. "+)an$")
	if pref then
		-- sċūfan "shove"; brūcan "use" (þū brīecst/brīeċest);
		-- būgan "bend" (þū bīeġst, bēag/bēah [handled by default_construct_stem_ending],
		-- bugon, bogen)
		data.pres23 = pref .. "ȳ" .. palatalize_final_cons(suf)
		data.impsg = pref .. "ū" .. suf
		-- could have palatalization here, but no known examples
		data.pastsg = pref .. "ēa" .. suf
		-- no known examples of Vernerization, but just in case
		local pastsuf = vernerize_cons(suf, data.verner)
		data.pastpl = pref .. "u" .. pastsuf
		data.pp = pref .. "o" .. pastsuf .. "en"
		return
	end
	local pref = rmatch(data.inf, "^(.-)ēon$")
	if pref then
		-- tēon "pull, draw" < *teuhanan, flēon "flee"
		-- Beware of homonyms, e.g. tēon/tīon "accuse" (strong 1) < *tīhanan,
		-- tēon "make, create" (weak) ?< *tīhan (strong)
		data.pres23 = pref .. "īeh"
		data.impsg = gsub(data.inf, "n$", "h")
		data.pastsg = pref .. "ēah"
		-- no examples where depalatalization is needed
		data.pastpl = pref .. "ug"
		data.pp = pref .. "ogen"
		return
	end
end

strong_verbs["3"] = function(data)
	local pref, suf = rmatch(data.inf, "^(.-)i([mn]" .. cons_c .. "+)an$")
	if pref then
		-- bindan "bind", climban "climb", ġelimpan "happen", winnan "win",
		-- swimman "swim", etc.; ācwincan "vanish" (þū ācwincst/ācwinċest);
		-- cringan (þū cringst/crinġest), variant crinċġan (þū cringst/crinċġest)
		data.pres23 = pref .. "i" .. palatalize_final_cons(suf)
		data.impsg = pref .. "i" .. suf
		-- onġinnan "begin" (ongann, ongunnon, ongunnen), ċimban "join"
		local pastpref = depalatalize_final_cons_before_back_vowel(pref)
		-- no Verner alternation here; expected #fīþan, #fōþ, #fundon <
		-- *finþanan, *fanþ, *fundund regularized into findan, fand, fundon;
		-- but depalatalize to handle crin(ċ)ġan past crang/crungon/crungen
		-- and (maybe?) variant crinċan past cranc/cruncon/cruncen
		local pastsuf = depalatalize_final_cons_before_cons(suf)
		data.pastsg = pastpref .. "a" .. pastsuf
		data.pastpl = pastpref .. "u" .. pastsuf
		data.pp = pastpref .. "u" .. pastsuf .. "en"
		return
	end
	local pref, vowel, suf = rmatch(data.inf, "^(.-)(ie)(l" .. cons_c .. "+)an$")
	if not pref then
		-- ġeldan/ġildan/ġyldan variants of ġieldan; helpan/bellan/delfan etc.
		pref, vowel, suf = rmatch(data.inf, "^(.-)([eiy])(l" .. cons_c .. "+)an$")
	end
	if not pref then
		pref, vowel, suf = rmatch(data.inf, "^(.-)(eo)(" .. cons_c .. "+)an$")
	end
	if pref then
		-- helpan "help" (þū hilpst), bellan "bellow", beteldan "cover", delfan "dig",
		-- sweltan "die", etc.; belgan "swell with anger" (þū bielġst, bealg/bealh
		-- [handled by default_construct_stem_ending])
		--
		-- ġieldan "yield, pay" (þū ġiel(t)st, hē ġieldeþ/ġielt, ġeald, guldon, golden),
		-- ġiellan "yell", ġielpan "boast"
		--
		-- weorpan "throw" (þū wierpst), feohtan "fight", sċeorfan "gnaw";
		-- āseolcan "languish" (þū āsielcst/āsielċest), beorgan "protect"
		-- (þū bierġst, bearg/bearh [handled by default_construct_stem_ending])
		data.pres23 = pref .. (vowel == "e" and "i" or vowel == "eo" and "ie" or vowel) .. palatalize_final_cons(suf)
		data.impsg = pref .. vowel .. suf
		data.pastsg = pref .. "ea" .. suf
		-- ġieldan etc. (see above)
		local pastpref = depalatalize_final_cons_before_back_vowel(pref)
		-- weorþan -> wurdon
		local pastsuf = vernerize_cons(suf, data.verner)
		data.pastpl = pastpref .. "u" .. pastsuf
		data.pp = pastpref .. "o" .. pastsuf .. "en"
		return
	end
	local pref = rmatch(data.inf, "^(.-)eġdan$")
	if pref then
		-- breġdan "brandish" (þū breġdest, hē breġdeþ, bræġd/brǣd, brugdon, brogden)
		-- streġdan "strew" (like breġdan)
		data.pres23 = pref .. "eġde"
		data.impsg = pref .. "eġd"
		data.pastsg = {pref .. "æġd", pref .. "ǣd"}
		data.pastpl = pref .. "ugd"
		data.pp = pref .. "ogden"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)(ie?rn)an$")
	if not pref then
		pref, suf = rmatch(data.inf, "^(.-)(yrn)an$")
	end
	if pref then
		-- biernan/birnan/byrnan "burn" < *brinnanan (þū biernst, barn/born/bearn, burnon, burnen)
		-- iernan/irnan/yrnan "run" < *rinnan (like biernan)
		data.pres23 = pref .. suf
		data.impsg = data.pres23
		data.pastsg = {pref .. "arn", pref .. "orn", pref .. "earn"}
		data.pastpl = pref .. "urn"
		data.pp = pref .. "urnen"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)([ou])rnan$")
	if pref then
		-- murnan "mourn" (þū myrnst, mearn, murnon, — but BT documents mornen)
		-- spurnan/spornan "spurn" (þū spyrnst, spearn, spurnon, spornen)
		data.pres23 = pref .. "yrn"
		data.impsg = pref .. vowel .. "rn"
		data.pastsg = pref .. "earn"
		data.pastpl = pref .. "urn"
		data.pp = pref .. "ornen"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)e(r" .. cons_c .. "+)an$")
	if pref then
		-- berstan "burst" < *brestanan (þū birst, hē birsteþ/birst, bærst, burston, borsten)
		-- þersċan "thrash, thresh" < *þreskanan (þū þirsċst, hē þirsċeþ, þærsċ, þursċon, þorsċen)
		data.pres23 = pref .. "i" .. suf
		data.impsg = pref .. "e" .. suf
		data.pastsg = pref .. "æ" .. suf
		data.pastpl = pref .. "u" .. suf
		data.pp = pref .. "o" .. suf .. "en"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)fr([ei])ġnan$")
	if pref then
		-- friġnan "ask", variant freġnan
		data.pres23 = {pref .. "friġne", pref .. "friġn"}
		data.impsg = pref .. "fr" .. vowel .. "ġn"
		data.pastsg = pref .. "fræġn"
		data.pastpl = pref .. "frugn"
		data.pp = pref .. "frugnen"
		return
	end
	local pref = rmatch(data.inf, "^(.-)frīnan$")
	if pref then
		-- frīnan "ask" variant of friġnan
		data.pres23 = {pref .. "frīne", pref .. "frīn"}
		data.impsg = pref .. "frīn"
		data.pastsg = pref .. "frān"
		data.pastpl = pref .. "frūn"
		data.pp = pref .. "frūnen"
		return
	end
	local pref = rmatch(data.inf, "^(.-)fēolan$")
	if pref then
		-- fēolan "enter, penetrate"
		data.pres23 = {pref .. "fielh", pref .. "filh"}
		data.impsg = pref .. "feolh"
		data.pastsg = pref .. "fealh"
		data.pastpl = {pref .. "fǣl", pref .. "fulg", pref .. "fūl"}
		data.pp = {pref .. "folgen", pref .. "fōlen"}
		return
	end
end

strong_verbs["4"] = function(data)
	local pref, suf = rmatch(data.inf, "^(.-)ie(" .. cons_c .. ")an$")
	if pref then
		-- sċieran "shear"
		data.pres23 = {pref .. "ie" .. suf .. "e", pref .. "ie" .. suf}
		data.impsg = pref .. "ie" .. suf
		-- no Verner alternation or depalatalization here
		data.pastsg = pref .. "ea" .. suf
		data.pastpl = pref .. "ēa" .. suf
		data.pp = pref .. "o" .. suf .. "en"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)e(" .. cons_c .. ")an$")
	if pref then
		-- beran "bear, carry", cwelan "die", brecan "break" (þū bricst/briċest), etc.
		if suf == "r" or suf == "l" then
			data.pres23 = {pref .. "i" .. suf .. "e", pref .. "i" .. suf}
		else
			data.pres23 = pref .. "i" .. palatalize_final_cons(suf)
		end
		data.impsg = pref .. "e" .. suf
		-- no Verner alternation or depalatalization here
		data.pastsg = pref .. "æ" .. suf
		data.pastpl = pref .. "ǣ" .. suf
		data.pp = pref .. "o" .. suf .. "en"
		return
	end
	-- Next two are irregular but occur with so many prefixes that we special-case them here.
	local pref = rmatch(data.inf, "^(.-)cuman$")
	if pref then
		-- cuman "come"
		data.pres23 = {pref .. "cyme", pref .. "cym"}
		data.impsg = pref .. "cum"
		data.pastsg = {pref .. "cōm", pref .. "cwōm"}
		data.pastpl = data.pastsg
		data.pp = {pref .. "cumen", pref .. "cymen"}
		return
	end
	local pref = rmatch(data.inf, "^(.-)niman$")
	if pref then
		-- niman "take"
		data.pres23 = {pref .. "nime", pref .. "nim"}
		data.impsg = pref .. "nim"
		data.pastsg = pref .. "nōm"
		data.pastpl = data.pastsg
		data.pp = pref .. "numen"
		return
	end
end

strong_verbs["5"] = function(data)
	local pref, suf = rmatch(data.inf, "^(.-)ie(" .. cons_c .. ")an$")
	if not pref then
		pref, suf = rmatch(data.inf, "^(.-ġ)[eiy](" .. cons_c .. ")an$")
	end
	if pref then
		-- ġiefan "give", forġietan "forget"
		data.pres23 = pref .. "ie" .. suf
		data.impsg = data.pres23
		data.pastsg = pref .. "ea" .. suf
		data.pastpl = pref .. "ēa" .. suf
		data.pp = pref .. "e" .. suf .. "en"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)e(" .. cons_c .. ")an$")
	if suf == "g" then
		-- wegan "carry"; special-case
		data.pres23 = pref .. "iġ"
		data.impsg = pref .. "eġ"
		data.pastsg = pref .. "æġ"
		data.pastpl = {pref .. "ǣg", pref .. "āg"}
		data.pp = pref .. "eġen"
		return
	elseif pref then
		-- metan "measure", sprecan "speak", cnedan "knead", wefan "weave", etc.
		data.pres23 = pref .. "i" .. palatalize_final_cons(suf)
		data.impsg = pref .. "e" .. suf
		if suf == "t" and not pref:find("m$") and not pref:find("ġ$") then
			-- etan "eat", fretan "devour", compounds; exclude metan "measure",
			-- ġetan (potential variant of ġietan)
			data.pastsg = pref .. "ǣ" .. suf
		else
			data.pastsg = pref .. "æ" .. suf
		end
		-- cweþan "say" -> cwǣdon, wesan "be" -> wǣron
		-- (but not ġenesan "be saved", lesan "collect, gather")
		local pastsuf = vernerize_cons(suf, data.verner)
		data.pastpl = pref .. "ǣ" .. pastsuf
		if data.inf:find("drepan$") then
			-- Special-case drepan "strike, slay"; Wright lists brecan here with
			-- pp only brocen, but that just makes it a strong-4 verb and that's
			-- where it's normally listed in dictionaries
			data.pp = {pref .. "epen", pref .. "open"}
		else
			data.pp = pref .. "e" .. pastsuf .. "en"
		end
		return
	end
	local pref, last_cons = rmatch(data.inf, "^(.-)i([dt])%2an$")
	if pref then
		-- biddan "pray", sittan "sit"
		data.pres23 = pref .. "i" .. last_cons
		data.impsg = data.pres23 .. "e"
		data.pastsg = pref .. "æ" .. last_cons
		data.pastpl = pref .. "ǣ" .. last_cons
		data.pp = pref .. "e" .. last_cons .. "en"
		return
	end
	local pref = rmatch(data.inf, "^(.-)iċġe?an$")
	if pref then
		-- liċġan "lie down", þiċġan "receive", friċġan "ask, inquire"
		data.pres23 = pref .. "iġ"
		data.impsg = data.pres23 .. "e"
		if rfind(data.inf, "[þð]iċġe?an$") then
			data.pastsg = {pref .. "eah", pref .. "ah"}
		else
			data.pastsg = pref .. "æġ"
		end
		data.pastpl = {pref .. "ǣg", pref .. "āg"}
		if data.inf:find("friċġe?an$") then
			data.pp = {pref .. "iġen", pref .. "ugen"}
		else
			data.pp = pref .. "eġen"
		end
		return
	end
	local pref = rmatch(data.inf, "^(.-)ēon$")
	if pref then
		-- sēon "see" < *sehwanan, ġefēon "rejoice" < *gafehanan, plēon "risk" < *plehanan
		data.pres23 = pref .. "ieh"
		data.impsg = pref .. "eoh"
		data.pastsg = pref .. "eah"
		if pref:find("s$") then
			-- sēon "see"
			data.pastpl = pref .. "āw"
			data.pp = pref .. "ewen"
		else
			data.pastpl = pref .. "ǣg"
			data.pp = pref .. "eġen"
		end
		return
	end
end

strong_verbs["6"] = function(data)
	local pref, suf = rmatch(data.inf, "^(.-)ea(" .. cons_c .. "+)an$")
	if pref then
		-- sċ(e)acan "shake", sċ(e)afan "shave, scrape"
		data.pres23 = pref .. "ea" .. suf
		data.impsg = {pref .. "a" .. suf, data.pres23}
		data.pastsg = {pref .. "ō" .. suf, pref .. "eō" .. suf}
		data.pastpl = data.pastsg
		data.pp = {pref .. "a" .. suf .. "en", pref .. "ea" .. suf .. "en"}
		return
	end
	local pref, vowel, suf = rmatch(data.inf, "^(.-)(a)(" .. cons_c .. "+)an$")
	if not pref then
		-- stondan variant of standan, sponan variant of spanan
		pref, vowel, suf = rmatch(data.inf, "^(.-)(o)(n" .. cons_c .. "*)an$")
	end
	if not pref then
		-- wæsċan/wasċean/wæsċean variant of wasċan
		pref, vowel, suf = rmatch(data.inf, "^(.-)([aæ])(sċ" .. cons_c .. "*)e?an$")
	end
	if suf == "g" then
		-- gnagan "gnaw", dragan "draw"
		data.pres23 = pref .. "æġ"
		data.impsg = pref .. "ag"
		data.pastsg = pref .. "ōg"
		data.pastpl = data.pastsg
		data.pp = {pref .. "æġen", pref .. "agen"}
		return
	elseif suf == "nd" and pref:find("st$") then
		-- standan/stondan "stand"
		data.pres23 = pref .. "end"
		data.impsg = pref .. vowel .. "nd"
		data.pastsg = pref .. "ōd"
		data.pastpl = data.pastsg
		data.pp = pref .. vowel .. "nden"
		return
	elseif suf == "n" and pref:find("sp$") then
		-- spanon/sponan "allure"
		data.pres23 = {pref .. "ene", pref .. "en"}
		data.impsg = pref .. vowel .. "n"
		data.pastsg = {pref .. "ōn", pref .. "ēon"} -- has alt class VII past
		data.pastpl = data.pastsg
		data.pp = pref .. vowel .. "nen"
		return
	elseif pref then
		-- faran "go", alan "grow", bacan "bake", hladan "lade", wasċan "wash", etc.
		if suf == "r" or suf == "l" or suf == "sċ" then
			data.pres23 = {pref .. "æ" .. suf .. "e", pref .. "æ" .. suf}
		else
			data.pres23 = pref .. "æ" .. palatalize_final_cons(suf)
		end
		data.impsg = pref .. "a" .. suf
		data.pastsg = pref .. "ō" .. suf
		-- Apparently no Verner variation here
		data.pastpl = data.pastsg
		data.pp = {pref .. "æ" .. suf .. "en", pref .. "a" .. suf .. "en"}
		return
	end
	local pref = rmatch(data.inf, "^(.-)ēan$")
	if pref then
		-- slēan "strike", flēan "flay", lēan "blame", þwēan "wash"
		data.pres23 = pref .. "ieh"
		data.impsg = pref .. "eah"
		data.pastsg = pref .. "ōg" -- also slōh etc., which will get automatically added
		data.pastpl = data.pastsg
		data.pp = {pref .. "æġen", pref .. "agen", pref .. "eġen"}
		return
	end
	local pref, last_cons = rmatch(data.inf, "^(.-)i([dt])%2an$")
	-- All the j-present verbs have to be handled individually as each has
	-- idiosyncrasies.
	local pref, last_cons = rmatch(data.inf, "^(.-)hebban$")
	if pref then
		-- hebban "raise"
		data.pres23 = pref .. "hefe"
		data.impsg = data.pres23
		data.pastsg = pref .. "hōf"
		data.pastpl = data.pastsg
		data.pp = {pref .. "hæfen", pref .. "hafen", pref .. "hefen"}
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)hl([iy]?e?)hhan$")
	if pref then
		-- hliehhan "laugh"
		data.pres23 = pref .. "hl" .. vowel .. "h"
		data.impsg = data.pres23
		data.pastsg = pref .. "hlōg"
		data.pastpl = data.pastsg
		data.pp = pref .. "hlæġen"
		return
	end
	local pref, cons = rmatch(data.inf, "^(.-)sċe([þð])%2an$")
	if pref then
		-- sċeþþan "injure" (for some reason, expected #sċieþþan doesn't occur)
		data.pres23 = pref .. "sċe" .. cons .. "e"
		data.impsg = data.pres23
		data.pastsg = "sċōd"
		data.pastpl = data.pastsg
		data.pp = "sċeaþen"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)sċ([iy]?e?)ppan$")
	if pref then
		-- sċieppan "create"
		data.pres23 = pref .. "sċ" .. vowel .. "pe"
		data.impsg = data.pres23
		data.pastsg = {pref .. "sċōp", pref .. "sċeōp"}
		data.pastpl = data.pastsg
		data.pp = {pref .. "sċeapen", pref .. "sċapen"}
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)st([æe])ppan$")
	if pref then
		-- stæppan "step, go"
		data.pres23 = pref .. "st" .. vowel .. "pe"
		data.impsg = data.pres23
		data.pastsg = pref .. "stōp"
		data.pastpl = data.pastsg
		data.pp = {pref .. "stæpen", pref .. "stapen"}
		return
	end
	local pref = rmatch(data.inf, "^(.-s[wƿ])erian$")
	if pref then
		-- swerian "swear"
		data.pres23 = pref .. "ere"
		data.impsg = data.pres23
		data.pastsg = pref .. "ōr"
		data.pastpl = data.pastsg
		data.pp = pref .. "oren" -- on the analogy of class IV verbs like beran
		return
	end
end

strong_verbs["7"] = function(data)
	-- Type (7a) verbs
	local pref = rmatch(data.inf, "^(.-)hātan$")
	if pref then
		-- hātan "call"
		data.pres23 = pref .. "hǣt"
		data.impsg = pref .. "hāt"
		data.pastsg = {pref .. "hēt", pref .. "hēht"} -- FIXME, indicate latter as poetic
		data.pastpl = data.pastsg
		data.pp = pref .. "hāten"
		return
	end
	local pref = rmatch(data.inf, "^(.-)lācan$")
	if pref then
		-- lācan "play"
		data.pres23 = pref .. "lǣċ"
		data.impsg = pref .. "lāc"
		data.pastsg = {pref .. "lēc", pref .. "leolc"} -- FIXME, indicate latter as poetic
		data.pastpl = data.pastsg
		data.pp = pref .. "lācen"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)sċ(e?ā)dan$")
	if not pref then
		pref, vowel = rmatch(data.inf, "^(.-)sċ(ēa)dan$")
	end
	if pref then
		-- sċ(e)ādan or sċēadan "separate"
		data.pres23 = pref .. "sċēad"
		data.impsg = pref .. "sċ" .. vowel .. "d"
		data.pastsg = {pref .. "sċēd", pref .. "sċēad"}
		data.pastpl = data.pastsg
		data.pp = pref .. "sċ" .. vowel .. "den"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)lǣtan$")
	if pref then
		-- lǣtan "let, allow"
		data.pres23 = pref .. "lǣt"
		data.impsg = pref .. "lǣt"
		data.pastsg = {pref .. "lēt", pref .. "leort"} -- FIXME, indicate latter as poetic
		data.pastpl = data.pastsg
		data.pp = pref .. "lǣten"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)ondrǣdan$")
	if pref then
		-- ondrǣdan "dread, fear"
		data.pres23 = pref .. "ondrǣd"
		data.impsg = pref .. "ondrǣd"
		data.pastsg = {pref .. "ondrēd", pref .. "ondreord"} -- FIXME, indicate latter as poetic
		data.pastpl = data.pastsg
		make_weak_past(data.extraforms, pref, "ondrǣdd")
		data.pp = pref .. "ondrǣden"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)rǣdan$")
	if pref then
		-- rǣdan "advise"
		data.pres23 = pref .. "rǣd"
		data.impsg = pref .. "rǣd"
		-- NOTE: Also (mostly) has weak past rǣdde and pp (ġe)rǣdd; can be handled
		-- by declaring the verb as both weak 1b and strong 7.
		data.pastsg = {pref .. "rēd", pref .. "reord"} -- FIXME, indicate latter as poetic
		data.pastpl = data.pastsg
		data.pp = pref .. "rǣden"
		return
	end
	local pref, vowel = rmatch(data.inf, "^(.-)slǣpan$")
	if pref then
		-- slǣpan "sleep"
		data.pres23 = pref .. "slǣp"
		data.impsg = pref .. "slǣp"
		data.pastsg = pref .. "slēp"
		data.pastpl = data.pastsg
		make_weak_past(data.extraforms, pref, "slǣpt")
		data.pp = pref .. "slǣpen"
		return
	end
	local pref, main = rmatch(data.inf, "^(.-)(gr[ǣē]t)an$")
	if pref then
		-- grǣtan "wail"
		data.pres23 = pref .. main
		data.impsg = pref .. main
		data.pastsg = pref .. "grēt"
		data.pastpl = data.pastsg
		data.pp = pref .. main .. "en"
		return
	end
	local pref = rmatch(data.inf, "^(.-)blandan$")
	if pref then
		-- blandan "mix"
		data.pres23 = pref .. "blend"
		data.impsg = pref .. "bland"
		data.pastsg = pref .. "blēnd"
		data.pastpl = data.pastsg
		data.pp = pref .. "blanden"
		return
	end
	local pref = rmatch(data.inf, "^(.-)ōn$")
	if pref then
		-- fōn "seize", hōn "hang"
		data.pres23 = pref .. "ēh"
		data.impsg = pref .. "ōh"
		data.pastsg = pref .. "ēng"
		data.pastpl = data.pastsg
		data.pp = {pref .. "angen"}
		return
	end
	-- Type (7b) verbs
	local pref = rmatch(data.inf, "^(.-)annan$")
	if pref then
		-- bannan "summon", spannan "join, clasp"
		data.pres23 = pref .. "enn"
		data.impsg = pref .. "ann"
		data.pastsg = {pref .. "ēonn", pref .. "ēon"}
		data.pastpl = data.pastsg
		data.pp = pref .. "annen"
		return
	end
	local pref = rmatch(data.inf, "^(.-)gangan$")
	if pref then
		-- gangan "go"
		data.pres23 = pref .. "geng"
		data.impsg = pref .. "gang"
		data.pastsg = {pref .. "ġēng", pref .. "ġīeng"}
		data.pastpl = data.pastsg
		data.pp = pref .. "gangen"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)ea(" .. cons_c .. "+)an$")
	if pref then
		-- fealdan "fold", feallan "fall", healdan "hold", stealdan "possess",
		-- wealcan "roll", wealdan "rule", weallan "boil", weaxan "grow"
		data.pres23 = pref .. "ie" .. palatalize_final_cons(suf)
		data.impsg = pref .. "ea" .. suf
		data.pastsg = pref .. "ēo" .. suf
		data.pastpl = data.pastsg
		data.pp = pref .. "ea" .. suf .. "en"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)ā(" .. cons_c .. "+)an$")
	if pref then
		-- blāwan "blow", cnāwan "know", crāwan "crow", māwan "mow", sāwan "sow",
		-- swāpan "sweep", þrāwan "turn, twist", wāwan "blow"
		data.pres23 = pref .. "ǣ" .. palatalize_final_cons(suf)
		data.impsg = pref .. "ā" .. suf
		data.pastsg = pref .. "ēo" .. suf
		data.pastpl = data.pastsg
		data.pp = pref .. "ā" .. suf .. "en"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)ēa(" .. cons_c .. "+)an$")
	if pref then
		-- bēatan "beat", āhnēapan "pluck off", hēawan "hew", hlēapan "leap"
		data.pres23 = pref .. "īe" .. palatalize_final_cons(suf)
		data.impsg = pref .. "ēa" .. suf
		data.pastsg = pref .. "ēo" .. suf
		data.pastpl = data.pastsg
		data.pp = pref .. "ēa" .. suf .. "en"
		return
	end
	local pref, suf = rmatch(data.inf, "^(.-)ō(" .. cons_c .. "+)an$")
	if pref then
		if suf == "g" and pref:find("sw$") then
			-- swōgan "sound"
			data.pres23 = pref .. "ēġ"
			data.impsg = pref .. "ōg"
			data.pastsg = {} -- no preterite
			data.pastpl = {}
			data.pp = pref .. "ōgen"
			return
		end
		if suf == "c" and pref:find("fl$") or suf == "t" and rfind(pref, "[wƿ]r$") then
			-- flōcan "clap, strike", wrōtan "root up"
			data.pres23 = pref .. "ē" .. palatalize_final_cons(suf)
			data.impsg = pref .. "ō" .. suf
			data.pastsg = {} -- no preterite
			data.pastpl = {}
			data.pp = pref .. "ō" .. suf .. "en"
			return
		end
		-- blōtan "sacrifice", blōwan "bloom, blossom", hrōpan "shout",
		-- hwōpan "threaten", flōwan "flow", grōwan "grow", hlōwan "low, bellow",
		-- spōwan "succeed"; also rōwan "row", which has alt pret pl rēon
		data.pres23 = pref .. "ē" .. palatalize_final_cons(suf)
		data.impsg = pref .. "ō" .. suf
		data.pastsg = pref .. "ēo" .. suf
		data.pastpl = data.pastsg
		data.pp = pref .. "ō" .. suf .. "en"
		if (suf == "w" or suf == "ƿ") and pref:find("r$") and not pref:find("gr$") then
			-- rōwan "row"
			data.extraforms["pl_past_indc"] = {pref .. "ēon"}
		end
		return
	end
	local pref = rmatch(data.inf, "^(.-[wƿ])ēpan$")
	if pref then
		-- wēpan "weep"; j-present
		data.pres23 = pref .. "ēp"
		data.impsg = pref .. "ēp"
		data.pastsg = pref .. "ēop"
		data.pastpl = data.pastsg
		data.pp = pref .. "ōpen"
		return
	end
end

local weak_verbs = {}

weak_verbs["1a"] = function(data)
	local pref = rmatch(data.inf, "^(.-)ian$")
	if pref then
		data.pres23 = pref .. "e"
		data.impsg = data.pres23
		data.past = data.pres23 .. "d"
		data.pp = data.past
		return
	end
	local pref, last_cons = rmatch(data.inf, "^(.-)([dt])%2an$")
	if pref then
		data.pres23 = pref .. last_cons
		data.impsg = data.pres23 .. "e"
		data.past = data.pres23 .. last_cons
		data.pp = {data.pres23 .. "ed", data.past, data.pres23}
		return
	end
	local pref = rmatch(data.inf, "^(.-)ċġe?an$")
	if pref then
		data.pres23 = pref .. "ġ"
		data.impsg = data.pres23 .. "e"
		data.past = data.pres23 .. "d"
		data.pp = {data.pres23 .. "ed", data.past}
		return
	end
	local pref = rmatch(data.inf, "^(.-)bban$")
	if pref then
		-- swebban etc.
		data.pres23 = pref .. "fe"
		data.impsg = data.pres23
		data.past = data.pres23 .. "d"
		data.pp = data.past
		return
	end
	local pref, last_cons = rmatch(data.inf, "^(.-)(.)%2an$")
	if not pref then
		pref, last_cons = rmatch(data.inf, "^(.-)(ċ)ċean$")
	end
	if pref then
		data.pres23 = pref .. last_cons .. "e"
		data.impsg = data.pres23
		data.past = data.pres23 .. "d"
		data.pp = data.past
		return
	end
end

weak_verbs["1b"] = function(data)
	local pref, cons = rmatch(data.inf, "^(.-[rl])([wƿ])an$")
	if pref then
		-- ġierwan, hierwan, nierwan, sierwan, smierwan, wielwan (?)
		data.pres23 = pref .. "e"
		data.impsg = data.pres23
		data.past = data.pres23 .. "d"
		data.pp = {pref .. cons .. "ed", pref .. "ed"}
		return
	end
	local pref = rmatch(data.inf, "^(.-" .. cons_c .. "[rlmnwƿ])an$")
	if pref and not pref:find("[rl][mn]$") and not pref:find("rl$") and not rfind(pref, "(" .. cons_c .. ")%1$") then
		-- hyngran, dīeglan, bīecnan, þrysman, etc.
		data.pres23 = pref .. "e"
		data.impsg = data.pres23
		local vowel = rmatch(pref, "^.-(" .. vowel_c .. "+)" .. single_cons_c .. "[rlmnwƿ]$")
		if vowel and not ends_in_long_vowel(vowel) then
			-- efnan, bytlan, eġlan, ræfnan, seġlan, þrysman; form without -ed is older
			data.past = {pref .. "d", pref .. "ed"}
		else
			-- dīeglan, forglendran, wrixlan, frēfran, timbran, symblan, etc.
			data.past = pref .. "ed"
		end
		data.pp = pref .. "ed"
		return
	end
	if data.inf:find("ēan$") or rfind(data.inf, vowel_c .. "n$") and not data.inf:find("an$") then
		-- contracted verb, e.g. hēan, þēon, tȳn, rȳn, þȳn
		-- FIXME, verify in Campbell that all of the following are correct
		data.pres23 = data.presa
		data.impsg = data.presa
		data.past = data.presa .. "d"
		data.pp = data.past
		return
	end
	local pref = rmatch(data.inf, "^(.-)an$")
	if pref then
		data.pres23 = {pref .. "e", pref}
		data.impsg = pref
		data.past = construct_cons_stem_ending(pref, "d")
		data.pp = pref .. "ed"
		return
	end
end

weak_verbs["1"] = function(data)
	local weaktype
	if data.inf:find("ian$") then
		weaktype = "1a"
	elseif rfind(data.inf, "(" .. cons_c .. ")%1an$") or data.inf:find("ċċean$") or data.inf:find("cgan$") or data.inf:find("ċġan$") then
		local prev = rmatch(data.inf, "^(.-)....$")
		if rfind(prev, vowel_c .. "$") and not ends_in_long_vowel(prev) then
			weaktype = "1a"
		else
			weaktype = "1b"
		end
	else
		weaktype = "1b"
	end
	weak_verbs[weaktype](data)
end

weak_verbs["2"] = function(data)
	-- lufian
	local pref, suf = rmatch(data.inf, "^(.-)(ian)$")
	if not pref then
		-- lufiġan, lufeġan, lufiġean, lufeġean (alternative spellings)
		pref, suf = rmatch(data.inf, "^(.-)([ei]ġe?an)$")
	end
	if pref then
		data.pres23 = pref .. "a"
		data.presenfin = data.prese
		data.presefin = suf == "ian" and pref .. "iġe" or data.presenfin
		data.impsg = data.pres23
		data.past = pref .. "od"
		data.pp = data.past
		return
	end
	local pref = rmatch(data.inf, "^(.-)ġe?an$")
	if pref then
		-- twēoġan, smēaġ(e)an
		data.pres23 = pref
		data.impsg = data.pres23
		data.past = data.pres23 .. "d"
		data.pp = data.past
		return
	end
end

weak_verbs["3"] = function(data)
	local pref = rmatch(data.inf, "^(.-)abban$")
	if pref then
		data.prese = pref .. "æbbe"
		data.pres23 = pref .. "æf"
		data.impsg = pref .. "afa"
		data.past = pref .. "æfd"
		data.pp = data.past
		return
	end
	local pref = rmatch(data.inf, "^(.-)libban$")
	if pref then
		-- libban, compounds
		data.pres23 = pref .. "leofa"
		data.impsg = data.pres23
		data.past = pref .. "lifd"
		data.pp = data.past
		return
	end
	local pref = rmatch(data.inf, "^(.-)seċġe?an$")
	if pref then
		-- seċġan, compounds
		data.pres23 = pref .. "sæġ"
		data.impsg = pref .. "sæġe"
		data.past = pref .. "sæġd"
		data.pp = data.past
		return
	end
	local pref = rmatch(data.inf, "^(.-)hyċġe?an$")
	if pref then
		-- hyċġan, compounds
		data.pres23 = pref .. "hyġ"
		data.impsg = pref .. "hyġe"
		data.past = pref .. "hogd"
		data.pp = data.past
		return
	end
end

local function make_irregular_non_past(args, inf, pref, pres1, pres2, pres3, prespl,
	presp, subj, impsg, imppl, pp, with_ge)
	add_ending(args, "infinitive", inf, "")
	add_ending_with_prefix(args, "infinitive2", pref, presp, "ne")
	add_ending_with_prefix(args, "1sg_pres_indc", pref, pres1, "")
	add_ending_with_prefix(args, "2sg_inten_aor", pref, pres2, "")
	add_ending_with_prefix(args, "3sg_inten_aor", pref, pres3, "")
	add_ending_with_prefix(args, "pl_pres_indc", pref, prespl, "")
	add_ending_with_prefix(args, "sg_pres_subj", pref, subj, "")
	add_ending_with_prefix(args, "pl_pres_subj", pref, subj, "n")
	add_ending_with_prefix(args, "sg_impr", pref, impsg, "")
	add_ending_with_prefix(args, "pl_impr", pref, imppl, "")
	add_ending_with_prefix(args, "pres_ptc", pref, presp, "de")
	if with_ge then
		add_ending_with_prefix(args, "past_ptc", pref, pp, "", construct_optional_ge_stem_ending)
	else
		add_ending_with_prefix(args, "past_ptc", pref, pp, "")
	end
end

-- Construct all preterite-present forms. Each of the arguments can be a single string or list.
-- If a given argument is missing, supply an empty list {}.
-- Arguments:
-- INF: Infinitive.
-- PREF: Prefix to add to all forms other than the infinitive.
-- PRES13: 1st/3rd singular present form.
-- PRES2: 2nd singular present form.
-- PRESPL: Plural present form.
-- PRESP: Present participle stem (up through -en, missing final -de; also used to construct
--        second infinitive by adding -ne)
-- SUBJ: Singular present subjunctive form (also used to construct plural subjunctive by adding -n)
-- IMPSG: 2nd singular imperative form.
-- IMPPL: 2nd plural imperative form.
-- PAST: Stem of past tense.
-- PP: Past participle.
local function make_preterite_present_forms(inf, pref, pres13, pres2, prespl,
	presp, subj, impsg, imppl, past, pp, with_ge)
	local args = {}
	make_irregular_non_past(args, inf, pref, pres13, pres2, pres13, prespl, presp, subj,
		impsg, imppl, pp, with_ge)
	make_weak_past(args, pref, past)
	return args
end

local function make_preterite_present(inf, with_ge)
	local pref, vowel = rmatch(inf, "^(.-[wƿn])([iy])[oe]?tan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "āt", "āst", vowel .. "ton", vowel .. "ten",
			vowel .. "te", vowel .. "te", vowel .. "taþ", {vowel .. "ss", vowel .. "st"},
			vowel .. "ten", with_ge)
	end
	local pref = rmatch(inf, "^(.-)dugan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "dēag", "dēaht", "dugon", "dugen", "dyġe", "dyġe", "dugaþ", "doht", {}, with_ge)
	end
	local pref = rmatch(inf, "^(.-)cunnan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "cann", "canst", "cunnon", "cunnen", "cunne",
			"cunne", "cunnaþ", "cūþ", "cunnen", with_ge)
	end
	local pref = rmatch(inf, "^(.-)unnan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "ann", "anst", "unnon", "unnen", "unne", "unne", "unnaþ", "ūþ", "unnen", with_ge)
	end
	local pref = rmatch(inf, "^(.-[þð])urfan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "earf", "earft", "urfon", "earfen", "yrfe", "yrfe", "urfaþ", "orft", {}, with_ge)
	end
	local pref = rmatch(inf, "^(.-)durran$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "dearr", "dearst", "durron", {}, "dyrre", "dyrre", "durfaþ",
			"dorst", {}, with_ge)
	end
	local pref = rmatch(inf, "^(.-)sċulan$")
	if not pref then
		pref = rmatch(inf, "^(.-)sċeolan$")
	end
	if pref then
		return make_preterite_present_forms(
			inf, pref, "sċeal", "sċealt", "sċulon", {}, "sċyle",
			{}, {}, "sċold", {}, with_ge)
	end
	local pref = rmatch(inf, "^(.-)munan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "man", "manst", "munon", "munen", "myne",
			{"mun", "myne"}, "munaþ", "mund", "munen", with_ge)
	end
	local pref = rmatch(inf, "^(.-)magan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "mæġ", "meaht", "magon", "magen", "mæġe", {}, {},
			{"meaht", "meht"}, {}, with_ge)
	end
	local pref = rmatch(inf, "^(.-)nugan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "neah", {}, "nugon", {}, "nuge", {}, {}, "noht", {}, with_ge)
	end
	local pref = rmatch(inf, "^(.-)mōtan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "mōt", "mōst", "mōton", {}, "mōte", {}, {}, "mōst", {}, with_ge)
	end
	local pref = rmatch(inf, "^(.-)āgan$")
	if pref then
		return make_preterite_present_forms(
			inf, pref, "āh", "āhst", "āgon", "āgen", "āge", "āge", "āgaþ", "āht", "āgen", with_ge)
	end
	error("Unrecognized preterite-present verb: " .. inf)
end

local function make_irregular(inf, with_ge)
	local args = {}
	local pref, cons = rmatch(inf, "^(.-)([wƿ])esan$")
	if pref then
		make_irregular_non_past(args, inf, pref, "eom", "eart", "is", {"sind", "sindon"},
			cons .. "esen", "sīe", cons .. "es", cons .. "esaþ", {}, with_ge)
		make_strong_past(args, pref, cons .. "æs", cons .. "ǣr")
		return args
	end
	local pref = rmatch(inf, "^(.-)nesan$")
	if pref then
		make_irregular_non_past(args, inf, pref, "neom", "neart", "nis", {"ne sind", "ne sindon"},
			"nesen", "ne sīe", "nes", "nesaþ", {}, with_ge)
		make_strong_past(args, pref, "næs", "nǣr")
		return args
	end
	local pref, vowel = rmatch(inf, "^(.-)b([īē]o)n$")
	if pref then
		make_irregular_non_past(args, inf, pref, "b" .. vowel, "bist", "biþ", "b" .. vowel .. "þ",
			"b" .. vowel .. "n", "b" .. vowel, "b" .. vowel, "b" .. vowel .. "þ", "b" .. vowel .. "n", with_ge)
		return args
	end
	local pref = rmatch(inf, "^(.-)dōn$")
	if pref then
		make_irregular_non_past(args, inf, pref, "dō", "dēst", "dēþ", "dōþ", "dōn",
			"dō", "dō", "dōþ", "dōn", with_ge)
		make_weak_past(args, pref, "dyd")
		return args
	end
	local pref = rmatch(inf, "^(.-)gān$")
	if pref then
		make_irregular_non_past(args, inf, pref, "gā", "gǣst", "gǣþ", "gāþ", "gān",
			"gā", "gā", "gāþ", "gān", with_ge)
		make_weak_past(args, pref, "ēod")
		return args
	end
	local pref = rmatch(inf, "^(.-[wƿ])illan$")
	if pref then
		make_irregular_non_past(args, inf, pref, "ille", "ilt", "ile", "illaþ", "illen",
			"ille", {}, {}, {}, with_ge)
		make_weak_past(args, pref, "old")
		return args
	end
	local pref, vowel = rmatch(inf, "^(.-)n([eiy])llan$")
	if pref then
		make_irregular_non_past(args, inf, pref, "n" .. vowel .. "lle", "n" .. vowel .. "lt",
			"n" .. vowel .. "le", "n" .. vowel .. "llaþ",
			"n" .. vowel .. "llen", "n" .. vowel .. "lle",
			{}, {}, {}, with_ge)
		make_weak_past(args, pref, "nold")
		return args
	end
	error("Unrecognized irregular verb: " .. inf)
end

local function set_categories(typ, class)
	local cats = {}
	local classtext = class and "class " .. class .. " " or ""
	if typ == "strong" then
		table.insert(cats, "Old English " .. classtext .. "strong verbs")
	elseif typ == "weak" then
		table.insert(cats, "Old English " .. classtext .. "weak verbs")
	elseif typ == "pretpres" then
		table.insert(cats, "Old English preterite-present verbs")
	elseif typ == "irreg" or typ == "anomalous" then
		table.insert(cats, "Old English irregular verbs")
	elseif typ then
		error("Unrecognized verb type: " .. typ)
	end
	return cats
end

function export.show(frame)
	local parent_args = frame:getParent().args
	local params = {
		[1] = {required = true, list = true, default = "beran<s4>"},
	}
	for slot, _ in pairs(slots_and_accel) do
		params[slot] = {}
	end
	local args = require("Module:parameters").process(parent_args, params)
	local allforms
	local allcats = {}
	if #args[1] > 1 then
		table.insert(allcats, "Old English verbs with multiple conjugations")
	end
	for _, infspec in ipairs(args[1]) do
		local inf, spec = rmatch(infspec, "^(.-)<(.*)>$")
		if not inf then
			error("Verb spec should look like e.g. 'beran<s4>' or 'dēman<w1>': " .. infspec)
		end
		local specs = rsplit(spec, "/")
		local class = specs[1]
		local verner = rfind(class, "v")
		class = gsub(class, "v", "")
		local presa = gsub(inf, "n$", "")
		data = {
			inf = inf,
			presa = presa,
			prese = presa:find("ēa$") and presa or gsub(presa, "e?a$", "e"),
			verner = verner,
			with_ge = not begins_with_verbal_prefix(inf),
			extraforms = {},
		}
		local typ
		if class:find("^s") then
			typ = "strong"
			class = gsub(class, "^s", "")
			if strong_verbs[class] then
				strong_verbs[class](data)
			else
				error("Unrecognized strong class: " .. class)
			end
		elseif class:find("^w") then
			typ = "weak"
			class = gsub(class, "^w", "")
			if weak_verbs[class] then
				weak_verbs[class](data)
				data.presenfin = data.presenfin or data.prese
				data.presefin = data.presefin or data.prese
			else
				error("Unrecognized weak class: " .. class)
			end
		elseif class == "pp" then
			typ = "pretpres"
		elseif class == "i" then
			typ = "irreg"
		else
			error("Unrecognized verb class: " .. class)
		end

		local overrides = {}
		for i, spec in ipairs(specs) do
			if i > 1 then
				local parts = rsplit(spec, ":")
				if #parts ~= 2 then
					error("Verb modified spec must be of the form 'KEY:VALUE': " .. spec)
				end
				local k = parts[1]
				local v = parts[2]
				local splitv = rsplit(v, ", *")
				local unrecognized = false
				if slots_and_accel[k] then
					overrides[k] = splitv
				elseif k == "ge" then
					data.with_ge = require("Module:yesno")(v)
				elseif k == "impers" then
					data.impers = require("Module:yesno")(v)
				elseif typ == "weak" or typ == "strong" then
					if k == "pres" then
						data.presa = splitv
						data.prese = splitv
						data.presefin = splitv
						data.presenfin = splitv
						data.pres23 = splitv
						data.impsg = splitv
					elseif k == "pres1456" then
						data.presa = splitv
						data.prese = splitv
						data.presefin = splitv
						data.presenfin = splitv
					elseif k == "pres23" then
						data.pres23 = splitv
					elseif k == "presa" then
						data.presa = splitv
					elseif k == "prese" then
						data.prese = splitv
						data.presefin = splitv
						data.presenfin = splitv
					elseif typ == "weak" and k == "presefin" then
						data.presefin = splitv
					elseif typ == "weak" and k == "presenfin" then
						data.presenfin = splitv
					elseif k == "past" then
						data.past = splitv
						data.pastsg = splitv
						data.pastpl = splitv
					elseif typ == "strong" and k == "pastsg" then
						data.pastsg = splitv
					elseif typ == "strong" and k == "pastpl" then
						data.pastpl = splitv
					elseif k == "impsg" then
						data.impsg = splitv
					elseif k == "pp" then
						data.pp = splitv
					elseif typ == "weak" and k == "papp" then
						data.past = splitv
						data.pp = splitv
					else
						unrecognized = true
					end
				else
					unrecognized = true
				end
				if unrecognized then
					error("Unrecognized spec key '" .. k .. "' (value '" .. v .. "')")
				end
			end
		end

		if typ == "weak" or typ == "strong" then
			local missing_fields = {}
			local fields_to_check =
				typ == "weak" and {"presa", "presefin", "presenfin", "pres23", "impsg", "past", "pp"} or
				{"presa", "prese", "pres23", "impsg", "pastsg", "pastpl", "pp"}
			for _, field in ipairs(fields_to_check) do
				if not data[field] then
					table.insert(missing_fields, field)
				end
			end
			if #missing_fields > 0 then
				error("Required spec(s) " .. table.concat(missing_fields, ",") ..
					" missing, i.e. the form of the infinitive '" .. inf ..
					"' was not recognized as a " .. typ .. " class-" .. class .. " verb and the " ..
					"overriding specs weren't (all) supplied")
			end
		end
		local forms
		if typ == "strong" then
			forms = make_strong(data.presa, data.prese, data.pres23, data.impsg, data.pastsg,
				data.pastpl, data.pp, data.with_ge)
		elseif typ == "weak" then
			forms = make_weak(data.presa, data.presefin, data.presenfin,
				data.pres23, data.impsg, data.past, data.pp, data.with_ge)
		elseif typ == "pretpres" then
			forms = make_preterite_present(inf, data.with_ge)
		elseif typ == "irreg" then
			forms = make_irregular(inf, data.with_ge)
		else
			error("Internal error: Unrecognized verb type: " .. typ)
		end
		for k, v in pairs(data.extraforms) do
			add_form_to_slot(forms, k, v)
		end
		for k, v in pairs(overrides) do
			forms[k] = v
		end
		if data.impers then
			-- e.g. mǣtan; clear personal non-3sg forms
			forms["1sg_pres_indc"] = {}
			forms["2sg_inten_aor"] = {}
			forms["pl_pres_indc"] = {}
			forms["pl_pres_subj"] = {}
			forms["1sg_past_indc"] = {}
			forms["2sg_past_indc"] = {}
			forms["pl_past_indc"] = {}
			forms["pl_past_subj"] = {}
			forms["sg_impr"] = {}
			forms["pl_impr"] = {}
		end

		class = rsub(class, "[a-z]", "")
		if class == "" then
			class = nil
		end
		if not allforms then
			allforms = forms
			allforms["type"] = typ
			allforms["class"] = class
		else
			for k, v in pairs(forms) do
				for _, form in ipairs(v) do
					ut.insert_if_not(allforms[k], form)
				end
			end
		end
		local cats = set_categories(typ, class)
		for _, cat in ipairs(cats) do
			ut.insert_if_not(allcats, cat)
		end
	end
	local table = make_table(allforms)
	return table .. m_utilities.format_categories(allcats, lang)
end

return export