Documentation for this module may be created at မဝ်ဂျူ:pi-decl/noun/doc

local export = {}
-- require("Module:log globals") -- Examine Lua logs at end of preview for results. 
local links = require("Module:links")
local lang = require("Module:languages").getByCode("pi")
local m_parameters = require("Module:parameters")
local m_str_utils = require("Module:string utilities")
local m_translit
local to_script

local find = m_str_utils.find
local gsub = m_str_utils.gsub
local match = m_str_utils.match
local sub = m_str_utils.sub
local u = m_str_utils.char -- For readability.
local load = mw.loadData
local ti = table.insert
local currentScript
local scriptCode

local genders = {
	["m"] = "ပုလ္လိၚ်", ["f"] = "ဣတ္တိလိင်", ["n"] = "နပုလ္လိၚ်",
}
local rows = {
	"Nominative (first)", "Accusative (second)", "Instrumental (third)", "Dative (fourth)",
	"Ablative (fifth)", "Genitive (sixth)", "Locative (seventh)", "Vocative (calling)",
}

local endings = {
	["one"] = {
		-- key(Ln) Thai Deva       Beng       Mymr       Lana     Laoo       Khmr
        --           Sinh     Brah       Cakm
		["a"]  = {},
		["ā"]  = { "า", "ा", "आ", "া", "আ", "ါ", "ာ", " ႃ", "ᩣ", "ᩤ", "າ",  "ា", u(0x17A4),
					"ා", "ආ", "𑀸", "𑀆",  "𑄂" },
		["i"]  = { "ิ",  "ि", "इ",  "ি", "ই", "ိ", "ဣ",  "ᩥ", "ᩍ", "ິ",        "ិ", "ឥ",
					"ි", "ඉ", "𑀺", "𑀇",  "𑄨" },
		["ī"]  = { "ี",  "ी", "ई",  "ী", "ঈ", "ီ", "ဳ", "ဤ", "ᩦ", "ᩎ", "ີ",   "ី", "ឦ",
					"ී", "ඊ", "𑀻", "𑀈",  "𑄩" },
		["u"]  = { "ุ",  "ु", "उ",  "ু", "উ", "ု", "ဥ",  "ᩩ", "ᩏ",  "ຸ",        "ុ", "ឧ",
					"ු", "උ", "𑀼", "𑀉",  "𑄪" },
		["ū"]  = { "ู",  "ू", "ऊ",  "ূ", "ঊ", "ူ", "ဦ",  "ᩪ", "ᩐ",  "ູ",        "ូ", "ឨ", "ឩ",
					"ූ", "ඌ", "𑀽", "𑀊",  "𑄫" },
		["ah"] = { "ะ",                                           "ະ"},
	},
	["two"] = {
		-- key(Ln) Thai      Deva    Beng       Mymr       Lana          Laoo        Khmr
		--         Sinh     Brah   Cakm
		["ar"] = { "รฺ", "ัร",  "र्",     "র্",      "ရ်",       "ᩁ᩺", "ᩁ᩼", "ຣ໌", "ຣ຺", "ັຣ",   "រ៑",
					"ර්",     "𑀭𑁆",  "𑄢𑄴"},
		["as"] = { "สฺ", "ัส", "स्",    "স্",      "သ်",      "ᩈ᩺", "ᩈ᩼",  "ສ໌", "ສ຺", "ັສ",  "ស៑",
					"ස්",    "𑀲𑁆",  "𑄥𑄴"  },
		["an"] = { "นฺ", "ัน", "न्",    "ন্",      "န်",       "ᨶ᩺", "ᨶ᩼",  "ນ໌", "ນ຺", "ັນ",   "ន៑",
					"න්",     "𑀦𑁆", "𑄚𑄴"},
		ent    = { "นต",										  "ນຕ"},
		["in"] =  { "ิน",                                          "ິນ"},
	},
	three = {
		-- key(Ln) Thai Deva       Beng       Mymr       Lana     Laoo       Khmr
		--         Sinh     Brah  Cakm
		ant   =  { "ันต" ,                                         "ັນຕ"},
		ent   =  {},
		ont   =  {},
		["in"] =  { "ินฺ", "िन्",      "িন্",     "ိန်",        "ᩥᨶ᩺",     "ິນ຺",		"ិន៑",
					"ින්",    "𑀺𑀦𑁆", "𑄨𑄚𑄴" },
	},
	four = {
		-- key(Ln) Thai Deva       Beng       Mymr       Lana     Laoo       Khmr
		--	       Sinh        Brah   Cakm
		ant   =  { "นฺตฺ", "न्त्",     "ন্ত্",      "န္တ်",       "ᨶ᩠ᨲ᩺", "ᨶ᩠ᨲ᩼", "ນ຺ຕ໌", "ນ຺ຕ຺", "ន្ត៑",
					"න්ත්",     "𑀦𑁆𑀢𑁆",  "𑄚𑄴𑄖𑄴"   },
		vant  =  { "วันต",                                         "ວັນຕ" },
		mant  =  { "มันต",                                         "ມັນຕ" },
	},
	five = { -- 'ent' and 'ont' are discontiguous for Thai and Lao.  Assume NFC (as above).
		-- key(Ln) Thai Deva       Beng       Mymr       Lana        Laoo          Khmr
		--         Sinh        Brah  Cakm
		antT  =  {                                                                       "න‍්ත්"          },
		vant  =  { "วนฺตฺ", "वन्त्",  "ৱন্ত্","ৰন্ত্",  "ွန္တ်", "ဝန္တ်", "ᩅᨶ᩠ᨲ᩺", "ᩅᨶ᩠ᨲ᩼", "ວນ຺ຕ຺", "ວນ຺ຕ໌",  "វន្ត៑",
					"වන්ත්",   "𑀯𑀦𑁆𑀢𑁆", "𑅇𑄚𑄴𑄖𑄴"      },
		mant  =  { "มนฺตฺ", "मन्त्",   "মন্ত্",     "မန္တ်",       "ᨾᨶ᩠ᨲ᩺", "ᨾᨶ᩠ᨲ᩼", "ມນ຺ຕ຺", "ມນ຺ຕ໌", "មន្ត៑",
                                                        "ᩜᨶ᩠ᨲ᩺", "ᩜᨶ᩠ᨲ᩼",
					"මන්ත්",   "𑀫𑀦𑁆𑀢𑁆", "𑄟𑄚𑄴𑄖𑄴"},
		ent   =  {         "ेन्त्",   "েন্ত্",    "ေန္တ်",      "ᩮᨶ᩠ᨲ᩺", "ᩮᨶ᩠ᨲ᩼",             "េន្ត៑",
		                  "एन्त्",   "এন্ত্",     "ဧန္တ်",       "ᩑᨶ᩠ᨲ᩺",  "ᩑᨶ᩠ᨲ᩼",              "ឯន្ត៑",
					"ෙන්ත්",  "𑁂𑀦𑁆𑀢𑁆",   "𑄬𑄚𑄴𑄖𑄴"  ,
					"එන්ත්",   "𑀏𑀦𑁆𑀢𑁆"     },
		ont   =  {        "ोन्त्",   "োন্ত্",   "ာန္တ်", "ါန္တ်", "ᩣᨶ᩠ᨲ᩺", "ᩣᨶ᩠ᨲ᩼",           "ោន្ត៑",
                                                         "ᩤᨶ᩠ᨲ᩺", "ᩤᨶ᩠ᨲ᩼", 
		                  "ओन्त्",  "ওন্ত্",     "ဩန္တ်",      "ᩰᨶ᩠ᨲ᩺", "ᩰᨶ᩠ᨲ᩼",             "ឲន្ត៑",
                                                         "ᩒᨶ᩠ᨲ᩺", "ᩒᨶ᩠ᨲ᩼",                                          
					"ොන්ත්",   "𑁄𑀦𑁆𑀢𑁆", "𑄮𑄚𑄴𑄖𑄴",    
					"ඔන්ත්",   "𑀑𑀦𑁆𑀢𑁆"},
	},
	six  = {
		-- key(Ln) Thai Deva       Beng       Mymr       Lana        Laoo          Khmr
		--         Sinh     Brah
		vantT  =  {"වන‍්ත්"        },
		mantT  =  {"මන‍්ත්"   },
		entT   =  {"ෙන‍්ත්",     
		           "එන‍්ත්"     },
		ontT   =  {"ොන‍්ත්",     
	               "ඔන‍්ත්"   },
	},
}

function export.detectEnding(stem, options)
-- Correct checking order is last 6, last 5, last 4, last 3, last 2, last 1, but we
-- Can do slightly better by knowing the data.
	local oneLetter = sub(stem, -1)
	for key, arr in pairs(endings.one) do
		if oneLetter == key then
			return key
		end
		for _, val in ipairs(arr) do
			if oneLetter == val then
				return key
			end
		end
	end
-- Check Latin script first
	local fourLetters = sub(stem, -4)
	if 'mant' == fourLetters or 'vant' == fourLetters then
		return fourLetters
	end
	local wordEnd = sub(stem, -6)
	for key, arr in pairs(endings.six) do
--		if wordEnd == key then
--			return key
--		end
		for _, val in ipairs(arr) do
			if wordEnd == val then
				return key
			end
		end
	end
	wordEnd = sub(stem, -5)
	for key, arr in pairs(endings.five) do
--		if wordEnd == key then
--			return key
--		end
		for _, val in ipairs(arr) do
			if wordEnd == val then
				return key
			end
		end
	end
	for key, arr in pairs(endings.four) do
		if fourLetters == key then return key end
		for _, val in ipairs(arr) do
			if fourLetters == val then
-- Scripts with visually ordered preposed vowels have not been checked thoroughly
				if key == 'ant' and
				(oneLetter == u(0x0E3A) or oneLetter == u(0xECC) or
				 oneLetter == u(0x0EBA)) then
					local pm6 = sub(stem, -6, -6)
					if match(pm6, '[เโເໂ]') then -- 1 char onset
						return 'ent' -- 'ent' for 'ont' matters not.
					elseif match(pm6, '['..u(0x0E3A)..u(0x0EBA)..']')
					and match(sub(stem, -8, -8), '[เโເໂ]') then -- 2 char onset
						return 'ent' -- 'ent' for 'ont' matters not.
					else
						return key
					end
				else
					return key
				end
			end
		end
	end
	local threeLetters = sub(stem, -3)
	for key, arr in pairs(endings.three) do
		if threeLetters == key then
			return key
		end
		for _, val in ipairs(arr) do
			if threeLetters == val then return key; end
		end
	end
	local impl = options and options.impl or 'yes' -- Fudge to pass old tests.
	wordEnd = sub(stem, -2)
	for key, arr in pairs(endings.two) do
		if wordEnd == key then
			return key
		end
		for _, val in ipairs(arr) do
			if wordEnd == val then
				if key == 'ent' then
					local pm3 = sub(stem, -3, -3)
					if match(pm3, '['..u(0x0e31)..u(0xeb1)..']') then
						-- Recognise below
						return 'ant'
					elseif match(sub(stem, -4, -3), '[เโເໂ][ก-ฮກ-ຮ]') then -- 1 char onset
						return 'ent'
					elseif match(sub(stem, -5, -3), '[เโເໂ][ก-ฮກ-ຮ][ก-ฮກ-ຮ]') then -- 2 char onset
						return 'ent'
					end
				elseif wordEnd == "ิน" or wordEnd ==  "ິນ" then
					if impl == 'yes' then
						return 'a'
					elseif impl == 'both' then
						error("Does "..stem.." end in -in or -ina?")
					else
						return key
					end
				else
					return key
				end
			end
		end
	end

	return "a"
end

-- Selectively converts touching to conjoining.
local sinh_flip = {["කⒿ‍්ව"]="ක්‍ව",
                   ["තⒿ‍්ථ"]="ත්‍ථ", ["තⒿ‍්ව"]="ත්‍ව",
                   ["නⒿ‍්ථ"]="න්‍ථ", ["නⒿ‍්ද"]="න්‍ද", ["නⒿ‍්ධ"]="න්‍ධ", ["නⒿ‍්ව"]="න්‍ව",
}
-- Argument option is optional.
function export.joinSuffix(scriptCode, stem, suffixes, option)

	if stem == nil then
		errmes = {}
		table.insert(errmes, 'joinSuffix('..scriptCode)
		table.insert(errmes, tostring(stem))
		table.insert(errmes, tostring(suffixes))
		table.insert(errmes, tostring(option)..')')
		error(table.concat(errmes, ','))
	end

	local output = {}
	local term
	local aa = option and option.aa or "default"
	local join, term2

	if scriptCode == 'Lana' or scriptCode == 'Mymr' or scriptCode == 'Sinh' then
		join = 'Ⓙ'
	else
		join = ""
	end

	for _,suffix in ipairs(suffixes) do
		if match(suffix, "^⌫⌫⌫⌫⌫") then --backspace
			term = sub(stem, 1, -6) .. join .. sub(suffix, 6, -1)
		elseif match(suffix, "^⌫⌫⌫⌫") then --backspace
			term = sub(stem, 1, -5) .. join .. sub(suffix, 5, -1)
		elseif match(suffix, "^⌫⌫⌫") then --backspace
			term = sub(stem, 1, -4) .. join .. sub(suffix, 4, -1)
		elseif match(suffix, "^⌫⌫") then --backspace
			term = sub(stem, 1, -3) .. join .. sub(suffix, 3, -1)
		elseif match(suffix, "^⌫") then --backspace
			term = sub(stem, 1, -2) .. join .. sub(suffix, 2, -1)
		else
			term = stem .. join .. suffix
		end

		--note: Sinh conjuncts are already ready.
		if scriptCode == "Thai" then
			term = gsub(term, "(.)↶([เโ])", "%2%1") --swap
		elseif scriptCode == "Mymr" then
--			term = gsub(term, "င္", "င်္") -- Pali doesn't have -Vr mid-word like Sanskrit, so no need to include repha.
			term = gsub(term, "(င်္)([ခဂငဒပဝ])(ေ?)Ⓙာ", "%1%2%3ါ") -- redundant!
--			term = gsub(term, "္[ယရ]", { ["္ယ"] = "ျ", ["္ရ"] = "ြ" }) --these not need tall aa
			term = gsub(term, "Ⓙ္[ယရ]", { ["Ⓙ္ယ"] = "ျ", ["Ⓙ္ရ"] = "ြ" }) --these not need tall aa
			term = gsub(term, "^([ခဂငဒပဝ])Ⓙ(ေ?)ာ", "%1%2ါ")
			term = gsub(term, "([^္])([ခဂငဒပဝ])Ⓙ(ေ?)ာ", "%1%2%3ါ")
			term = gsub(term, "([^္])Ⓙ([ခဂငဒပဝ])(ေ?)ာ", "%1%2%3ါ")
			term = gsub(term, "([ခဂငဒပဝ])(္[က-အဿ])Ⓙ(ေ?)ာ", "%1%2%3ါ")
			term = gsub(term, "([ခဂငဒပဝ])Ⓙ(္[က-အဿ])(ေ?)ာ", "%1%2%3ါ")
--			term = gsub(term, "္[ဝဟ]", { ["္ဝ"] = "ွ", ["္ဟ"] = "ှ" })
--			term = gsub(term, "ဉ္ဉ", "ည")
--			term = gsub(term, "သ္သ", "ဿ")
			term = gsub(term, 'Ⓙ', '')
		elseif scriptCode == "Lana" then
			if aa == "both" then
				term2 = gsub(term, 'Ⓙ', '')
		    end
			if aa == "tall" or aa == "both" then
				term = gsub(term, "^([ᨣᨴᨵᨷᩅ])Ⓙ(ᩮ?)ᩣ", "%1%2ᩤ")
				term = gsub(term, "([^᩠])([ᨣᨴᨵᨷᩅ])Ⓙ(ᩮ?)ᩣ", "%1%2%3ᩤ")
				term = gsub(term, "([^᩠])Ⓙ([ᨣᨴᨵᨷᩅ])(ᩮ?)ᩣ", "%1%2%3ᩤ")
				term = gsub(term, "([ᨣᨴᨵᨷᩅ])(᩠[ᨠ-ᩌᩔ])Ⓙ(ᩮ?)ᩣ", "%1%2%3ᩤ")
				term = gsub(term, "([ᨣᨴᨵᨷᩅ])Ⓙ(᩠[ᨠ-ᩌᩔ])(ᩮ?)ᩣ", "%1%2%3ᩤ")
				term = gsub(term, "(ᨻᩛ)Ⓙ(ᩮ?)ᩣ", "%1%2ᩤ")
				term = gsub(term, 'Ⓙ', '')
				if aa == "tall" then
					term2 = term
				end
			elseif aa == "round" then
				term = gsub(term, 'Ⓙ', '')
				term2 = term
			elseif aa == "default" then
--				term = gsub(term, "ᨦ᩠", "ᩘ")
				term = gsub(term, "^([ᨣᨴᨵᨷᩅ])Ⓙ(ᩮ?)ᩣ", "%1%2ᩤ")
				term = gsub(term, "([^᩠])([ᨣᨴᨵᨷᩅ])Ⓙ(ᩮ?)ᩣ", "%1%2%3ᩤ")
				term = gsub(term, "([^᩠])Ⓙ([ᨣᨴᨵᨷᩅ])(ᩮ?)ᩣ", "%1%2%3ᩤ")
				term = gsub(term, "([ᨣᨴᨵᨷᩅ])(᩠[ᨠ-ᩌᩔ])Ⓙ(ᩮ?)ᩣ", "%1%2%3ᩤ")
				term = gsub(term, "([ᨣᨴᨵᨷᩅ])Ⓙ(᩠[ᨠ-ᩌᩔ])(ᩮ?)ᩣ", "%1%2%3ᩤ")
--				term = gsub(term, "᩠[ᩁᩃ]", { ["᩠ᩁ"] = "ᩕ", ["᩠ᩃ"] = "ᩖ" })
--				term = gsub(term, "([ᨭ-ᨱ])᩠ᨮ", "%1ᩛ")
--				term = gsub(term, "([ᨷ-ᨾ])᩠ᨻ", "%1ᩛ")
--				term = gsub(term, "ᩈ᩠ᩈ", "ᩔ")
				term = gsub(term, 'Ⓙ', '')
				term2 = term
			else
				error('Parameter aa has undefined value "'..aa..'".')
			end
			if term ~= term2 then table.insert(output, term2) end
		elseif scriptCode == "Beng" then
			term = gsub(term, "ৰ্", "ৰ"..u(0x200d).."্") -- ৰ্(v-) needs ZWJ to display correctly
		elseif scriptCode == "Laoo" then
			term = gsub(term, "(.຺?)↶([ເໂ])", "%2%1")
		elseif scriptCode == "Sinh" then
-- Assume cluster formation appends the joiner.
			term = gsub(term, "[කතන]Ⓙ..[ථදධව]", sinh_flip)
			term = gsub(term, 'Ⓙ', '')
		end
        table.insert(output, term)
	end

	return output

end

function export.joinSuffixes(scriptCode, stem, pattern, option)
	local forms = {}
	for i,_ in ipairs(rows) do
		forms[2*i-1] = export.joinSuffix(scriptCode, stem, pattern[2 * i - 1],
										option)
		forms[2*i]   = export.joinSuffix(scriptCode, stem, pattern[2 * i],
										option)
	end
	return forms
end

function export.orJoin(script, list, options) -- options is optional!
	local output = {};
	local scriptCode = script:getCode()
	local showtr = options and options.showtr or 'plain'
	local sep = ''
	if 'Latn' == scriptCode then showtr = 'none' end
	for _,term in ipairs(list) do
		local item = {sc = script, lang = lang, term = term} -- links.full_link() is at liberty to trash this table.
		ti(output, sep)
		sep = " <small style=\"color:888\">or</small> "
		if showtr == 'none' then
			item.tr = '-'
		else
			if options and options.subst then
-- Legal stuff:
-- The contents of this block were lifted from English Wiktionary Module:usex lines 97 to 101
-- of 3 July 2021, which see for attribution, and then localised.
				local substs = mw.text.split(options.subst, ",")
				for _, subpair in ipairs(substs) do
					local subsplit =
						mw.text.split(subpair, find(subpair, "//") and "//" or "/")
					term = gsub(term, subsplit[1], subsplit[2])
				end
			end
			local aslat = nil
			if (scriptCode == 'Thai' and options and options.impl == 'no' or
				scriptCode == 'Laoo') then
				m_translit = m_translit or require("Module:pi-translit")
				aslat = m_translit.trwo(term, 'pi', scriptCode, options)
			elseif term ~= item.term then -- Must complete transliteration
				aslat = (lang:transliterate(term, script))
			end
			if showtr == 'plain' then
				item.tr = aslat
			elseif showtr == 'link' then
				aslat = aslat or (lang:transliterate(term, script))
				item.tr = links.full_link({term = aslat, lang = lang})
			else
				item.tr = '-'
				error('Bad value for option showtr.')
			end
		end
		ti(output, links.full_link(item))
	end

	return table.concat(output)
end

-- convert Latin script inflections to another script
-- C2 is second character of pseudostem.  Ignored if NIL.
local function convert_one_set(stem, nstrip, suffixes, sc, impl, c2)
	local form, pre
	local strip = string.rep("⌫", nstrip)
	local option = {impl = impl}
	local xlitend = {}
	form = export.joinSuffix('Latn', stem, suffixes)
	for ia, va in pairs(form) do
		local altform = sub(to_script(va..'#', sc, option), 1, -2)
-- Special handling is needed for a preposed vowel.
		pre = match(altform, "^[เโເໂ]")
		if pre then
			xlitend[ia] = strip .. "↶" .. pre .. sub(altform, 3)
-- Quick cheat for Myanmar script variants.
		elseif c2 and c2 == sub(altform,2,2) then
			xlitend[ia] = sub(strip, 2) .. sub(altform, 3)
-- Back to the normal case.
		else
			xlitend[ia] = strip .. sub(altform, 2)
		end
	end
	return xlitend
end
		
local convert_suffixes = function(stem, nstrip, suffixes, sc, impl)
	local xlitend = {}
	to_script = to_script or require("Module:pi-Latn-translit").tr
	local c2
	if nstrip > 0 and sc == 'Mymr' then
		 c2 = sub(to_script(stem, sc, option), 2, 2)
	end
-- Seemingly #suffixes doesn't work because the module is loaded!
-- Testing didn't reveal a problem, but avoiding it solved the problem!
--	for k = 1, #suffixes do
if #suffixes ~= 16 then error('#suffixes = '..tostring(#suffixes)) end
	for k, _ in ipairs(suffixes) do
		xlitend[k] = convert_one_set(stem, nstrip, suffixes[k], sc, impl, c2)
	end
	return xlitend
end

local liapise = function(retval, liap) -- Change Lao abl/ins plural
-- Copy list to avoid changing data from data module.
	local oval = retval retval = {}
	for _, forms in ipairs(oval) do table.insert(retval, forms) end
	local dob = nil local dobh = nil local sena = nil
	if liap == 'b' then
		dob = 1
	elseif liap == 'bh' then
		dobh = 1
	elseif liap == 'b.' then
		sena = 1
	elseif liap == 'bbh' then
		dob = 1 dobh = 1
	elseif liap == 'bb.' then
		dob = 1 sena = 1
	elseif liap == 'bhb.' then
		dobh = 1 sena = 1
	elseif liap == 'none' then
	elseif liap == 'all' or liap == 'bbhb.' then
		dob = 1 dobh = 1 sena = 1
	else
		error('Value "'..liap..'" of liap is not understood.')
	end
	for caseno = 6, 10, 4 do
		local forms = retval[caseno]
		local nuforms = {}
		for _, form in ipairs(forms) do
			if sub(form, -2, -1) == 'ຠິ' then
				if dob  then table.insert(nuforms, sub(form,1,-3)..'ພິ') end
				if dobh then table.insert(nuforms, form) end
				if sena then table.insert(nuforms, sub(form,1,-3)..'ພ຺ິ') end
			else
				table.insert(nuforms, form)
			end
		end
		retval[caseno] = nuforms
	end
	return retval
end

local yselect = function(retval, yval, nvals) -- Change Lao case ending
-- Copy list to avoid changing data from data module.
	local oval = retval retval = {}
	for _, forms in ipairs(oval) do table.insert(retval, forms) end
	local yung = nil local yaa = nil
	if yval == 'both' then
		yung = 1
		yaa  = 1
	elseif yval == 'ຍ' then
		yung = 1
	elseif yval == 'ຢ' then
		yaa = 1
	elseif yval == 'yung' then
		yung = 1
	elseif yval == 'yaa' then
		yaa = 1
	else
		error('Value "'..yval..'" of argument y is not understood.')
	end
	for caseno = 1, nvals do
		local forms = retval[caseno]
		local nuforms = {}
		for _, form in ipairs(forms) do
			if yung then
				local s = gsub(form, '[ຍຢ]', 'ຍ') -- gsub() is a bad actual arg!
				table.insert(nuforms, s)
			end
			if yaa then
				local s = gsub(form, '[ຍຢ]', 'ຢ')
				table.insert(nuforms, s)
			end
		end
		retval[caseno] = nuforms
	end
	return retval
end


function export.arrcat_nodup(a1, a2) -- Concatenate two arrays without duplication
-- One of the arrays may have been 'loaded', so cannot use the # operator.
	local n1 = 0
	local cat = {}
	for _, a1v in ipairs(a1) do
		n1 = n1 + 1
		cat[n1] = a1v
	end
	for _, a2v in ipairs(a2) do
		local met = false
		for j = 1, n1 do
			if a2v == cat[j] then
				met = true
				break
			end
		end
		if not met then
			n1 = n1 + 1
			cat[n1] = a2v
		end
	end
	return cat
end

local arrcat = export.arrcat_nodup

local both_sets = function(scriptCode, ending, g, option)
	option.impl= 'yes'
	iset = export.getSuffixes(scriptCode, ending, g, option)
	option.impl = 'no'
	eset = export.getSuffixes(scriptCode, ending, g, option)
	retval = {}
--	error('i='..iset[3][1]..' e='..eset[3][1])
	for ic = 1, 16 do
		retval[ic] = arrcat(iset[ic], eset[ic])
	end
--	error('m1='..'<'..tostring(retval[1][1])..'>'..' m2='..'<'..tostring(retval[1][2])..'>')
	return retval
end
		
local function wayToConvert(ending, impl)
	local antlen = {yes = 4, no = 3} -- Length by implicitness.
	local inlen  = {yes = 3, no = 2}
	local way = {
		a     = {pseudoStem = 'ka',  ndel = 0},
		ar    = {pseudoStem = 'kar', ndel = 2},
		as    = {pseudoStem = 'kas', ndel = 2},
		an    = {pseudoStem = 'kan', ndel = 2},
		ant   = {pseudoStem = 'kant', ndel = antlen[impl]}, 
		ent   = {pseudoStem = 'kant', ndel = antlen[impl]}, 
		ont   = {pseudoStem = 'kant', ndel = antlen[impl]}, 
		mant  = {pseudoStem = 'kant', ndel = antlen[impl]}, 
		vant  = {pseudoStem = 'kant', ndel = antlen[impl]}, 
		antT  = {pseudoStem = 'kant', ndel = 5}, 
		entT  = {pseudoStem = 'kant', ndel = 5}, 
		ontT  = {pseudoStem = 'kant', ndel = 5}, 
		mantT = {pseudoStem = 'kant', ndel = 5}, 
		vantT = {pseudoStem = 'kant', ndel = 5}, 
		["ā"] = {pseudoStem = 'kā', ndel = 1},
		i     = {pseudoStem = 'ki', ndel = 1},
		["ī"] = {pseudoStem = 'kī', ndel = 1},
		["in"]= {pseudoStem = 'kin', ndel = inlen[impl]},
		u     = {pseudoStem = 'ku', ndel = 1},
		["ū"] = {pseudoStem = 'kū', ndel = 1},
	}
	if impl == 'no' then
		way.a   = {pseudoStem = 'ka',  ndel = 1}
		way.ent = {pseudoStem = 'knt', ndel = 2}
		way.ont = {pseudoStem = 'knt', ndel = 2}
	end
	return way[ending]
end

function export.getSuffixes(scriptCode, ending, g, option)
	local impl = option and option.impl or 'yes'
	if (impl == 'both') then
		return both_sets(scriptCode, ending, g, option)
	end
	local pattern = load("Module:pi-decl/noun/" .. scriptCode)
	local applicable = pattern and pattern[ending] and pattern[ending][g]
	if applicable then
		if impl == 'yes' or ending == 'ah' then
			return applicable
		end
	elseif 'Latn' == scriptCode then
		return nil
	elseif 'ah' == ending then
		ending = 'a'
		impl = 'no'
	end
	pattern = require("Module:pi-decl/noun/Latn") -- Why doesn't load work with testcases?
	local tabulated_ending = ending
	if 'T' == sub(ending, -1) then
		tabulated_ending = sub(ending, 1, -2)
	end
	applicable = pattern and pattern[tabulated_ending] and
					pattern[tabulated_ending][g]
	if not applicable then
		error('Not even Latin script has ' .. g .. ' -'..tabulated_ending..
			  ' endings.')
		return nil -- If you don't like the message above!
	end
	way = wayToConvert(ending, impl)
	if not way then return nil end
	return convert_suffixes(way.pseudoStem, way.ndel, applicable,
							scriptCode, impl)
end

function export.present(stem, g, forms, number, options) -- options is optional
	local gmark, dos, dop
	if 'no' == g then
		gmark = ''
	else
		gmark = ' (' .. genders[g] .. ')'
	end
	if not number or number == 'both'then
		dos = 1; dop = 1
	elseif number == 's' then
		dos = 1; dop = nil;
	elseif number == 'p' then
		dos = nil; dop = 1;
	else
		error('Parameter "number" has meaningless value "'..number..'".' )
	end
	local output = {}
	table.insert(output, '<div class="NavFrame" style="min-width:30%"><div class="NavHead" style="background:var(--wikt-palette-lightblue,#d9ebff)">Declension table of "' .. stem .. '"' .. gmark..'</div><div class="NavContent">')
	table.insert(output, '<table class="inflection-table" style="background:var(--wikt-palette-paleblue,#f8f9fa);text-align:center;width:100%"><tr><th style="background:var(--wikt-palette-cyan,#eaffff)">Case \\ Number</th>')
	if dos then
		table.insert(output, '<th style="background:var(--wikt-palette-cyan,#eaffff)">Singular</th>')
	end
	if dop then
		table.insert(output, '<th style="background:var(--wikt-palette-cyan,#eaffff)">Plural</th></tr>')
	end

	for i,v in ipairs(rows) do
		if #forms[2*i-1] > 0 or #forms[2*i] > 0 then
			table.insert(output, "<tr><td style=\"background:var(--wikt-palette-cyan,#eaffff)\">" .. v .. "</td>")
			if dos then
				table.insert(output, "<td>")
				table.insert(output, export.orJoin(currentScript, forms[2 * i - 1], options))
				table.insert(output, "</td>")
			end
			if dop then
				table.insert(output, "<td>")
				table.insert(output, export.orJoin(currentScript, forms[2 * i], options))
				table.insert(output, "</td>")
			end
			table.insert(output, "</tr>")
		end
	end

	table.insert(output, "</table></div></div>")
	return table.concat(output)
end

local function unwritten() error('Code missing.') end

local function liapise_one_set(set, liap)
	local forms = {	{}, {}, {}, {}, {}, set,
					{}, {}, {}, {}, {},
					{}, {}, {}, {}, {} }
	local modified = liapise(forms, liap)
	return modified[6]
end

local function modify_form_set(stem, ending, name, caseno, forms, at)
	local ipalts = at[name]
	local way = at[name..'_mod']
	to_script = to_script or require("Module:pi-Latn-translit").tr
	if ipalts and #ipalts > 0 then
		local alts = {}
		for j, v in ipairs(ipalts) do
			local c1 = string.sub(v,1,1)
			local vsc = lang:findBestScript(v):getCode()
			if vsc == 'None' then
				vsc = at.sc and sc or vsc
			end
			if '+' == c1 then
				local vext
				v = string.sub(v,2)
				if vsc ~= 'Latn' then
					vext = {at.dc and dc(v) or v}
				elseif scriptCode ~= 'Latn' then
					local impls
					if at.impl == 'both' then
						impls = {'yes', 'no'}
					else
						impls = {at.impl}
					end
					vext = {}
					for _, impl in ipairs(impls) do
						local cvtway = wayToConvert(ending, impl)
						local vset = convert_one_set(cvtway.pseudoStem, cvtway.ndel,
                    								{v}, scriptCode, impl, nil)
						vext = arrcat(vext, vset)
					end
					if scriptCode == 'Laoo' then
						local vexset = yselect({vext}, at.y, 1)
						vext = vexset(1)
					end
				else
					vext = {v}
				end
				if scriptCode == 'Laoo' and vsc == 'Latn'
				and (caseno == 6 or caseno == 10) then
					vext = liapise_one_set(vext, at.liap)
				end
				local vext = export.joinSuffix(scriptCode, stem, vext, at)
				for _, vv in ipairs(vext) do ti(alts, vv) end
			elseif vsc == scriptCode then
				ti(alts, v)
			elseif vsc == 'Latn' then
-- TODO: Sane Myanmar and Lao script support.
				local options = {}
				local vext = {}
				if at.impl and at.impl == 'both' then
					options.y = at.y -- Probably ineffective
					options.impl = 'yes'
					ti(vext, to_script(v, scriptCode, options))
					options.impl = 'no'
					ti(vext, to_script(v, scriptCode, options))
				else
					ti(vext, to_script(v, scriptCode, options))
				end
				if scriptCode == 'Laoo' and vsc == 'Latn'
				and (caseno == 6 or caseno == 10) then
					vext = liapise_one_set(vext, at.liap)
					local vexset = yselect({vext}, at.y, 1)
					vext = vexset(1)
				end
				for _, vv in ipairs(vext) do ti(alts, vv) end
			else
				ti(alts, v) -- Go ahead anyway
			end
		end
		if 'after' == way then 
			forms[caseno] = arrcat(forms[caseno], alts)
		elseif 'before' == way then
			forms[caseno] = arrcat(alts, forms[caseno])
		elseif 'replace' == way then
			forms[caseno] = alts;
		elseif 'blank' == way then
			-- Issue warning about alts?
			forms[caseno] = {} 
		else
			error('Bad value for parameter '..name..'_mod')
		end
	elseif 'blank' == way then
		forms[caseno] = {}
	end
end

local function modify(stem, ending, forms, args)
	local mod_default = 'after'
	local params = {
		[1] = {alias_of = 'stem'},
		[2] = {alias_of = 'ending'},
		[3] = {alias_of = 'g'},
		stem = {},
		ending = {},
		g = {required = true},
		gender = {alias_of = 'g'},
		v = {},
		variation = {alias_of = 'v'},
        label = {},
        number = {},
        showtr = {},
		subst = {},
		sc = {},

		aa = {default = 'default'},
		liap = {default = 'default'},
		impl = {default = 'yes'},
		y = {default = 'default'},

		nonom = {type = 'boolean'},
		noms = {list = true},
		noms_mod = {default = mod_default},
		nomp = {list = true},
		nomp_mod = {default = mod_default},
		
		noacc = {type = 'boolean'},
		accs = {list = true},
		accs_mod = {default = mod_default},
		accp = {list = true},
		accp_mod = {default = mod_default},
		
		noins = {type = 'boolean'},
		inss = {list = true},
		inss_mod = {default = mod_default},
		insp = {list = true},
		insp_mod = {default = mod_default},
		
		nodat = {type = 'boolean'},
		dats = {list = true},
		dats_mod = {default = mod_default},
		datp = {list = true},
		datp_mod = {default = mod_default},
		
		noabl = {type = 'boolean'},
		abls = {list = true},
		abls_mod = {default = mod_default},
		ablp = {list = true},
		ablp_mod = {default = mod_default},
		
		nogen = {type = 'boolean'},
		gens = {list = true},
		gens_mod = {default = mod_default},
		genp = {list = true},
		genp_mod = {default = mod_default},
		
		noloc = {type = 'boolean'},
		locs = {list = true},
		locs_mod = {default = mod_default},
		locp = {list = true},
		locp_mod = {default = mod_default},
		
		novoc = {type = 'boolean'},
		vocs = {list = true},
		vocs_mod = {default = mod_default},
		vocp = {list = true},
		vocp_mod = {default = mod_default},
	}
	local at = m_parameters.process(args, params)
	if ending == 'ah' then
		at.impl = 'no'
	end
	for i, v in ipairs(rows) do
		local name = string.lower(string.sub(v,1,3))
		if at['no'..name] then
			forms[2*i] = {}
			forms[2*i-1] = {}
		else
            modify_form_set(stem, ending, name..'s', 2*i-1, forms, at)
            modify_form_set(stem, ending, name..'p', 2*i,   forms, at)
		end
	end
	return forms;
end

function export.show(frame)
	local args = frame:getParent().args
	local PAGENAME = mw.title.getCurrentTitle().text
	local stem = args[1] or args["stem"] or PAGENAME
	currentScript = lang:findBestScript(stem)
	scriptCode = currentScript:getCode()
	if scriptCode == "None" and args["sc"] then
		scriptCode = args["sc"]
		currentScript = require("Module:scripts").getByCode(scriptCode, "No such script as "..scriptCode)
	end
	local g = args[3] or args["g"] or args["gender"] -- for each gender only
	local variation = args["v"] or args["variation"] -- for some scripts

	if not g then
		error("A gender is required to display proper declensions.")
	end

	local lookup_g = g
	if 'no' == lookup_g then lookup_g = 'm' end -- Arbitrary!
	local option = {impl = args["impl"] or 'yes'}
	local xlit_options = {}
	xlit_options.impl = option.impl
	xlit_options.showtr = args.showtr
	local ending = args[2] or args["ending"] or export.detectEnding(stem, option)
	if ending == 'ah' then xlit_options.impl = 'no' end
	local selectedPattern =
			export.getSuffixes(scriptCode, ending, lookup_g, option)
	if args["liap"] and (scriptCode == 'Laoo') then
		selectedPattern = liapise(selectedPattern, args["liap"])
	end
	if args.y and (scriptCode == 'Laoo') then
		selectedPattern = yselect(selectedPattern, args.y, 16)
		xlit_options.y = args.y
	end
	option.aa = args["aa"] -- Reusable!
	local forms = export.joinSuffixes(scriptCode, stem, selectedPattern, option)
	modify(stem, ending, forms, args)
	for ic = 1, 16 do forms[ic] = arrcat({}, forms[ic]) end -- Remove duplicates.
	xlit_options.subst = args["subst"]
--	for name, _ in pairs(_G) do mw.addWarning('Global '..name) end
	return export.present(args["label"] or stem, g, forms, args["number"], xlit_options)
end

return export