Jump to content

Module:sjd-decl

From Wiktionary, the free dictionary


local export = {}

local vowels = "аӓеёиоуыҍэӭюя"
local consonants = "бвджзгһйҋклӆмӎнӊӈпрҏстфхцчшщ"
local macron = mw.ustring.char(0x0304)

-- also includes digraphs for monophthongs, despite its name
local diphthongs = {
	"оа", "ёа",
	"оа̄", "ёа̄",
	"уэ", "уа",
}

-- e.g. ьэ => е
local fusion = {
	["ьэ"] = "е", ["ьа"] = "я",
	["ҍэ"] = "ӭ", ["ҍа"] = "ӓ",
	["йэ"] = "е", ["йа"] = "я",
	["чэ"] = "че",

-- remove redundant palatal markers
	["ьи"] = "и", ["ҍи"] = "и",
--	["ье"] = "е", 
	["ҍӭ"] = "ӭ",
--	["ья"] = "я",
	["ҍӓ"] = "ӓ",
	["ьё"] = "ё", ["ҍё"] = "ё",
	["ью"] = "ю", ["ҍю"] = "ю",
}

-- e.g. a word ending is е is actually ь + э
local fission = {
	["е"] = { "ь", "э" },
	["я"] = { "ь", "а" },
	["ӭ"] = { "ҍ", "э" },
	["ӓ"] = { "ҍ", "а" },
}

-- precomposed Unicode macrons (i.e. ones that don't require a separate combining macron codepoint)
local macrons = {
	["ӣ"] = "и",
	["ӯ"] = "у",
}

local macroned = ""
local macroned_reverse = ""
local macrons_with_macron = {}
local macrons_reversed = {}

for k, v in pairs(macrons) do
	macroned = macroned .. k
	macroned_reverse = macroned_reverse .. v
	macrons_reversed[v .. macron] = k
	macrons_with_macron[k] = v .. macron
end

local diphthongs_by_final = {}
for _, v in ipairs(diphthongs) do
	local a, b = mw.ustring.sub(v, 1, 1), mw.ustring.sub(v, 2)
	diphthongs_by_final[b] = (diphthongs_by_final[b] or "") .. a
end

local function extract(word, ending)
	if mw.ustring.match(word, ending .. "$") then
		return mw.ustring.match(word, "(.-)" .. ending .. "$")
	end
	error("Unexpected ending for this inflection type! Wrong type?")
end

local function extract_final_vowel(word)
	local stem, vowel, macron = extract(word, "([" .. vowels .. "])(" .. macron .. "?)")

	if diphthongs_by_final[vowel .. macron] and mw.ustring.match(stem, "[" .. diphthongs_by_final[vowel .. macron] .. "]$") then
		vowel = mw.ustring.sub(stem, -1, -1) .. vowel .. macron
		stem = mw.ustring.sub(stem, 1, -2)
	end
	if fission[vowel] then
		local stem_extra
		stem_extra, vowel = unpack(fission[vowel])
		stem = stem .. stem_extra
	end
	if vowel == "оа̄" then vowel = "оа" end
	if vowel == "ёа̄" then vowel = "ёа" end
	return stem, vowel .. macron
end

local function decompose_macrons(word)
	return mw.ustring.gsub(word, "[" .. macroned .. "]", macrons_with_macron)
end

local function recompose_macrons(word)
	return mw.ustring.gsub(word, "[" .. macroned_reverse .. "]" .. macron, macrons_reversed)
end

local function append_hard_sign(t, x)
	-- hard sign
	if mw.ustring.match(x, "^[ея]") and mw.ustring.match(t, "[" .. consonants .. "]$") then
		return t .. "ъ" .. x
	else
		return t .. x
	end
end

local function fusion_special_case(a, b)
	if mw.ustring.match(a, "нь$") then
		if mw.ustring.match(b, "^оа") then
			return mw.ustring.sub(a, 1, -2) .. "ё" .. mw.ustring.sub(b, 2)
		elseif mw.ustring.match(b, "^уэ") then
			return mw.ustring.sub(a, 1, -2) .. "ю" .. mw.ustring.sub(b, 2)
		end
	end
end

local function join(...)
	local t = {}
	for _, s in ipairs({ ... }) do
		if type(s) == "table" then
			for _, v in ipairs(s) do
				table.insert(t, v)
			end
		else
			table.insert(t, s)
		end
	end
	return t
end

local function append(t, x)
	if not x then return t end
	if type(t) == "string" then
		return t .. x
	end
	local r = {}
	for _, v in ipairs(t) do
		table.insert(r, v .. x)
	end
	return r
end

local function append_smart(t, x)
	if not x then return t end
	if type(t) == "string" then
		-- special case
		local spec = fusion_special_case(t, x)
		if spec then return spec end

		-- two letters
		local key = mw.ustring.sub(t, -1, -1) .. mw.ustring.sub(x, 1, 1)
		if fusion[key] then
			return append_hard_sign(mw.ustring.sub(t, 1, -2), fusion[key] .. mw.ustring.sub(x, 2))
		end
		
		return append_hard_sign(t, x)
	end
	local r = {}
	for _, v in ipairs(t) do
		table.insert(r, append_smart(v, x))
	end
	return r
end
export.p=append_smart

local function strip_final_vowel(t)
	if type(t) == "string" then
		return mw.ustring.gsub(t, "[" .. vowels .. "]+$", "")
	end
	local r = {}
	for _, v in ipairs(t) do
		table.insert(r, strip_final_vowel(v))
	end
	return r
end

local function make_gradation(s, w)
	if s == w then
		return "no gradation"
	else
		return s .. "-" .. w .. " gradation"
	end
end

local function process(data, result, vh)
	local vh = data.vh
	-- genitive singular/plural stem
	local gs = result.stem_gs
	local gp = result.stem_gp
	-- illative singular stem
	local is = result.stem_is
	-- locative singular stem
	local ls = result.stem_ls or gs
	-- comitative singular stem
	local cs = result.stem_cs or gp
	-- essive stem
	local es = result.stem_es or data.title
	-- abessive singular stem
	local as = result.stem_as or gs
	-- partitive singular stem
	local pt = result.stem_as or gs

	if not data.no_singular then
		result["nom_sg"] = { data.title }
		result["gen_sg"] = gs
		result["ill_sg"] = is
		result["loc_sg"] = append_smart(ls, "сьт")
		result["com_sg"] = append_smart(cs, "нҍ")
		result["abe_sg"] = result.strong_abessive and append_smart(strip_final_vowel(es), "аһта") or append_smart(as, "ха")
		result["ess"]    = append_smart(es, "нҍ")
		result["prt"]    = result.strong_partitive and append_smart(is, "дтӭ") or es
	end

	if not data.no_plural then
		result["nom_pl"] = gs
		result["acc_pl"] = append_smart(gp, "тҍ")
		result["gen_pl"] = gp
		result["ill_pl"] = append_smart(gp, "тҍ")
		result["loc_pl"] = append_smart(gp, "нҍ")
		result["com_pl"] = append_smart(gp, "гуэйм")
		result["abe_pl"] = append_smart(gp, "ха")
	end

	for k, v in pairs(result) do
		if type(v) == "string" then
			result[k] = recompose_macrons(v)
		elseif type(v) == "table" then
			for i = 1,#v do
				v[i] = recompose_macrons(v[i])
			end
		end
	end

	return result
end

-- inflection classes begin
local inflections = {}

inflections["I"] = function (data)
	local vowel_com  = data.args[1] or error("must specify plural/comitative vowel")
	local vowel_ill  = data.args[2] or error("must specify illative vowel")
	local strong     = data.args[3] or error("must specify strong grade")
	local weak       = data.args[4] or error("must specify weak grade")
	local ending_ill = data.args[5] or error("must specify illative ending")

	local result = { typeno = (strong == weak and "II" or "I") }
	local word = decompose_macrons(data.title)

	local stem, vowel_nom
	if data.no_singular then
		stem = extract(word, weak)
	else
		stem = extract(word, strong)
	end
	stem, vowel_nom = extract_final_vowel(stem)
	vowel_com = decompose_macrons(vowel_com)
	vowel_ill = decompose_macrons(vowel_ill)

	result.stem_gs = append_smart(stem, vowel_nom .. weak)
	result.stem_ls = append_smart(append_smart(stem, vowel_nom .. weak), "э")
	result.stem_es = append_smart(append_smart(stem, vowel_nom .. strong), "э")
	result.stem_is = append_smart(stem, vowel_ill .. ending_ill)
	result.stem_gp = append_smart(append_smart(stem, vowel_com .. weak), "э")
	
	result.grade = make_gradation(strong, weak)

	return process(data, result)
end

inflections["II"] = inflections["I"]

inflections["III"] = function (data)
	local vowel_obl  = data.args[1] or error("must specify oblique vowel")
	local strong     = data.args[2] or error("must specify strong grade")
	local extended   = data.args[3] or error("must specify extended grade")

	local iv = mw.ustring.match(extended, "с$")
	local result = { typeno = (iv and "IV" or "III") }
	local word = decompose_macrons(data.title)

	local stem = word
	local weak
	if iv then stem = extract(stem, "с") end
	stem, weak = extract(stem, "([^" .. vowels .. macron .. "]+)")
	local final_v
	stem, final_v = extract_final_vowel(stem)

	local stem_w = stem .. vowel_obl .. weak
	local stem_s = stem .. vowel_obl .. strong
	local stem_e = stem .. vowel_obl .. strong .. extended

	if mw.ustring.match(extended, "^[" .. vowels .. "]") then
		stem_s = stem .. vowel_obl .. strong .. mw.ustring.gsub(extended, "^[" .. vowels .. "]", "")

		result.stem_gs = stem_e
		result.stem_is = append_smart(stem_s, "э")
		result.stem_es = append_smart(stem_s, "э")
		result.stem_as = result.stem_es
		result.stem_gp = result.stem_es
		result.stem_ls = result.stem_es
	else
		if extended == "к" then
			if mw.ustring.match(strong, "[птк][ҍь]?$") then
				stem_e = { stem .. vowel_obl .. strong .. "к", stem .. vowel_obl .. strong .. "х" }
			elseif mw.ustring.match(strong, "[нмӈлрй][ҍь]?$") then
				stem_e = stem .. vowel_obl .. strong .. "г"
			end
		elseif extended ~= "й" then
			error("unsupported grade extension for type III")
		end

		result.stem_gs = append_smart(stem_s, "э")
		result.stem_is = append_smart(stem_e, "э")
		result.stem_es = result.stem_gs
		result.stem_as = result.stem_gs
		result.stem_gp = result.stem_gs
		result.stem_ls = result.stem_gs	
	end

	result.strong_partitive = true
	result.strong_abessive = true
	
	result.grade = make_gradation(strong, weak)
	return process(data, result)
end

inflections["IV"] = inflections["III"]

-- inflection classes end

local infl_table = [=[{| class="inflection-table vsSwitcher" data-toggle-category="inflection" style="border:solid 1px #CCCCFF; text-align: left;" cellspacing="1" cellpadding="2"
|- style="background: #E2F6E2; text-align: left;"
! class="vsToggleElement" colspan="3" | Declension of {{{title}}} (<span style="font-size:90%">{{{type}}}</span>)
|- class="vsShow" style="background: #F2F2FF;"
! style="width: 10em; background: #E2F6E2;" | Nominative
| style="width: 15em;" colspan="2" | {{{nom_sg}}}
|- class="vsShow" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Genitive
| colspan="2" | {{{gen_sg}}}
|- class="vsShow" style="background: #F2F2FF;"
! style="width: 10em; background: #E2F6E2;" | Dative-Illative
| style="width: 15em;" colspan="2" | {{{ill_sg}}}
|- class="vsShow" style="background: #F2F2FF;"
! style="width: 10em; background: #E2F6E2;" | Comitative
| style="width: 15em;" colspan="2" | {{{com_sg}}}
|- class="vsShow" style="background: #F2F2FF;"
|- class="vsHide"
! style="width: 10em; background:#c0e4c0" |
! style="width: 15em; background:#c0e4c0" | singular
! style="width: 15em; background:#c0e4c0" | plural
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Nominative
| {{{nom_sg}}}
| {{{nom_pl}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Accusative
| {{{gen_sg}}}
| {{{acc_pl}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Genitive
| {{{gen_sg}}}
| {{{gen_pl}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Dative-Illative
| {{{ill_sg}}}
| {{{ill_pl}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Locative
| {{{loc_sg}}}
| {{{loc_pl}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Comitative
| {{{com_sg}}}
| {{{com_pl}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Abessive
| {{{abe_sg}}}
| {{{abe_pl}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Essive
| colspan="2" | {{{ess}}}
|- class="vsHide" style="background: #F2F2FF;"
! style="background: #E2F6E2;" | Partitive
| colspan="2" | {{{prt}}}
|}]=]

local function link(text)
	return require("Module:links").full_link{ term = text, lang = require("Module:languages").getByCode("sjd") }
end

local function mention(text)
	return require("Module:links").full_link({ term = text, lang = require("Module:languages").getByCode("sjd") }, "term")
end

function export.show(frame)
	local infl_type = frame.args[1] or error("inflection class not specified")
	local infl = inflections[infl_type] or error("unsupported inflection type")
	local args = frame:getParent().args
	local title = args["title"] or mw.title.getCurrentTitle().text

	local data = { title = title, headword = headword, args = args }

	if args["n"] then
		if args["n"] == "s" or args["n"] == "sg" then
			data.no_plural = true
		elseif args["n"] == "p" or args["n"] == "pl" then
			data.no_singular = true
		end
	end

	local forms = infl(data)

	local function repl(form)
		if form == "title" then
			return "'''" .. title .. "'''"
		elseif form == "type" then
			if forms.irregular then
				return "irregular"
			end
			local s = "type " .. (forms.typeno or infl_type) .. " noun"
			if forms.grade then
				s = s .. ", " .. forms.grade
			else
				s = s .. ", " .. make_gradation(nil, nil)
			end
			return s
		else
			local value = forms[form]
			if not value then
				return "&mdash;"
			elseif type(value) == "table" then
				local result = {}
				for _, f in ipairs(value) do
					table.insert(result, link(f))
				end
				return table.concat(result, ", ")
			else
				return link(value)
			end
		end
	end

	local result = mw.ustring.gsub(infl_table, "{{{([a-z0-9_:]+)}}}", repl)
	result = mw.ustring.gsub(result, "{{m|sjd|([^}]-)}}", mention)
	return result
end

return export