Jump to content

Module:de-headword

From Wiktionary, the free dictionary

This module needs documentation.
Please document this module by describing its purpose and usage on the documentation page.

local export = {}
local pos_functions = {}

local headword_utilities_module = "Module:headword utilities"
local inflection_utilities_module = "Module:inflection utilities"

local lang = require("Module:languages").getByCode("de")

local legal_gender = {
	["m"] = true,
	["f"] = true,
	["n"] = true,
	["p"] = true,
}

local gender_names = {
	["m"] = "masculine",
	["f"] = "feminine",
	["n"] = "neuter",
}

local legal_verb_classes = {
	["1"] = true,
	["2"] = true,
	["3"] = true,
	["4"] = true,
	["5"] = true,
	["6"] = true,
	["7"] = true,
}

local function ine(val)
	if val == "" then return nil else return val end
end


local function glossary_link(entry, text)
	text = text or entry
	return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end


local function track(page)
	require("Module:debug").track("de-headword/" .. page)
	return true
end


-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
	local args = frame:getParent().args
	PAGENAME = mw.title.getCurrentTitle().text

	local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
	local class = frame.args[2]; if class == "" then class = nil end

	local data = {lang = lang, pos_category = poscat, categories = {}, heads = {}, genders = {}, inflections = {}}

	if pos_functions[poscat] then
		pos_functions[poscat](class, args, data)
	end

	return
		require("Module:headword").full_headword(data)
end

local function old_adjectives(class, args, data)
	track("de-adj-old")
	local params = {
		[1] = {list = "comp"},
		[2] = {list = "sup"},
		["head"] = {list = true},
	}
	local args = require("Module:parameters").process(args, params)
	data.heads = args["head"]

	if args[1][1] == "-" then
		table.insert(data.inflections, {label = "not comparable"})
		table.insert(data.categories, "German uncomparable adjectives")
		return
	end

	if #args[1] > 0 then
		for i, form in ipairs(args[1]) do
			args[1][i] = {term = (form == "er" and PAGENAME .. "er" or form),
				accel = {form = "comparative"}}
		end
	else
		args[1] = {request = true}
		track("de-adj lacking comparative")
	end
	args[1].label = glossary_link("comparative")
	table.insert(data.inflections, args[1])

	if #args[2] > 0 then
		for i, form in ipairs(args[2]) do
			args[2][i] = {
				term = "am [[" ..  ((form == "st" or form == "sten") and PAGENAME .. "sten" or (form == "est" or form == "esten") and PAGENAME .. "esten" or form) .. "]]",
				accel = {form = "superlative"}}
		end
	else
		args[2] = {request = true}
		track("de-adj lacking superlative")
	end
	args[2].label = glossary_link("superlative")
	table.insert(data.inflections, args[2])
end


pos_functions.adjectives = function(class, args, data, proper)
	-- Compatibility with old calling convention, either if old= is given or any arg no longer supported is given.
	if ine(args.old) or ine(args[2]) or ine(args.comp1) or ine(args.comp2) or ine(args.comp3) or
		ine(args.sup1) or ine(args.sup2) or ine(args.sup3) or args[1] == "-" then
		return old_adjectives(class, args, data)
	else
		-- Remove this code once we convert to the new format.
		track("de-adj-need-old")
	end

	local alternant_multiword_spec = require("Module:de-adjective").do_generate_forms(args, nil, "from headword")
	data.heads = alternant_multiword_spec.args.head
	data.id = alternant_multiword_spec.args.id
	data.sort = alternant_multiword_spec.args.sort

	local function do_adj_form(slot, label, should_be_present, accel_form)
		local forms = alternant_multiword_spec.forms[slot]
		local retval
		if not forms then
			if not should_be_present then
				return
			end
			retval = {label = "no " .. label}
		else
			retval = {label = label, accel = accel_form and {form = accel_form} or nil}
			local prev_footnotes
			for _, form in ipairs(forms) do
				local footnotes = form.footnotes
				if footnotes and prev_footnotes and require("Module:table").deepEquals(footnotes, prev_footnotes) then
					footnotes = nil
				end
				prev_footnotes = form.footnotes
				local quals, refs = require(inflection_utilities_module).
					convert_footnotes_to_qualifiers_and_references(footnotes)
				local term = form.form
				table.insert(retval, {term = term, q = quals, refs = refs, genders = genders})
			end
		end

		table.insert(data.inflections, retval)
	end

	if alternant_multiword_spec.props.indecl then
		table.insert(data.inflections, {label = glossary_link("indeclinable")})
		if alternant_multiword_spec.props.predonly then
			table.insert(data.inflections, {label = "predicative only"})
		elseif alternant_multiword_spec.props.nopred then
			table.insert(data.inflections, {label = "no predicative form"})
		end
	else
		if alternant_multiword_spec.props.nopred then
			table.insert(data.inflections, {label = "no predicative form"})
		end
		do_adj_form("str_nom_m", "strong nominative masculine singular", true)
		local should_comp_sup =
			alternant_multiword_spec.forms.comp_pred or alternant_multiword_spec.forms.sup_pred or
			alternant_multiword_spec.forms.comp_str_nom_m or alternant_multiword_spec.forms.sup_str_nom_m
		if not should_comp_sup then
			table.insert(data.inflections, {label = "not comparable"})
		else
			-- If there is no predicative comparative, but there is an attributive comparative, include it;
			-- otherwise, include the predicative comparative (if any). If there is no comparative but a superlative,
			-- this will display "no comparative".
			if not alternant_multiword_spec.forms.comp_pred and alternant_multiword_spec.forms.comp_str_nom_m then
				do_adj_form("comp_str_nom_m", "strong comparative nominative masculine singular", true)
			else
				do_adj_form("comp_pred", glossary_link("comparative"), true, "comparative")
			end
			-- Same as above, for the superlative.
			if not alternant_multiword_spec.forms.sup_pred and alternant_multiword_spec.forms.sup_str_nom_m then
				do_adj_form("sup_str_nom_m", "strong superlative nominative masculine singular", true)
			else
				do_adj_form("sup_pred", glossary_link("superlative"), true, "superlative")
			end
		end
	end

	-- Add categories.
	for _, cat in ipairs(alternant_multiword_spec.categories) do
		table.insert(data.categories, cat)
	end

	-- Use the "linked" form of the lemma as the head if no head= explicitly given.
	data.no_redundant_head_cat = true -- always set since we have a different lemma linking algorithm from [[Module:headword]].
	if #data.heads == 0 then
		data.heads = {}
		local lemmas = alternant_multiword_spec.forms.the_lemma or {}
		for _, lemma_obj in ipairs(lemmas) do
			local head = alternant_multiword_spec.args.nolinkhead and lemma_obj.form or
				require(headword_utilities_module).add_links_to_multiword_term(lemma_obj.form,
					{split_hyphen_when_space = alternant_multiword_spec.args.splithyph})
			local quals, refs = require(inflection_utilities_module).
				convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
			table.insert(data.heads, {term = head, q = quals, refs = refs})
		end
	end
end

local function old_nouns(class, args, data)
	track("de-noun-old")
	local params = {
		[1] = {list = "g", default = "?"},
		[2] = {list = "gen"},
		[3] = {list = "pl"},
		[4] = {list = "dim"},
		["head"] = {list = true},
		["m"] = {list = true},
		["f"] = {list = true},
		["old"] = {type = "boolean"},
	}

	local args = require("Module:parameters").process(args, params)
	data.heads = args["head"]

	-- Gender
	for _, g in ipairs(args[1]) do
		if legal_gender[g] then
			table.insert(data.genders, g)

			if g == "p" then
				table.insert(data.categories, "German pluralia tantum")
			else
				table.insert(data.categories, "German " .. gender_names[g] .. " nouns")
			end
		else
			if g == "m-s" or g == "f-s" or g == "n-s" or g == "m-p" or g == "f-p" or g == "n-p" then
				require("Module:debug").track("de-headword/genders")
			end

			table.insert(data.genders, "?")
		end
	end

	if args[1][1] ~= "p" then
		-- Genitive
		if not args[2][1] then
			if args[1][1] == "m" or args[1][1] == "n" then
				table.insert(args[2], PAGENAME .. "s")
			else
				table.insert(args[2], PAGENAME)
			end
		end

		for i, form in ipairs(args[2]) do
			args[2][i] = {term = form}
		end

		args[2].accel = {form = "gen|s"}
		args[2].label = "genitive"
		table.insert(data.inflections, args[2])

		-- Plural
		if not args[3][1] and data.pos_category == "nouns" then
			table.insert(args[3], PAGENAME .. "en")
		end

		if args[3][1] == "-" then
			table.insert(data.inflections, {label = "no plural"})
			table.insert(data.categories, "German uncountable nouns")
		elseif #args[3] > 0 then
			for i, form in ipairs(args[3]) do
				args[3][i] = {term = form}
			end

			args[3].accel = {form = "p"}
			args[3].label = "plural"
			table.insert(data.inflections, args[3])
		end
	end

	-- Diminutive
	if #args[4] > 0 then
		for i, form in ipairs(args[4]) do
			args[4][i] = {term = form, genders = {"n"}}
		end

		args[4].accel = {form = "diminutive", gender = "n"}
		args[4].label = "diminutive"
		table.insert(data.inflections, args[4])
	end

	-- Other gender
	if #args.f > 0 then
		args.f.label = "female"
		if args.f[1] == "in" then
			args.f[1] = PAGENAME .. "in"
		end
		if args.f[1] == PAGENAME .. "in" then
			args.f.accel = {form = "feminine", gender = "f"}
			args.f.label = "feminine"
		end
		table.insert(data.inflections, args.f)
	end

	if #args.m > 0 then
		args.m.label = "male"
		table.insert(data.inflections, args.m)
	end
end


pos_functions.nouns = function(class, args, data, proper)
	-- Compatibility with old calling convention, either if old= is given or any arg no longer supported is given.
	if ine(args.old) or ine(args[2]) or ine(args[3]) or ine(args[4]) or ine(args.g1) or ine(args.g2) or ine(args.g3) or
		ine(args.gen1) or ine(args.gen2) or ine(args.gen3) or ine(args.pl1) or ine(args.pl2) or ine(args.pl3) then
		return old_nouns(class, args, data)
	end

	local m_de_noun = require("Module:de-noun")
	local alternant_multiword_spec = m_de_noun.do_generate_forms(args, nil, "from headword", proper)
	data.heads = alternant_multiword_spec.args.head
	data.genders = alternant_multiword_spec.genders
	data.id = alternant_multiword_spec.args.id
	data.sort = alternant_multiword_spec.args.sort
	if not proper then
		data.pos_category = alternant_multiword_spec.pos
		if alternant_multiword_spec.pos == "suffixes" then
			table.insert(data.categories, "German noun-forming suffixes")
		end
	end

	local function get_nom_articles(alternant_multiword_spec)
		local articles = {}
		local m_table = require("Module:table")
		if alternant_multiword_spec.number == "pl" then
			m_table.insertIfNot(articles, "[[die]]")
		else
			for _, gender in ipairs(alternant_multiword_spec.genders) do
				if gender.spec == "m" then
					m_table.insertIfNot(articles, "[[der]]")
				elseif gender.spec == "f" then
					m_table.insertIfNot(articles, "[[die]]")
				elseif gender.spec == "n" then
					m_table.insertIfNot(articles, "[[das]]")
				else
					error("Internal error: Unrecognized gender '" .. gender.spec .. "'")
				end
			end
		end
		return table.concat(articles, "/")
	end

	local function get_gen_s_articles(alternant_multiword_spec)
		local articles = {}
		local m_table = require("Module:table")
		if alternant_multiword_spec.number == "pl" then
			error("Internal error: Should not be called on plural-only nouns")
		else
			for _, gender in ipairs(alternant_multiword_spec.genders) do
				if gender.spec == "m" or gender.spec == "n" then
					m_table.insertIfNot(articles, "[[des]]")
				elseif gender.spec == "f" then
					m_table.insertIfNot(articles, "[[der]]")
				else
					error("Internal error: Unrecognized gender '" .. gender.spec .. "'")
				end
			end
		end
		return table.concat(articles, "/")
	end

	local function do_noun_form(slot, label, should_be_present, accel_form, genders, prefix)
		local forms = alternant_multiword_spec.forms[slot]
		local retval
		if not forms then
			if not should_be_present then
				return
			end
			retval = {label = "no " .. label}
		else
			retval = {label = label, accel = accel_form and {form = accel_form} or nil}
			local prev_footnotes
			for _, form in ipairs(forms) do
				local footnotes = form.footnotes
				if footnotes and prev_footnotes and require("Module:table").deepEquals(footnotes, prev_footnotes) then
					footnotes = nil
				end
				prev_footnotes = form.footnotes
				local quals, refs = require(inflection_utilities_module).convert_footnotes_to_qualifiers_and_references(footnotes)
				local term = form.form
				if prefix then
					if not term:find("[%[%]]") then
						term = "[[" .. term .. "]]"
					end
					term = prefix .. " " .. term
				end
				table.insert(retval, {term = term, q = quals, refs = refs, genders = genders})
			end
		end

		table.insert(data.inflections, retval)
	end

	if proper then
		table.insert(data.inflections, {label = glossary_link("proper noun")})
	end
	local decl_types = alternant_multiword_spec.decl_type
	if decl_types and #decl_types > 0 then
		local decl_descs = {}
		for _, decl_type in ipairs(decl_types) do
			table.insert(decl_descs, glossary_link(decl_type .. " declension", decl_type))
		end
		table.insert(data.inflections, {label = table.concat(decl_descs, " or ")})
	end
	local overall_adj = alternant_multiword_spec.props.overall_adj
	local article = alternant_multiword_spec.props.article
	local surname = alternant_multiword_spec.props.surname
	local langname = alternant_multiword_spec.props.langname
	local saw_f = false
	local saw_mn = false
	for _, gender in ipairs(alternant_multiword_spec.genders) do
		if gender.spec == "f" then
			saw_f = true
		elseif gender.spec == "m" or gender.spec == "n" then
			saw_mn = true
		end
	end
	local fem_sg_only = not saw_mn and alternant_multiword_spec.number ~= "pl"
	if not alternant_multiword_spec.first_noun and alternant_multiword_spec.first_adj then
		table.insert(data.inflections, {label = "adjectival"})
	end
	if surname then
		table.insert(data.inflections, {label = "surname"})
	end
	if langname then
		table.insert(data.inflections, {label = "language name"})
	end
	if alternant_multiword_spec.number == "pl" then
		table.insert(data.inflections, {label = glossary_link("plural only")})
	end
	if article then
		table.insert(data.inflections, {label = "usually definite"})
	end
	if alternant_multiword_spec.number == "pl" then
		if overall_adj then
			do_noun_form("wk_nom_p", "definite plural", true, nil, nil, "[[die]]")
		end
	elseif surname then
		do_noun_form("gen_m_s", "masculine genitive", true)
		do_noun_form("gen_f_s", "feminine genitive", true)
		do_noun_form("nom_p", "plural", true)
	elseif langname then
		do_noun_form("gen_s", "genitive", true)
		do_noun_form("nom_s_alt", "alternative nominative (used with the definite article)", true)
		do_noun_form("gen_s_alt", "alternative genitive", true)
		-- There should be no plural; this will trigger the display of 'no plural'.
		do_noun_form("nom_p", "plural", true)
	elseif overall_adj then
		if article then
			if not fem_sg_only then
				do_noun_form("wk_nom_s", "definite nominative", true, nil, nil, get_nom_articles(alternant_multiword_spec))
			end
			do_noun_form("wk_gen_s", "definite genitive", true, nil, nil, get_gen_s_articles(alternant_multiword_spec))
			do_noun_form("wk_nom_p", "definite plural", not proper, nil, nil, "[[die]]")
		else
			do_noun_form("wk_nom_s", "definite nominative", true, nil, nil, get_nom_articles(alternant_multiword_spec))
			do_noun_form("str_gen_s", "genitive", true, nil, nil, not saw_f and "([[des]])" or nil)
			if saw_f then
				do_noun_form("wk_gen_s", "definite genitive", true, nil, nil, get_gen_s_articles(alternant_multiword_spec))
			end
			do_noun_form("str_nom_p", "plural", not proper)
			do_noun_form("wk_nom_p", "definite plural", nil, nil, nil, "[[die]]")
		end
	else
		if article then
			do_noun_form("gen_s", "definite genitive", true, nil, nil, get_gen_s_articles(alternant_multiword_spec))
			do_noun_form("nom_p", "definite plural", not proper, nil, nil, "[[die]]")
		else
			do_noun_form("gen_s", "genitive", true)
			do_noun_form("nom_p", "plural", not proper)
		end
	end
	-- FIXME: Should we include the article in the singular equivalent if .article is given? I have no examples to go by.
	do_noun_form("sg", "singular")
	do_noun_form("dim", "diminutive", nil, "diminutive", {"n"}, article and "[[das]]" or nil)
	do_noun_form("m", "masculine", nil, nil, nil, article and "[[der]]" or nil)
	do_noun_form("f", "feminine", nil, "feminine", nil, article and "[[die]]" or nil)
	do_noun_form("n", "neuter", nil, "neuter", nil, article and "[[das]]" or nil)

	-- Add categories.
	for _, cat in ipairs(alternant_multiword_spec.categories) do
		table.insert(data.categories, cat)
	end

	-- Use the "linked" form of the lemma as the head if no head= explicitly given.
	data.no_redundant_head_cat = true -- always set since we have a different lemma linking algorithm from [[Module:headword]].
	if #data.heads == 0 then
		data.heads = {}
		local lemmas = m_de_noun.get_lemmas(alternant_multiword_spec, "linked variant")
		for _, lemma_obj in ipairs(lemmas) do
			local head = alternant_multiword_spec.args.nolinkhead and lemma_obj.form or
				require(headword_utilities_module).add_links_to_multiword_term(lemma_obj.form,
					{split_hyphen_when_space = alternant_multiword_spec.args.splithyph})
			if article and (not overall_adj or fem_sg_only) then
				head = get_nom_articles(alternant_multiword_spec) .. " " .. head
			end
			local quals, refs = require(inflection_utilities_module).
				convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
			table.insert(data.heads, {term = head, q = quals, refs = refs})
		end
	end
end

pos_functions["proper nouns"] = function(class, args, data)
	return pos_functions.nouns(class, args, data, "proper noun")
end

pos_functions.verbs = function(class, args, data)
	if args[2] then -- old-style
		local params = {
			[1] = {list = "pres", required = true},
			["pres_qual"] = {list = "pres\1_qual", allow_holes = true},
			[2] = {list = "past", required = true},
			["past_qual"] = {list = "past\1_qual", allow_holes = true},
			[3] = {list = "pp", required = true},
			["pp_qual"] = {list = "pp\1_qual", allow_holes = true},
			[4] = {list = "pastsubj"},
			["pastsubj_qual"] = {list = "pastsubj\1_qual", allow_holes = true},
			["aux"] = {list = true},
			["aux_qual"] = {list = "aux\1_qual", allow_holes = true},
			["head"] = {list = true},
			["class"] = {list = true},
		}

		local args = require("Module:parameters").process(args, params)
		data.heads = args["head"]

		local function collect_forms(label, accel_form, forms, qualifiers)
			if forms[1] == "-" then
				return {label = "no " .. label}
			else
				-- Disable this for now as deduplication between heardword and conjugation table doesn't work correctly.
				-- local into_table = accel_form and {label = label, accel = {form = accel_form}} or {label = label}
				local into_table = {label = label}
				for i, form in ipairs(forms) do
					table.insert(into_table, {term = form, q = qualifiers[i] and {qualifiers[i]} or nil})
				end
				return into_table
			end
		end

		if #args.class > 0 then
			local class_descs, cats = require("Module:de-verb").process_verb_classes(args.class)
			for _, cats in ipairs(cats) do
				table.insert(data.categories, cats)
			end
			table.insert(data.inflections, {label = require("Module:table").serialCommaJoin(class_descs, {conj = "or"})})
		end
		table.insert(data.inflections, collect_forms("third-person singular present", "3|s|pres", args[1], args.pres_qual))
		table.insert(data.inflections, collect_forms("past tense", "1//3|s|pret", args[2], args.past_qual))
		table.insert(data.inflections, collect_forms("past participle", "perf|part", args[3], args.pp_qual))
		if #args[4] > 0 then
			table.insert(data.inflections, collect_forms("past subjunctive", "1//3|s|sub|II", args[4], args.pastsubj_qual))
		end
		if #args.aux > 0 then
			table.insert(data.inflections, collect_forms("auxiliary", nil, args.aux, args.aux_qual))
		end
		return
	end

	local function get_headword_inflection(forms, label, accel_form)
		if forms then
			-- Disable this for now as deduplication between heardword and conjugation table doesn't work correctly.
			-- local inflection = accel_form and {label = label, accel = {form = accel_form}} or {label = label}
			local inflection = {label = label}
			for _, form in ipairs(forms) do
				local qualifiers
				if form.footnotes then
					qualifiers = {}
					for _, footnote in ipairs(form.footnotes) do
						footnote = footnote:gsub("^%[(.*)%]$", "%1")
						table.insert(qualifiers, footnote)
					end
				end
				table.insert(inflection, {term = form.form, q = qualifiers})
			end
			return inflection
		elseif label then
			return {label = "no " .. label}
		else
			return {}
		end
	end

	local alternant_multiword_spec = require("Module:de-verb").do_generate_forms(args, "from headword")
	for _, cat in ipairs(alternant_multiword_spec.categories) do
		table.insert(data.categories, cat)
	end
	table.insert(data.inflections, {label = table.concat(alternant_multiword_spec.verb_types, " or ")})

	if #data.heads == 0 then
		data.no_redundant_head_cat = true
		for _, head in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
			table.insert(data.heads, head.form)
		end
	end
	table.insert(data.inflections, get_headword_inflection(alternant_multiword_spec.forms.pres_3s,
		"third-person singular present", "3|s|pres"))
	local pret_3s = alternant_multiword_spec.forms.pret_3s
	table.insert(data.inflections, get_headword_inflection(pret_3s, "past tense", "1//3|s|pret"))
	table.insert(data.inflections, get_headword_inflection(alternant_multiword_spec.forms.perf_part,
		"past participle", "perf|part"))
	-- See if we need the past subjunctive, i.e. there exist past subjunctive forms whose stem is not the
	-- same as some past tense form. To facilitate comparison, we truncate final -e in both preterite 3s
	-- and past subjunctive 3s, to handle cases like subjunctive 'ginge aus' vs. preterite 'ging aus'.
	-- We need to compare 3s forms (and not e.g. 3p forms, where the issue with truncating -e doesn't
	-- occur) so we work correctly with impersonal verbs.
	local need_past_subj
	local truncated_pret_3s_forms = {}
	if pret_3s then
		for _, form in ipairs(pret_3s) do
			local truncated_form = form.form:gsub("e$", ""):gsub("e ", " ") -- discard 2nd retval
			table.insert(truncated_pret_3s_forms, truncated_form)
		end
	end
	local subii_3s = alternant_multiword_spec.forms.subii_3s
	local truncated_subii_3s_forms = {}
	if subii_3s then
		for _, form in ipairs(subii_3s) do
			local truncated_form = form.form:gsub("e$", ""):gsub("e ", " ") -- discard 2nd retval
			table.insert(truncated_subii_3s_forms, truncated_form)
		end
	end
	for _, past_subj_form in ipairs(truncated_subii_3s_forms) do
		local saw_same = false
		for _, pret_3s_form in ipairs(truncated_pret_3s_forms) do
			if past_subj_form == pret_3s_form then
				saw_same = true
				break
			end
		end
		if not saw_same then
			need_past_subj = true
			break
		end
	end
	if need_past_subj then
		table.insert(data.inflections, get_headword_inflection(subii_3s, "past subjunctive", "1//3|s|sub|II"))
	end

	local auxes = alternant_multiword_spec.forms.aux
	table.insert(data.inflections, get_headword_inflection(auxes, "auxiliary"))
end

return export