Module:grc-form of

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

A module that may handle an Ancient Greek–specific form-of template, with features missing from {{inflection of}}. Or perhaps the features can be integrated into Module:form of.

Desired features:

  • Checking lemmas for monophthongal α, ι, υ (a, i, u) with ambiguous length: that is, without macrons, breves, iota subscripts, or circumflexes.
  • Putting inflectional categories in a consistent order.
  • Checking that inflectional categories are actually ones present in Ancient Greek (no conditional mood).
  • Checking that combinations of inflectional categories are valid in Ancient Greek (for instance, no imperfect infinitive).
  • Allowing the presence of movable nu, for instance in ἐστίν (estín), to be indicated with a parameter.
  • Allowing the terms movable nu, contraction, first aorist, and second aorist to be linked to pages explaining what they mean.
  • Allowing definitions for apocopic or aspirated forms of inflected forms, for instance ξεῖν’ (xeîn’) or δώμαθ’ (dṓmath’).

local export = {}

local m_fun = require("Module:fun")
local m_table = require("Module:table")
local list_to_set = m_table.listToSet
local invert = m_table.invert
local keys_to_list = m_table.keysToList

local function quote(text)
	return "“" .. tostring(text) .. "”"
end

local old_error = error
local function error(msg, level)
	msg = string.gsub(msg, "'", "’")
	old_error(msg, level)
end

local function errorf(...)
	error(string.format(...), 3)
end

local info = setmetatable({}, {
	__index = function(self, key)
		local val = { type = key }
		self[key] = val
		return val
	end
})

local label_info = {
	["first person"] = info.person, ["second person"] = info.person, ["third person"] = info.person,
	singular = info.number, dual = info.number, plural = info.number,
	present = info.tense, future = info.tense, perfect = info.tense,
	aorist = info.tense, imperfect = info.tense, pluperfect = info.tense,
	["future perfect"] = info.tense,
	indicative = info.mood, subjunctive = info.mood, optative = info.mood,
	imperative = info.mood, infinitive = info.nonfinite, participle = info.nonfinite,
	active = info.voice, middle = info.voice, mediopassive = info.voice, passive = info.voice,
	nominative = info.case, vocative = info.case, accusative = info.case,
	genitive = info.case, dative = info.case,
	masculine = info.gender, feminine = info.gender, neuter = info.gender,
	m = info.gender, f = info.gender, n = info.gender,
}

local abbreviations = {
	s = "singular", d = "dual", p = "plural",
	[1] = "first person", [2] = "second person", [3] = "third person",
	pres = "present", aor = "aorist", fut = "future", imperf = "imperfect",
	perf = "perfect", plup = "pluperfect",
	ind = "indicative", subj = "subjunctive", opt = "optative",
	imp = "imperative", inf = "infinitive", part = "participle",
	act = "active", mid = "middle", mp = "mediopassive", pass = "passive",
	masc = "masculine", nom = "nominative",
	m = "masculine", f = "feminine", n = "neuter",
}

local valid_categories = {
	verb = list_to_set{ "person", "number", "tense", "mood", "voice", "nonfinite" },
	adjective = list_to_set{ "gender", "case", "number" },
	noun = list_to_set{ "number", "case" },
	participle = list_to_set{ "gender", "case", "number", "tense", "voice" }, -- or treat as verb?
	infinitive = list_to_set{ "tense", "voice" }, -- or treat as verb?
}

valid_categories.pronoun = valid_categories.noun -- Some pronouns have gender, some don't.
valid_categories.demonstrative = valid_categories.adjective

-- No one part of speech has all these categories, but this order should hold true
-- for all of them.
local master_order = invert{ "gender", "case", "person", "number", "tense", "mood", "voice", "nonfinite" }

local function unrecognized_POS_error(POS)
	return error("The part of speech " .. quote(POS) .. " does not have a set of valid inflectional categories.", 3)
end

function export.validate_labels(category_map, POS)
	local validation = valid_categories[POS] or unrecognized_POS_error(POS)
	
	local invalid_labels = m_fun.filter(
		function(_, category)
			return not validation[category]
		end,
		category_map)
	
	local first_key = next(invalid_labels)
	if first_key then
		local agreement = next(invalid_labels, first_key) and { "ies", "are" } or { "y", "is" }
		errorf("The categor%s %s %s not used by the part of speech %s.",
			agreement[1], table.concat(m_fun.map(quote, keys_to_list(invalid_labels)), ", "), agreement[2], quote(POS))
	end
end

function export.resolve_abbr(labels)
	return m_fun.map(function(label)
			return abbreviations[tonumber(label)] or abbreviations[label] or label
		end,
		labels)
end

-- After resolution of abbreviations.
-- Creates map from inflectional category to label:
-- { "masculine", "nominative", "singular" }
--		↓
-- { gender = "masculine", case = "nominative", number = "singular" }
-- If there are two labels belonging to the same inflectional category,
-- throws an error.
local function make_category_map(labels)
	local category_map = {}
	local conflicts = {}
	
	for _, label in ipairs(labels) do
		local type = label_info[label] and label_info[label].type
		if not type then
			error("The label " .. quote(label) .. " is not in the list of labels.")
		end
		if category_map[type] then
			if not conflicts[type] then
				conflicts[type] = { category_map[type] }
			end
			table.insert(conflicts[type], label)
		end
		category_map[type] = label
	end
	
	if next(conflicts) then
		conflicts = m_fun.mapIter(function(conflict, category)
				return ("%s: %s"):format(quote(category), table.concat(m_fun.map(quote, conflict), ", "))
			end,
			pairs(conflicts))
		
		error(string.format("There were multiple labels belonging to the same categor%s: %s.",
			conflicts[2] and "ies" or "y",
			table.concat(conflicts, "; ")))
	end
	
	return category_map
end

local function capitalize(str)
	return string.gsub(str, "^.", string.upper)
end

local get_position = m_fun.memoize(function (label)
	return master_order[label_info[label].type] or
		mw.log("no position for label " .. quote(label) .. " of type " .. quote(label_info[label].type) .. ".")
end)

local function comp(label1, label2)
	return get_position(label1) < get_position(label2)
end

function export.sort(labels, POS)
	table.sort(labels, comp)
	return labels
end

function export.process(labels, POS)
	labels = export.resolve_abbr(labels)
	local category_map = make_category_map(labels, POS)
	if POS == "verb" then
		if category_map.nonfinite then
			POS, category_map.nonfinite = category_map.nonfinite, nil
		end
	end
	export.validate_labels(category_map, POS)
	export.sort(labels)
	return labels
end

function export.show(frame)
	local params = {
		[1] = { list = true },
		pos = {}
	}
	
	local args = require("Module:parameters").process(frame.args, params)
	local labels = args[1]
	local POS = args.pos
	
	return table.concat(export.process(labels, POS), ", ")
end

return export