Module:pi-decl/noun

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

Purpose[edit]

This module provides inflection tables for Pali for nouns, adjectives and pronouns. For pronouns, one currently uses the interface for nouns, while for adjectives one uses separate invocations for each gender.

Some functions are exported from this module to service the testing of noun inflection. The module also provides utility functions for the conjugation of verbs.

Normal Use[edit]

The normal way to use this module is to invoke the template {{pi-decl-noun}}, which see for the interface. This invokes the exported function show.

Data tables[edit]

The primary data table for the inflections is the data module Module:pi-decl/noun/Latn, which contains the Latin script tables. These are supplemented by identically structured tables for each of the other supported scripts. If the table for a particular paradigm is missing from one of these, the table will be generated using the transliteration functions in Module:pi-Latn-translit. The data modules for the other scripts are:

With the exception of the masculine and neuter thematic nouns, the Thai and Lao tables are not used for declension with explicit vowels.

There is no such redundant table for the Chakma script.


Deliberately Exported Functions[edit]

The following Lua functions are exported by this module:

  • orJoin()
  • joinSuffix
  • arrcat_nodup
  • present
  • show

Function orJoin[edit]

Function joinSuffix[edit]

The original idea was to share this function with the code for verb conjugation. However, the conjugation of verbs in the Thai and Lao scripts is more complicated, and there is therefore a more general function in use for verbs.

Function arrcat_nodup[edit]

Function present()[edit]

Function show()[edit]

Other exported functions[edit]

  • detectEnding()
  • joinSuffixes
  • getSuffixes
  • modify

Algorithm[edit]

The paradigm to use is determined using the script of the stem, the ending of the stem (for which there are a few conventional values - see {{pi-decl-noun}}) and gender of the stem. The script is always deduced from the script of the stem, while the ending may be supplied explicitly (in Latin script) or deduced from the stem. The gender is always supplied explicitly. The deduction of the ending from the stem is performed by function detectEnding.

The set of suffixes is obtained by function getSuffixes. This first attempts to load the paradigm from the data files. However, if the paradigm is unacceptable or missing, it will generate it itself. Paradigms from data files are only acceptable for some combinations of settings. At present, they are not acceptable for non-Roman scripts when using explicit vowels, except for the conventional ending 'ah', which denotes masculine or neuter nouns with stems in explicit -a. (The convention was chosen because the explicit vowel also represents the Sanskrit ending -aḥ.)

When paradigms are generated internally, they are converted from Latin script to the required script and implicit vowel settings. This is implemented in function convert_suffixes.

The second stage of the generation, applicable to the Lao script only, is to, where needed, convert the ablative and instrumental plural in -bhi to the correct forms. The editor specifies the correct form using the parameter |liap=.

The third stage of the generation, applicable to Lao script only, is to, where needed, convert the letter corresponding to <y> in the suffixes to the correct letter. This setting is treated as orthogonal to the choice between using or not using implicit vowels.

The endings are then attached to the stem using the function joinSuffixes. This invokes function joinSuffixes to apply the writing system-dependent rules for the attachment of suffixes. There is one user-controlled input to this process, the parameter |aa=, which is applicable to the Burmes and Tai Tham scripts.

Next, the function modify is applied to add, remove or replace the forms generated so far in accordance a list of modifications included in the invocation of {{pi-decl-noun}}.

Finally, the function present formats the list of forms for each combination of case and number. This formatting includes adding the transliteration, which is done in function orJoin. Function show then returns the inflection table for display on the page.


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"] = "masculine", ["f"] = "feminine", ["n"] = "neuter",
}
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
		item.no_check_redundant_translit = item.tr and item.tr ~= '-'
		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:#d9ebff">Declension table of "' .. stem .. '"' .. gmark..'</div><div class="NavContent">')
	table.insert(output, '<table class="inflection-table" style="background:#F9F9F9;text-align:center;width:100%"><tr><th style="background:#eff7ff">Case \\ Number</th>')
	if dos then
		table.insert(output, '<th style="background:#eff7ff">Singular</th>')
	end
	if dop then
		table.insert(output, '<th style="background:#eff7ff">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-color:#eff7ff;\">" .. 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