Module:amf-nominal

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

Implements {{amf-ndecl}}. Inflection data stored in Module:amf-nominal/data.


local export = {}

local m_para = require("Module:parameters")
local amf = require("Module:languages").getByCode("amf")
local m_link = require("Module:links")
local m_util = require("Module:amf-utilities")

local DATA = mw.loadData("Module:headword/data")
local NAMESPACE = DATA.page.namespace
local PAGENAME = DATA.pagename

export.show_table = require("Module:amf-nominal/table")

-- useful regexes and replacement tables
local r = {}
r.C = "[bBcCdDgGhjklmnNpqrsStwxyzQ]"
r.V = "[aeiouEO]"
r.add_n = {
	[""] = "n",
	b = "mm", -- náabi > námmo
	B = "mB", -- atáɓ > atámɓa
	D = "nD", -- tuɗí > tunɗó
	j = "N",  -- cʼagáj > cʼagáɲo
	k = "ng", -- gerák > gerángo
	l = "ll", -- afála > afállo
	m = "mm", -- qáami > qámmo
	n = "nn", -- ooní > onnó
	p = "mm", -- galáp > galámmo
	q = "nq", -- tubáqe > tubánqo
	r = "rr", -- kurí > kurró
	S = "N",  -- gaʔásh > gaʔáɲo
	t = "nn", -- qootí > qonnó
	z = "nn", -- maz > mánno
	-- missing: cCdgGhNswxyQ
}
r.add_t = {
	[""] = "t",
	b = "tt", -- zóbo > zɔttâ
	l = "lt", -- ukulí > ukultâ
	r = "rt", -- góro > gortóno
	S = "St", -- gaʔásh > gaʔashtóno
	y = "it", -- gáya > gaitâ
}

local genders = { "", "m", "f", "f2", "pl" }
local cases = { "nom", "obl", "acc", "gen", "dat", "aff", "ins", "loc",
	"ine", "ade", "all1", "all2", "abl", "com" }
local function combine(g,c)
	return g .. (g ~= "" and "_" or "") .. c
end

local case_suffix = {
	acc = "ɗan",
	gen = {"sa", "isa"},
	dat = "na",			-- qánte, nánte
	aff = "kal",		-- ~xal
	ins = "ka",			-- ~xa
	loc = "te",			-- te
	ine = {"r", "ir"},	-- "ir" is inferred
	ade = "bar",
	all1 = "dar",
	all2 = "shet",		-- shette also
	abl = {"rra", "irra"},	-- "irra" is inferred
	com = "be",			-- bet, bette also
}

-- see [[Module:amf-nominal/testcases]]
-- only accepts vowel stem
function export.make_masculine(syl)
	syl = mw.clone(syl)
	local n = #syl
	syl.accent = n -- move accent to last syllable
	syl.falling = true -- make the accent falling
	if not syl[n]:match("^"..r.C.."[aeio]$") then
		error("Word must end in -a, -e, -i, or -o.")
	end
	syl[n] = syl[n]:gsub("[aeio]$",{e="E",i="E",o="O"}) -- P5
	-- regressive vowel harmony; blocked by "i" (MP5)
	for i=n-1,1,-1 do
		if syl[i]:match("i") then break end
		syl[i] = syl[i]:gsub("[eo]",{e="E",o="O"})
	end
	return syl
end

-- clip the last vowel (tesíɓe > *tesíɓ)
-- keeps the accent position even if it is at the end
function export.truncate_vowel(syl)
	syl = mw.clone(syl)
	local n = #syl
	local cons,vow = syl[n]:match("^("..r.C..")([aeio])$")
	if cons then
		syl[n] = nil
		syl[n-1] = syl[n-1] .. cons
	end
	return syl
end

-- see [[Module:amf-nominal/testcases]]
-- add a syllable (CV) to a consonant stem, and apply the phonetic rules
-- e.g. panáq + no > panánqo
-- e.g. zób + ta > zótta
-- shorten the (new) penultimate syllable (e.g. yíir + no > yírro)
function export.attach_CV(syl,C,V)
	syl = mw.clone(syl)
	local n = #syl
	local onst,nucl,coda = syl[n]:match("("..r.C.."?)("..r.V.."+)("..r.C.."?)$")
	if nucl:sub(1,1) == nucl:sub(2,2) and coda ~= "" then
		nucl = nucl:sub(1,1)
	end
	local assimilated = r["add_"..C][coda] or error("Unrecognised pattern: " .. m_util.combine(syl))
	syl[n] = onst..nucl..(assimilated:sub(-2,-2))
	syl[n+1] = (assimilated:sub(-1,-1))..V
	return syl
end

function export.make_feminine(syl)
	return export.attach_CV(syl,"n","o")
end

function export.make_f2(data)
	if data.f2_nom then
		return nil
	end
	data.f2_nom = export.attach_CV(export.truncate_vowel(data.nom),"t","o")
	local n = #data.f2_nom
	data.f2_nom.accent = n
	data.f2_obl = mw.clone(data.f2_nom)
	data.f2_nom[n+1] = "no"
	data.f2_obl[n] = data.f2_obl[n].."n"
end

-- generates pl_nom from f_nom
local function make_pl(data)
	data.pl_nom = mw.clone(data.f_nom)
	local n = #data.pl_nom
	data.pl_nom[n] = data.pl_nom[n]:gsub("o$","a",1)
end

export.inflect = {}

export.inflect["1"] = function(data)
	data.m_nom = export.make_masculine(data.nom)
	data.f_nom = "no"
	data.pl_nom = "na"
	data.f_obl = "n"
end

export.inflect["2"] = function(data)
	data.m_nom = export.make_masculine(data.nom)
	data.f_nom = export.make_feminine(export.truncate_vowel(data.nom))
	make_pl(data)
	data.f_obl = "n"
end

export.inflect["3"] = function(data)
	local n = #data.nom
	data.m_nom = mw.clone(data.nom)
	local rest,coda = data.nom[n]:match("^(.+)("..r.C..")$")
	data.m_nom[n] = rest
	data.m_nom[n+1] = coda .. "a"
	data.m_nom = export.make_masculine(data.m_nom)
	data.f_nom = export.make_feminine(data.nom)
	make_pl(data)
	data.f_obl = "in"
end

-- vowel lowering observed in two out of two samples
export.inflect["4i"] = function(data)
	data.m_nom = mw.clone(data.nom)
	data.m_nom[2] = "ta"
	data.m_nom = export.make_masculine(data.m_nom)
	data.f_nom = "no"
	-- vowel lowering
	data.pl_nom = mw.clone(data.nom)
	data.pl_nom[1] = data.pl_nom[1]:gsub("([eo])%1",{ee="EE",oo="OO"})
	data.pl_nom[2] = "na"
	data.f_obl = "n"
end

local function inflect_4(data)
	local n = #data.nom
	if data.nom[n]:match("[aeiouEO][aeiouEO]") then
		data.truncated = mw.clone(data.nom)
		data.m_nom = mw.clone(data.nom)
		data.m_nom[n+1] = "ta"
	else
		data.truncated = export.truncate_vowel(data.nom)
		data.m_nom = export.attach_CV(data.truncated,"t","a")
	end
	data.m_nom = export.make_masculine(data.m_nom) -- ukultâ
	data.fem2 = true
	data.f_obl = "n"
end

export.inflect["4a"] = function(data)
	inflect_4(data)
	data.f_nom = "no"
	data.pl_nom = "na"
end

export.inflect["4a/"] = function(data)
	export.inflect["4a"](data)
	data.m_nom = {data.m_nom, export.make_masculine(data.nom)}
end

export.inflect["4b"] = function(data)
	inflect_4(data)
	data.f_nom = export.attach_CV(data.truncated,"n","o")
	make_pl(data)
end

export.inflect["4b/"] = function(data)
	export.inflect["4b"](data)
	data.m_nom = {data.m_nom, export.make_masculine(data.nom)}
end

local function inflect_5(data)
	local n = #data.nom
	data.f_nom = mw.clone(data.nom)
	data.f_nom[n] = "no"
	make_pl(data)
	
	-- for f_obl, the pattern seems to be that if final syllable of f_nom
	-- is accented, then the (originally) penultimate syllable breaks if
	-- it's a diphthong
	data.f_obl = mw.clone(data.f_nom)
	if data.f_obl.accent == n then
		local onset,nucl = data.f_obl[n-1]:match("^("..r.C.."?)("..r.V.."+)$")
		if #nucl == 2 and nucl:sub(1,1) ~= nucl:sub(2,2) then
			data.f_obl[n-1] = onset..nucl:sub(1,1)
			data.f_obl[n] = nucl:sub(2,2).."n"
		else
			data.f_obl[n-1] = onset..nucl.."n"
			data.f_obl[n] = nil
			data.f_obl.accent = (n>2) and (n-1) or 0
		end
	else
		data.f_obl[n-1] = data.f_obl[n-1].."n"
		data.f_obl[n] = nil
	end
end

export.inflect["5a"] = function(data)
	inflect_5(data)
	data.m_nom = mw.clone(data.nom)
	local n = #data.nom
	data.m_nom[n] = data.m_nom[n]:gsub("i$","a")
	data.m_nom = export.make_masculine(data.m_nom)
end

export.inflect["5b"] = function(data)
	inflect_5(data)
	data.m_nom = export.make_masculine(data.nom)
end

export.inflect["6"] = function(data)
	data.nom = data.pagename
end

-- m_obl does not exist, but the circumflex changes to acute in other cases
-- e.g. hattâ = tree:M; hattá-sa = tree:M-gen
function export.make_m_obl(data)
	if data.m_obl then
		return nil
	end
	data.m_obl = mw.clone(data.m_nom)
	if type(data.m_obl[1]) == "string" then
		data.m_obl.falling = false
	else
		for i=1,#data.m_obl do
			data.m_obl[i].falling = false
		end
	end
end

function export.combine_nom_obl(data)
	for _,g in ipairs(genders) do
		for _,c in ipairs({"nom","obl"}) do
			local gc = combine(g,c)
			local curr = data[gc]
			if curr then
				if data.pattern ~= "6" then
					if type(curr) == "string" then -- a simple suffix
						if data.pattern:sub(1,1) ~= "5" then
							data[gc] = data.pagename .. curr
						end
					elseif type(curr[1]) == "string" then -- one form
						data[gc] = m_util.combine(curr)
					else
						for i,syl in ipairs(curr) do
							curr[i] = m_util.combine(syl)
						end
						data[gc] = table.concat(curr, ", ")
					end
				else
					if curr:match(",") then
						data[gc] = mw.text.split(curr,",",true)
					end
				end
			end
		end
	end
end

function export.make_cases(data)
	-- only f_obl and f2_obl exist but we make the other obl to make the rest of the code easier
	data.obl = mw.clone(data.nom)
	data.pl_obl = mw.clone(data.pl_nom)
	local function attach_suffix(original,suffix)
		if suffix:sub(1,1) == "b" then
			original = original:gsub("n$","m")
		end
		return original .. suffix
	end
	-- make other cases
	local function make_one_case(g,c,suffix)
		local source = data[combine(g,"obl")]
		if not source then
			return nil
		elseif type(source) == "string" then
			data[combine(g,c)] = attach_suffix(source,suffix)
		else
			data[combine(g,c)] = {}
			for i=1,#source do
				data[combine(g,c)][i] = attach_suffix(source[i],suffix)
			end
		end
	end
	for i=3,#cases do
		local case = cases[i]
		local v_suffix, c_suffix
		if type(case_suffix[case]) == "string" then
			v_suffix = case_suffix[case]
			c_suffix = case_suffix[case]
		else
			v_suffix = case_suffix[case][1]
			c_suffix = case_suffix[case][2]
		end
		for _,g in ipairs(genders) do
			local suffix = (data.pattern == "3" and g == "" and c_suffix or v_suffix)
			make_one_case(g,case,suffix)
		end
	end
end

function export.delete_forms(data)
	if data.modifier == "m" then
		data.extra = "masculine only"
		for _,c in ipairs(cases) do
			data["f_"..c] = "—"
		end
	elseif data.modifier == "f" then
		data.extra = "feminine only"
		for _,c in ipairs(cases) do
			data["m_"..c] = "—"
		end
	elseif data.modifier == "sg" then
		data.extra = "singular only"
		data.category = "[[Category:"..amf:getFullName().." singularia tantum|"..amf:makeSortKey(data.pagename).."]]"
		for _,c in ipairs(cases) do
			data["pl_"..c] = "—"
		end
	end
	for _,g in ipairs(genders) do
		for _,c in ipairs(cases) do
			data[combine(g,c)] = data[combine(g,c)] or "?"
		end
	end
end

local function determine_pattern(data)
	local new_data = mw.loadData("Module:amf-nominal/data")[data.pagename]
	if new_data then
		for key,val in pairs(new_data) do
			data[key] = val
		end
		return new_data.pattern
	end
	local syl = data.nom
	if syl[#syl]:sub(-1,-1):match(r.C) then
		return "3"
	elseif #syl == 1 and syl[1]:match("^"..r.C.."?"..r.V.."+$") then
		return "4i"
	end
	error("Please specify the declension type.")
end

local pattern_display = {
	["1"]	= "1",
	["2"]	= "2",
	["3"]	= "3",
	["4i"]	= "4 – inanimate",
	["4a"]	= "4 – animate",
	["4a/"]	= "4 – animate",
	["4b"]	= "4 – animate",
	["4b/"]	= "4 – animate",
	["5a"]	= "5",
	["5b"]	= "5",
	["6"]	= "6",
}

local auto_patterns = {
	["3"] = true, ["4i"] = true, ["5a"] = true, ["5b"] = true, ["6"] = true
}

function export.show(frame)
	local args = m_para.process(frame:getParent().args,{
		[1] = {},
		[2] = {},
		pagename = (NAMESPACE == "Template" or NAMESPACE == "User") and {} or nil,
	})
	local pagename = args.pagename or PAGENAME
	if pagename == "hámar" then
		pagename = "hámari"
	end
	
	local data = {}
	data.pagename = pagename
	data.modifier = args[2]
	if args[2] == "f2" then
		data.fem2 = true
	end
	data.nom = m_util.syllabify(pagename) -- store the base form in data.nom
	if auto_patterns[args[1]] then
		error("Declension " .. args[1] .. " does not need to be specified.")
	end
	local pattern = args[1] or determine_pattern(data)
	data.pattern = pattern
	
	export.inflect[pattern](data)
	if data.fem2 then
		export.make_f2(data)
	end
	
	export.make_m_obl(data)
	export.combine_nom_obl(data)
	export.make_cases(data)
	if pagename == "hámari" then
		pagename = "hámar"
		data.nom = "hámar"
	end
	
	for _,g in ipairs(genders) do
		for _,c in ipairs(cases) do
			local gc = combine(g,c)
			local val = data[gc]
			if val and type(val) == "table" then
				data[gc] = table.concat(val, ", ")
			end
		end
	end
	
	export.delete_forms(data)
	data.title = '<i>' .. pagename .. '</i> (Declension ' .. pattern_display[pattern]
		.. (data.extra and ", ''"..data.extra.."''" or "") .. ')'
	
	local res = export.show_table(data.fem2):gsub('{{{([^{}]+)}}}', data)
	return res .. (data.category or "")
end

return export