Module:pqm-decl

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

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 = "[<span style=\"font-variant:small-caps;\">" .. annotation .. "</span>]"
	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 = [=[

|}
</div>
</div>]=]
	
	table.insert(result, [=[
<div class="NavFrame" style="width: 100%">
<div class="NavHead">Declension of ''{title}''&ensp;{annotation}</div>
<div class="NavContent">
{|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: {light}; text-align: left" |
!  style="background: {dark}; text-align: left; padding: 5px 8px 5px 8px" | unmarked{proximateText}
| '''{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, "&nbsp;/&nbsp;")
		end
	end

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

|- style="background: {light}; text-align: left" |
!  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, "| ")

		-- 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, "&nbsp;/&nbsp;")
			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, "&nbsp;/&nbsp;")
			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