Module:pqm-decl

local export = {} local m_links = require("Module:links") local lang = require("Module:languages").getByCode("pqm")

-- defined below local format_links, categorize

--entry point function export.pqmdecl(frame) local params = { [1] = {},		["head"] = {alias_of = 1}, ["h"] = {alias_of = 1}, ["gender"] = {}, ["g"] = {alias_of = "gender"}, ["ending"] = {list = true}, ["e"] = {alias_of = "ending", list = true}, ["mode"] = {default = "half"}, ["m"] = {alias_of = "mode"}, ["pl"] = {list = true, allow_holes = true}, ["obv.sg"] = {list = true, allow_holes = true}, ["obv.pl"] = {list = true, allow_holes = true}, ["pos.sg"] = {list = true, allow_holes = true}, ["pos.pl"] = {list = true, allow_holes = true}, ["loc.sg"] = {list = true, allow_holes = true}, ["loc.pl"] = {list = true, allow_holes = true}, ["dim.sg"] = {list = true, allow_holes = true}, ["dim.pl"] = {list = true, allow_holes = true}, ["abs.sg"] = {list = true, allow_holes = true}, ["abs.pl"] = {list = true, allow_holes = true}, ["abs-obv.sg"] = {list = true, allow_holes = true}, ["abs-obv.pl"] = {list = true, allow_holes = true}, ["voc.sg"] = {list = true, allow_holes = true}, ["voc.pl"] = {list = true, allow_holes = true}, ["auto"] = {type = "boolean", default = "true"}, ["dim"] = {list = true}, ["add"] = {list = true}, ["drop"] = {type = "boolean", default = "false"} }

local args = require("Module:parameters").process(frame:getParent.args, params)

local data = {forms = {}, endings = {}, endingForms = {}, cascade = {}} local head = args[1] if head == nil or head == "" then data.forms.title = mw.title.getCurrentTitle.text data.forms.sg = format_links(mw.title.getCurrentTitle.text) head = mw.title.getCurrentTitle.text else data.forms.title = head data.forms.sg = format_links(head) end

pl = args["pl"]

local gender = catch(args["g"] or args["gender"], "animate") local ending = args["ending"] if #ending > 0 then if catch(ending[1]:sub(-1), "-ok") == "l" then gender = "inanimate" end end

if gender:lower == "in" or gender:lower == "inanimate" then gender = "inanimate" else gender = "animate" end

data.cascade['unm'] = {pl = {}}

-- Handle additional root forms for addKey, addVal in pairs(args["add"]) do

ending[addKey] = format_ending(ending[addKey]) local firstEnding = ending[1]

-- Decline plural local pluralForm = addVal .. format_ending_append(firstEnding, addVal) data.forms["pl." .. addKey] = format_links(pluralForm) data.endingForms[addKey] = ending[addKey] local placeholder = "pl." .. addKey table.insert(data.cascade['unm'].pl, placeholder)

end

for key, value in pairs(ending) do

local en = catch(value, "-ok") if en:lower == "p" or en:lower == "participle" then ending[key] = "verb participle" end

ending[key] = format_ending(ending[key])

-- Decline plural local pluralForm = head .. format_ending_append(ending[key], head)

-- Replace final 'q' with 'k' if following vowel is 'u'		local enVow = ending[key]:sub(2, 2) if enVow == 'u' and head:sub(-1, -1) == 'q' then pluralForm = head:sub(1, -2) .. "k" .. format_ending_append(ending[key], head) end

data.forms["pl." .. key + #args["add"]] = format_links(pluralForm) data.endingForms[key] = ending[key]

local placeholder = "pl." .. key + #args["add"] table.insert(data.cascade['unm'].pl, placeholder)

end

-- Replace auto-declined forms if pl[1] ~= nil and pl[1] ~= "" and pl[1] ~= "—" then data.forms["pl.1"] = format_links(pl[1]) end

-- Additional plural forms for I = 2, pl.maxindex do

if pl[I] ~= nil then local placeholder = "pl." .. #data.endingForms + I - 1 + #args["add"] data.forms[placeholder] = format_links(pl[I]) table.insert(data.cascade['unm'].pl, placeholder) end

end

if #ending == 0 then ending[1] = "-ok" end

local joinedEndings = ", " .. table.concat(ending, " or ")

if #ending > 2 then local leadingEndings = table.concat(ending, ', ', 1, #ending - 2) local finalTwoEndings = table.concat(ending, ' or ', #ending - 1, #ending) joinedEndings = "; " .. leadingEndings .. ", " .. finalTwoEndings end

local annotation = gender .. joinedEndings

if ending[#ending] ~= "verb participle" then annotation = annotation .. " plural" end

local proximateText = "" if gender == "animate" then proximateText = " / proximate" end

data.forms.annotation = "[" .. annotation .. " ]"	data.forms.proximateText = proximateText

local mode = catch(args["mode"]:lower, "half") local infEnabled = {"pos", "loc", "dim"}

if mode == "full" and gender == "inanimate" then infEnabled = {"pos", "loc", "dim", "abs"} elseif mode == "full" and gender == "animate" then infEnabled = {"obv", "pos", "loc", "dim", "abs", "abs-obv"} elseif mode == "miniloc" or mode == "l" then infEnabled = {"loc"} elseif mode == "minipos" or mode == "p" then infEnabled = {"pos"} elseif mode == "minidim" or mode == "d" then infEnabled = {"dim"} elseif mode == "minilocdim" or mode == "ld" or mode == "dl" then infEnabled = {"loc", "dim"} elseif mode == "miniposdim" or mode == "pd" or mode == "dp" then infEnabled = {"pos", "dim"} elseif mode == "miniobv" or mode == "o" then infEnabled = {"obv"} elseif mode == "voc" or mode == "v" then infEnabled = {"pos", "loc", "dim", "voc"} elseif mode == "mini" then infEnabled = {} end

for key, formCode in pairs(infEnabled) do

data.cascade[formCode] = {sg = {}, pl = {}} local sgForms = args[formCode .. ".sg"] local plForms = args[formCode .. ".pl"]

data.forms[formCode .. ".sg.1"] = "—" data.forms[formCode .. ".pl.1"] = "—"

if args["auto"] == false then

for I = 1, sgForms.maxindex do

if sgForms[I] ~= nil then local placeholder = formCode .. ".sg." .. I					data.forms[placeholder] = format_links(sgForms[I]) table.insert(data.cascade[formCode].sg, placeholder) end

end

for I = 1, plForms.maxindex do

if plForms[I] ~= nil then local placeholder = formCode .. ".pl." .. I					data.forms[placeholder] = format_links(plForms[I]) table.insert(data.cascade[formCode].pl, placeholder) end

end

else

-- Handle additional root forms for addKey, addVal in pairs(args["add"]) do

data.forms[formCode .. ".sg." .. addKey] = format_links(autoDeclineSg(addVal, formCode, data.endingForms[1], gender, args["drop"])) data.forms[formCode .. ".pl." .. addKey] = format_links(autoDeclinePl(addVal, formCode, data.endingForms[1], gender, args["drop"]))

table.insert(data.cascade[formCode].sg, formCode .. ".sg." .. addKey) table.insert(data.cascade[formCode].pl, formCode .. ".pl." .. addKey)

end

local addLen = #args["add"]

-- Auto-decline for endingKey, endingVal in pairs(data.endingForms) do

-- Prevent diminutive duplicates if formCode ~= "dim" or endingKey == 1 then

data.forms[formCode .. ".sg." .. endingKey + addLen] = format_links(autoDeclineSg(head, formCode, endingVal, gender, args["drop"])) data.forms[formCode .. ".pl." .. endingKey + addLen] = format_links(autoDeclinePl(head, formCode, endingVal, gender, args["drop"]))

table.insert(data.cascade[formCode].sg, formCode .. ".sg." .. endingKey + addLen) table.insert(data.cascade[formCode].pl, formCode .. ".pl." .. endingKey + addLen)

end

end

-- Replace auto-declined forms if sgForms[1] ~= nil and sgForms[1] ~= "" and sgForms[1] ~= "—" then data.forms[formCode .. ".sg.1"] = format_links(sgForms[1]) end if plForms[1] ~= nil and plForms[1] ~= "" and plForms[1] ~= "—" then data.forms[formCode .. ".pl.1"] = format_links(plForms[1]) end -- Disable forms if sgForms[1] == "d" or sgForms[1] == "disable" then data.cascade[formCode].sg = {formCode .. ".sg.1"} data.forms[formCode .. ".sg.1"] = "—" end if plForms[1] == "d" or plForms[1] == "disable" then data.cascade[formCode].pl = {formCode .. ".pl.1"} data.forms[formCode .. ".pl.1"] = "—" end -- Additional forms local edgFormLen = #data.endingForms for I = 2, sgForms.maxindex do

if sgForms[I] ~= nil then local placeholder = formCode .. ".sg." .. edgFormLen + I - 1 + addLen data.forms[placeholder] = format_links(sgForms[I]) table.insert(data.cascade[formCode].sg, placeholder) end

end

for I = 2, plForms.maxindex do

if plForms[I] ~= nil then local placeholder = formCode .. ".pl." .. edgFormLen + I - 1 + addLen data.forms[placeholder] = format_links(plForms[I]) table.insert(data.cascade[formCode].pl, placeholder) end

end end

local sgInit = data.forms[formCode .. ".sg.1"] local plInit = data.forms[formCode .. ".pl.1"] if sgInit == "—" and plInit == "—" then

for infKey, infVal in pairs(infEnabled) do

if infVal == formCode then infEnabled[infKey] = nil end

end

end

end

-- Custom diminutive forms replace existing ones if #args["dim"] > 0 then data.cascade["dim"].sg = {} data.cascade["dim"].pl = {} end

-- Diminutives for dimKey, dimVal in pairs(args["dim"]) do

local commonDimMarkers = {"oss", "uhs", "s", "ehs", "es", "ahs"} local vow = {'a', 'e', 'i', 'o', 'u'} local dimHead = head

if contains(commonDimMarkers, dimVal) then if head:sub(-1, -1) == "u" and dimVal:sub(1, 1) == "u" then dimVal = dimVal:sub(2) -- Replace final 'q' with 'k' if following vowel is 'u'			elseif dimVal:sub(1, 1) == 'u' and head:sub(-1, -1) == 'q' then dimHead = dimHead:sub(1, -2) .. 'k'			elseif contains(vow, head:sub(-1, -1)) then dimVal = "w" .. dimVal end dimVal = dimHead .. dimVal end

data.forms["dim.sg." .. dimKey] = format_links(autoDeclineSg(dimVal, 'dim', 'nil', gender, args["drop"])) data.forms["dim.pl." .. dimKey] = format_links(autoDeclinePl(dimVal, 'dim', 'nil', gender, args["drop"]))

table.insert(data.cascade["dim"].sg, "dim.sg." .. dimKey) table.insert(data.cascade["dim"].pl, "dim.pl." .. dimKey)

end

infEnabled = removeNil(infEnabled)

data.infEnabled = infEnabled return export.make_table(data)

end

function export.make_table(data) local colors = {dark = "#C0CFE4", light = "#F8F9FA"} local result = {} local tail = [=[

]=]	table.insert(result, [=[ Declension of {title}&ensp;{annotation} {|style="width:100%; margin: 0px; border-collapse: separate; border-spacing: 2px; background: {light}" class="inflection-table" cellpadding=8 ! style="width: 30%; background-color: {dark}" | ! style="width: 35%; background-color: {dark}; text-align: left; padding: 5px 8px 5px 8px" | singular ! style="width: 35%; background-color: {dark}; text-align: left; padding: 5px 8px 5px 8px" | plural ! style="background: {dark}; text-align: left; padding: 5px 8px 5px 8px" | unmarked{proximateText} ]=])
 * }
 * - style="background: {light}; text-align: left" |
 * {sg}

table.insert(result, "| ")

for key, value in pairs(data.cascade['unm'].pl) do table.insert(result, "{" .. value .. "}") if key < #data.cascade['unm'].pl then table.insert(result, " / ") end end

for key, value in pairs(data.infEnabled) do		local formatting = [=[

! style="background: {dark}; text-align: left; padding: 5px 8px 5px 8px" | {form} ]=]		local formName = format_noun_form(value) table.insert(result, (formatting:gsub("{form}", formName))) table.insert(result, "| ")
 * - style="background: {light}; text-align: left" |

-- Singular form(s) for rock, stone in pairs(data.cascade[value].sg) do table.insert(result, "{" .. stone .. "}") if rock < #data.cascade[value].sg then table.insert(result, " / ") end end

table.insert(result, "\n| ")

-- Plural form(s) for rock, stone in pairs(data.cascade[value].pl) do table.insert(result, "{" .. stone .. "}") if rock < #data.cascade[value].pl then table.insert(result, " / ") end end end

-- Tail table.insert(result, tail)

return (string.gsub(table.concat(result), "{([^}]+)}", function(code) return data.forms[code] or colors[code] end))

end

function format_links(link) if (link == nil or link == "" or link == "—") then return "—" else return m_links.full_link({lang = lang, term = link}) end end

function format_ending(ending) if ending == "" or ending == nil then ending = "-ok" elseif ending:lower == "iyik-e" then ending = "-iyik (e)" elseif ending:lower == "iyik-i" then ending = "-iyik (i)" elseif ending:lower == "iyik" then ending = "-iyik" elseif ending:lower == "iyil-e" then ending = "-iyil (e)" elseif ending:lower == "iyil-i" then ending = "-iyil (i)" elseif ending:lower == "iyil" then ending = "-iyil" elseif ending:lower == "verb participle" then ending = "verb participle" else ending = "-" .. ending:gsub("-", ""):lower end return ending end

-- Obstruent consonants (save for 'h') local obs = {'p', 't', 'c', 'k', 'q', 's'} -- Vowels ('eh' is not phonemic) local vow = {'a', 'e', 'i', 'o', 'u'}

function format_ending_append(ending, head)

local lastLetter = head:sub(-1, -1) local enVow = ending:sub(2, 2)

if ending == "" or ending == nil then ending = "ok" elseif ending:lower == "-iyik (e)" then ending = "iyik" elseif ending:lower == "-iyik (i)" then ending = "iyik" elseif ending:lower == "-iyik" then ending = "iyik" elseif ending:lower == "-iyil (e)" then ending = "iyil" elseif ending:lower == "-iyil (i)" then ending = "iyil" elseif ending:lower == "-iyil" then ending = "iyil" elseif ending:lower == "verb participle" then ending = "" else ending = ending:gsub("-", ""):lower end

-- 'u' merges morphophonologically if enVow == 'u' and lastLetter == 'u' then ending = ending:sub(2) -- Other adjacent vowels are separated by 'w'	elseif contains(vow, enVow) and contains(vow, lastLetter) then ending = 'w' .. ending end

return ending

end

function format_noun_form(code) if code == "obv" then code = "obviative" elseif code == "pos" then code = "possessed" elseif code == "loc" then code = "locative" elseif code == "dim" then code = "diminutive" elseif code == "abs" then code = "absentative" elseif code == "abs-obv" then code = "abs. obviative" elseif code == "voc" then code = "vocative" else code = "ERROR. Unknown noun form." end return code end

function autoDeclineSg(head, code, edg, anim, drop)

local declined = "—" local enVow = edg:sub(2, 2) local lastLetter = head:sub(-1, -1)

if edg == '-iyik (e)' then enVow = 'e'	end

-- Possessed if code == "pos" then

local pre = '' -- Starting two letters form a consonant cluster: prepend "u" if contains(obs, head:sub(1, 1)) and contains(obs, head:sub(2, 2)) then pre = "u" -- Starting letter is obstruent: prepend "'" elseif contains(obs, head:sub(1, 1)) then pre = "'" -- Starting letter is vowel: prepend "'t"		elseif contains(vow, head:sub(1, 1)) then pre = "'t"		-- Starting letter is "'": prepend "u" elseif head:sub(1, 1) == "'" then pre = "u" head = head:sub(2) end

local post = ''

-- Animate singular -ol ending if anim == 'animate' then post = 'ol' end

-- Replace final 'q' with 'k' if following vowel is 'u'		if enVow == 'u' and lastLetter == 'q' then head = head:sub(1, -2) .. 'k'		-- 'u' merges morphophonologically elseif enVow == 'u' and lastLetter == 'u' then enVow = '' -- Other adjacent vowels are separated by 'w'		elseif contains(vow, enVow) and contains(vow, lastLetter) then enVow = 'w' .. enVow end

local connector = enVow .. 'm'		if drop == true then connector = "" end

declined = pre .. head .. connector .. post

elseif code == "loc" then

if anim == 'inanimate' and edg == '-uwol' then enVow = 'uwo' end

-- Replace final 'q' with 'k' if following vowel is 'u'		if enVow == 'u' and lastLetter == 'q' then head = head:sub(1, -2) .. 'k'		-- 'u' merges morphophonologically elseif enVow == 'u' and lastLetter == 'u' then enVow = '' -- Other adjacent vowels are separated by 'w'		elseif contains(vow, enVow) and contains(vow, lastLetter) then enVow = 'w' .. enVow end

declined = head .. enVow .. 'k'

-- Diminutive elseif code == "dim" then

if contains(vow, lastLetter) then head = head .. 'w'		end

declined = head .. 'is'

-- Absentative elseif code == "abs" then

local post = enVow .. "w"

if enVow == "u" then post = "u" elseif enVow == "o" then post = "" end

if lastLetter == "u" and enVow == "u" then post = "" elseif contains(vow, lastLetter) then head = head .. 'w'		end

declined = head .. post

-- Obviative elseif code == "obv" then

-- Replace final 'q' with 'k' if following vowel is 'u'		if enVow == 'u' and lastLetter == 'q' then head = head:sub(1, -2) .. 'k'		end

declined = head .. format_ending_append(edg, head):sub(1, -2) .. "l"

-- Absentative obviative elseif code == "abs-obv" then

local post = enVow .. "kkol"

if enVow == "o" then post = "kol" end

if lastLetter == "u" and enVow == "u" then post = "kkol" elseif contains(vow, lastLetter) then head = head .. "w" end

declined = head .. post

end

return declined

end

function autoDeclinePl(head, code, edg, anim, drop)

local declined = "—" local enVow = edg:sub(2, 2) local lastLetter = head:sub(-1, -1)

if edg == '-iyik (e)' then enVow = 'e'	end

-- Possessed if code == "pos" then

local pre = '' -- Starting two letters form a consonant cluster: prepend "u" if contains(obs, head:sub(1, 1)) and contains(obs, head:sub(2, 2)) then pre = "u" -- Starting letter is obstruent: prepend "'" elseif contains(obs, head:sub(1, 1)) then pre = "'" -- Starting letter is vowel: prepend "'t"		elseif contains(vow, head:sub(1, 1)) then pre = "'t"		-- Starting letter is "'": prepend "u" elseif head:sub(1, 1) == "'" then pre = "u" head = head:sub(2) end

local post = 'ol'

-- Animate plural has no -ol ending if anim == 'animate' then post = '' end

-- Replace final 'q' with 'k' if following vowel is 'u'		if enVow == 'u' and lastLetter == 'q' then head = head:sub(1, -2) .. 'k'		-- 'u' merges morphophonologically elseif enVow == 'u' and lastLetter == 'u' then enVow = '' -- Other adjacent vowels are separated by 'w'		elseif contains(vow, enVow) and contains(vow, lastLetter) then enVow = 'w' .. enVow end

local connector = enVow .. 'm'		if drop == true then connector = "" end

declined = pre .. head .. connector .. post

elseif code == "loc" then

if enVow == 'o' or enVow == 'i' then

enVow = ''

end

if enVow == 'u' then

enVow = 'uw'

-- 'q' replaces 'uw' before 'i'			if lastLetter == 'q' then enVow = '' elseif lastLetter == 'u' then enVow = 'w'			end

elseif contains(vow, enVow) and contains(vow, lastLetter) then

enVow = 'w' .. enVow .. 'w'

elseif enVow == 'i' then

enVow = ''

elseif contains(vow, enVow) then

enVow = enVow .. 'w'

end

declined = head .. enVow .. 'ihkuk'

-- Diminutive elseif code == "dim" then

if contains(vow, lastLetter) then head = head .. 'w'		end

declined = head .. 'isol'

if anim == 'animate' then declined = head .. 'isok' end

-- Absentative and absentative obviative elseif code == "abs" or code == "abs-obv" then

local post = enVow .. "kko" local animacyEnding = "l"

if anim == 'animate' then animacyEnding = "kk" end

if enVow == "o" then post = "ko" end

if lastLetter == "u" and enVow == "u" then post = "kko" elseif contains(vow, lastLetter) then head = head .. "w" end

declined = head .. post .. animacyEnding

-- Obviative elseif code == "obv" then

local post = enVow

if enVow == "o" then post = "" elseif enVow == "i" or enVow == "e" then post = "iyi" end

-- Replace final 'q' with 'k' if following vowel is 'u'		if enVow == 'u' and lastLetter == 'q' then head = head:sub(1, -2) .. 'k'		elseif lastLetter == "u" and enVow == "u" then post = "" elseif contains(vow, lastLetter) then head = head .. "w" end

declined = head .. post

end

return declined

end

function catch(arg, default) if (arg == nil or (arg:gsub("%s+", "")) == "") then return default else return (arg:gsub("%s+", "")) end end

function split(str) local lines = {} for s in str:gmatch("[^\r\n]+") do	   table.insert(lines, s)	end return lines end

function contains(set, key) for k0, val in pairs(set) do     if val == key then return true end end return false end

function removeNil(t) local temp = {} for I = 1, #t do		if t[I] ~= nil then table.insert(temp, t[I]) end end return temp end

return export