Jump to content

Module:es-headword

Hali sa Wiksyunaryo

Documentation for this module may be created at Module:es-headword/doc

local export = {}
local pos_functions = {}

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

local PAGENAME = mw.title.getCurrentTitle().text

local suffix_categories = {
	["adjectives"] = true,
	["adverbs"] = true,
	["nouns"] = true,
	["verbs"] = true,
}

local remove_stress = {
	["á"] = "a", ["é"] = "e", ["í"] = "i", ["ó"] = "o", ["ú"] = "u"
}
local add_stress = {
	["a"] = "á", ["e"] = "é", ["i"] = "í", ["o"] = "ó", ["u"] = "ú"
}

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

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

-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
	local tracking_categories = {}
	
	local poscat = frame.args[1]
		or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
	
	local params = {
		["head"] = {list = true},
		["suff"] = {type = "boolean"},
		["json"] = {type = "boolean"},
	}

	local parargs = frame:getParent().args
	if poscat == "nouns" and (not parargs[2] or parargs[2] == "") and parargs.pl2 then
		track("noun-pl2-without-pl")
	end

	if pos_functions[poscat] then
		for key, val in pairs(pos_functions[poscat].params) do
			params[key] = val
		end
	end
	
	local args = require("Module:parameters").process(parargs, params)
	local data = {
		lang = lang,
		pos_category = poscat,
		categories = {},
		heads = args["head"],
		genders = {},
		inflections = {},
		categories = {}
	}
	
	if args["suff"] then
		data.pos_category = "suffixes"
		
		if suffix_categories[poscat] then
			local singular_poscat = poscat:gsub("s$", "")
			table.insert(data.categories, "Spanish " .. singular_poscat .. "-forming suffixes")
		else
			error("No category exists for suffixes forming " .. poscat .. ".")
		end
	end
	
	if pos_functions[poscat] then
		pos_functions[poscat].func(args, data, tracking_categories)
	end
	
	if args["json"] then
		return require("Module:JSON").toJSON(data)
	end

	return require("Module:headword").full_headword(data)
		.. require("Module:utilities").format_categories(tracking_categories, lang)
end


local function add_ending_to_plurals(plurals, ending)
	local retval = {}
	for _, pl in ipairs(plurals) do
		table.insert(retval, pl .. ending)
	end
	return retval
end


function export.make_plural_noun(singular, gender)
	-- noun + adjective
	if singular:find(" ") then
		if singular:find(" del? .+$") then
			local preceding, prep_phrase = singular:match("^(.+)( del? .+)$")
			
			local preceding_pl = export.make_plural_noun(preceding, gender)
			if preceding_pl then
				return add_ending_to_plurals(preceding_pl, prep_phrase)
			else
				return nil
			end
		elseif singular:find(" al? .+$") then
			local preceding, prep_phrase = singular:match("^(.+)( al? .+)$")

			local preceding_pl = export.make_plural_noun(preceding, gender)
			if preceding_pl then
				return add_ending_to_plurals(preceding_pl, prep_phrase)
			else
				return nil
			end
		else
			local words = mw.text.split(singular, " ")
			if #words == 2 then
				local noun_p = export.make_plural_noun(words[1], gender)
				local adj_p = export.adjective_forms(words[2], gender)

				if noun_p then
					if gender == "m" and adj_p then
						return add_ending_to_plurals(noun_p, " " .. adj_p.mp)
					elseif gender == "f" and adj_p then
						return add_ending_to_plurals(noun_p, " " .. adj_p.fp)
					end
				end
			end
		end
	end
	
	-- ends in unstressed vowel or á, é, ó
	if mw.ustring.match(singular, "[aeiouáéó]$") then return {singular .. "s"} end
	
	-- ends in í or ú
	if mw.ustring.match(singular, "[íú]$") then
		return {singular .. "s", singular .. "es"}
	end
	
	-- ends in a vowel + z
	if mw.ustring.match(singular, "[aeiouáéíóú]z$") then
		-- discard all but first return value
		local retval = mw.ustring.gsub(singular, "z$", "ces")
		return {retval}
	end
	
	-- ends in tz
	if mw.ustring.match(singular, "tz$") then return {singular} end

	local vowels = {}
	-- Replace qu before e or i with k so that the u isn't counted as a vowel.
	local modified_singular = mw.ustring.gsub(singular, "qu([ie])", "k%1")
	for i in mw.ustring.gmatch(modified_singular, "[aeiouáéíóú]") do vowels[#vowels + 1] = i end
	
	-- ends in s or x with more than 1 syllable, last syllable unstressed
	if vowels[2] and mw.ustring.match(singular, "[sx]$")
	and mw.ustring.match(vowels[#vowels], "[aeiou]") then
		return {singular}
	end
	
	-- ends in l, r, n, d, z, or j with 3 or more syllables, accented on third to last syllable
	if vowels[3] and mw.ustring.match(singular, "[lrndzj]$")
	and mw.ustring.match(vowels[#vowels-2], "[áéíóú]") then
		return {singular}
	end
	
	-- ends in a in a stressed vowel + consonant
	if mw.ustring.match(singular, "[áéíóú][^aeiouáéíóú]$") then
		-- discard all but first return value
		local retval = mw.ustring.gsub(
			singular,
			"(.)(.)$",
			function (vowel, consonant)
				return remove_stress[vowel] .. consonant .. "es"
			end)
		return {retval}
	end
	
	-- ends in a vowel + y, l, r, n, d, j, s, x
	if mw.ustring.match(singular, "[aeiou][ylrndjsx]$") then
		--  two or more vowels: add stress mark to plural
		if vowels[2] and mw.ustring.match(singular, "n$") then
			local before_stress, after_stress = mw.ustring.match(
				modified_singular,
				"^(.*)[aeiou]([^aeiou]*[aeiou][nl])$")
			local stress = add_stress[vowels[#vowels - 1]]
			if before_stress and stress then
				-- discard all but first return value
				local retval = (before_stress .. stress .. after_stress .. "es"):gsub("k", "qu")
				return {retval}
			end
		end
		
		return {singular .. "es"}
	end
	
	-- ends in a vowel + ch
	if mw.ustring.match(singular, "[aeiou]ch$") then return {singular .. "es"} end
	
	-- ends in two consonants
	if mw.ustring.match(singular, "[^aeiouáéíóú][^aeiouáéíóú]$") then return {singular .. "s"} end
	
	-- ends in a vowel + consonant other than l, r, n, d, z, j, s, or x
	if mw.ustring.match(singular, "[aeiou][^aeioulrndzjsx]$") then return {singular .. "s"} end

	return nil
end

function export.adjective_forms(singular, gender)
	local last_two, last = mw.ustring.match(singular, "(.(.))$")
	
	if mw.ustring.match(singular, "dor$") and gender == "m" then
		return {
			['ms'] = singular,
			['mp'] = singular .. 'es',
			['fs'] = singular .. 'a',
			['fp'] = singular .. 'as'
		}
	end
	
	if mw.ustring.match(singular, "dora$") and gender == "f" then
		local stem = mw.ustring.sub(singular, 1, #singular-1)
		return {
			['ms'] = stem,
			['mp'] = stem .. 'es',
			['fs'] = stem .. 'a',
			['fp'] = stem .. 'as'
		}
	end
	
	if last == "o" or last == "a" and gender == "f" then
		local stem = mw.ustring.match(singular, "^(.+)[ao]$")
		return {
			['ms'] = stem .. "o",
			['mp'] = stem .. "os",
			['fs'] = stem .. "a",
			['fp'] = stem .. "as"
		}
	end
	
	if last == "e" or mw.ustring.match(singular, "ista$") then
		local plural = singular..'s'
		return {
			['ms'] = singular,
			['mp'] = plural,
			['fs'] = singular,
			['fp'] = plural
		}
	end
	
	if last == "z" then
		local plural = mw.ustring.gsub(singular, "z$", "ces")
		return {
			['ms'] = singular,
			['mp'] = plural,
			['fs'] = singular,
			['fp'] = plural
		}
	end
	
	local function make_stem(singular)
		return mw.ustring.gsub(
			singular,
			"^(.+)(.)(.)$",
			function (before_stress, stressed_vowel, after_stress)
				return before_stress .. (remove_stress[stressed_vowel] or stressed_vowel) .. after_stress
			end)
	end
	
	if last_two == "ar" or last_two == "ón" or last_two == "ún" or last == "l" then
		local plural = make_stem(singular).."es"
		return {
			['ms'] = singular,
			['mp'] = plural,
			['fs'] = singular,
			['fp'] = plural
		}
	end
	
	if last_two == "or" then
		return {
			['ms'] = singular,
			['mp'] = singular.."es",
			['fs'] = singular,
			['fp'] = singular.."es"
		}
	end

	if last_two == "án" or last_two == "és" or last_two == "ín" then
		local stem = make_stem(singular)
		return {
			['ms'] = singular,
			['mp'] = stem.."es",
			['fs'] = stem.."a",
			['fp'] = stem.."as"
		}
	end
	
	return nil
end

-- Display information for a noun's gender
-- This is separate so that it can also be used for proper nouns
function noun_gender(args, data)
	local gender = args[1]
	table.insert(data.genders, gender)
	if #data.genders == 0 then
		table.insert(data.genders, "?")
	end
end

pos_functions["proper nouns"] = {
	params = {
		[1] = {},
		},
	func = function(args, data)
		noun_gender(args, data)
	end
}

-- Display additional inflection information for a noun
pos_functions["nouns"] = {
	params = {
		[1] = {required = true, default = "m"}, --gender
		["g2"] = {}, --second gender
		["e"] = {type = "boolean"}, --epicene
		[2] = {list = "pl"}, --plural override(s)
		["f"] = {list = true}, --feminine form(s)
		["m"] = {list = true}, --masculine form(s)
		["fpl"] = {list = true}, --feminine plural override(s)
		["mpl"] = {list = true}, --masculine plural override(s)
		["title"] = {},
	},
	func = function(args, data, tracking_categories)
		local langname = lang:getCanonicalName()

		local allowed_genders = {
			["m"] = true,
			["f"] = true,
			["m-p"] = true,
			["f-p"] = true,
			["mf"] = true,
			["mf-p"] = true,
			["mfbysense"] = true,
			["mfbysense-p"] = true,
		}

		if args["title"] then
			track("noun-title")
		end

		local title = require("Module:links").remove_links(
			args["title"] or (#data.heads > 0 and data.heads[1]) or mw.title.getCurrentTitle().text)

		if args[1] == "m-f" then
			args[1] = "mf"
		elseif args[1] == "mfp" or args[1] == "m-f-p" then
			args[1] = "mf-p"
		end
		
		if not allowed_genders[args[1]] then error("Unrecognized gender: " .. args[1]) end

		table.insert(data.genders, args[1])	

		local function check_missing(form)
			if type(form) == "table" then
				form = form.term
			end
			if form and not mw.title.new(form).exists then
				table.insert(tracking_categories, "Spanish nouns with red links in their headword lines")
			end
		end
	
		local function check_all_missing(forms)
			for _, form in ipairs(forms) do
				check_missing(form)
			end
		end
	
		if args.g2 then table.insert(data.genders, args.g2) end
		
		if args["e"] then
			table.insert(data.categories, langname .. " epicene nouns")
			table.insert(data.inflections, {label = glossary_link("epicene")})
		end

		local plurals = {}

		if args[1]:find("%-p$") then
			table.insert(data.inflections, {label = glossary_link("plural only")})
			if #args[2] > 0 then
				error("Can't specify plurals of a plurale tantum noun")
			end
		else
			-- Gather plurals, handling requests for default plurals
			for _, pl in ipairs(args[2]) do
				if pl == "1" then
					track("noun-pl-1")
				end
				if pl == "1" or pl == "+" then
					local default_pls = export.make_plural_noun(title, args[1])
					for _, defp in ipairs(default_pls) do
						table.insert(plurals, defp)
					end
				else
					table.insert(plurals, pl)
				end
			end

			-- Check for special plural signals
			local mode = nil
			
			if plurals[1] == "?" or plurals[1] == "!" or plurals[1] == "-" or plurals[1] == "~" then
				mode = plurals[1]
				table.remove(plurals, 1)  -- Remove the mode parameter
			end
			
			if mode == "?" then
				-- Plural is unknown
				table.insert(data.categories, langname .. " nouns with unknown or uncertain plurals")
			elseif mode == "!" then
				-- Plural is not attested
				table.insert(data.inflections, {label = "plural not attested"})
				table.insert(data.categories, langname .. " nouns with unattested plurals")
				return
			elseif mode == "-" then
				-- Uncountable noun; may occasionally have a plural
				table.insert(data.categories, langname .. " uncountable nouns")
				
				-- If plural forms were given explicitly, then show "usually"
				if #plurals > 0 then
					table.insert(data.inflections, {label = "usually " .. glossary_link("uncountable")})
					table.insert(data.categories, langname .. " countable nouns")
				else
					table.insert(data.inflections, {label = glossary_link("uncountable")})
				end
			else
				-- Countable or mixed countable/uncountable
				if #plurals == 0 then
					local pls = export.make_plural_noun(title, args[1])
					if pls then
						for _, pl in ipairs(pls) do
							table.insert(plurals, pl)
						end
					end
				end
				if mode == "~" then
					-- Mixed countable/uncountable noun, always has a plural
					table.insert(data.inflections, {label = glossary_link("countable") .. " and " .. glossary_link("uncountable")})
					table.insert(data.categories, langname .. " uncountable nouns")
					table.insert(data.categories, langname .. " countable nouns")
				else
					-- Countable nouns
					table.insert(data.categories, langname .. " countable nouns")
				end
			end
		end

		local masculines = {}
		local feminines = {}
		local masculine_plurals = {}
		local feminine_plurals = {}
	
		-- Gather feminines. For each feminine, generate the corresponding plural(s).
		for _, f in ipairs(args.f) do
			if f == "1" then
				track("noun-f-1")
			end
			if f == "1" or f == "+" then
				-- Generate default feminine.
				local noun_forms = export.adjective_forms(title, "m")
				if not noun_forms then
					error("Unable to generate default feminine of '" .. title .. "'")
				end
				f = noun_forms.fs
			end
			table.insert(feminines, f)
			local fpls = export.make_plural_noun(f, "f")
			if fpls then
				for _, pl in ipairs(fpls) do
					-- Add an accelerator for each feminine plural whose lemma
					-- is the feminine singular, so that the accelerated entry
					-- that is generated has a definition that looks like
					-- # {{plural of|es|FEMININE}}
					table.insert(feminine_plurals, {term = pl, accel = {form = "p", lemma = f}})
				end
			end
		end
	
		-- Gather feminines. For each masculine, generate the corresponding plural(s).
		for _, m in ipairs(args.m) do
			if m == "1" then
				track("noun-m-1")
			end
			if m == "1" or m == "+" then
				-- Generate default masculine.
				local noun_forms = export.adjective_forms(title, "f")
				if not noun_forms then
					error("Unable to generate default masculine of '" .. title .. "'")
				end
				m = noun_forms.ms
			end
			table.insert(masculines, m)
			local mpls = export.make_plural_noun(m, "m")
			if mpls then
				for _, pl in ipairs(mpls) do
					table.insert(masculine_plurals, pl)
				end
			end
		end
	
		if #args.fpl > 0 then
			-- Override any existing feminine plurals.
			if #args.fpl == #feminines then
				-- If same number of overriding feminine plurals as feminines,
				-- assume each feminine plural goes with the corresponding feminine
				-- and use each corresponding feminine as the lemma in the accelerator.
				-- The generated entry will have # {{plural of|es|FEMININE}} as the
				-- definition.
				feminine_plurals = {}
				for i, fpl in ipairs(args.fpl) do
					table.insert(feminine_plurals, {term = fpl, accel = {form = "p", lemma = feminines[i]}})
				end
			else
				-- Otherwise, don't add any accelerators.
				feminine_plurals = args.fpl
			end
		end
		if #args.mpl > 0 then
			-- Override any existing masculine plurals.
			masculine_plurals = args.mpl
		end
	
		check_all_missing(plurals)
		check_all_missing(feminines)
		check_all_missing(feminine_plurals)
		check_all_missing(masculines)
		check_all_missing(masculine_plurals)

		local function redundant_plural(pl)
			for _, p in ipairs(plurals) do
				if p == pl then
					return true
				end
			end
			return false
		end

		for _, mpl in ipairs(masculine_plurals) do
			if redundant_plural(mpl) then
				track("noun-redundant-mpl")
			end
		end
		
		for _, fpl in ipairs(feminine_plurals) do
			if redundant_plural(fpl) then
				track("noun-redundant-fpl")
			end
		end

		if #plurals > 0 then
			plurals.label = "plural"
			plurals.accel = {form = "p"}
			table.insert(data.inflections, plurals)
		end
	
		if #feminines > 0 then
			feminines.label = "feminine"
			feminines.accel = {form = "f"}
			table.insert(data.inflections, feminines)
		end
		if #feminine_plurals > 0 then
			feminine_plurals.label = "feminine plural"
			table.insert(data.inflections, feminine_plurals)
		end
	
		if #masculines > 0 then
			masculines.label = "masculine"
			table.insert(data.inflections, masculines)
		end
		if #masculine_plurals > 0 then
			masculine_plurals.label = "masculine plural"
			masculine_plurals.accel = {form = "p"}
			table.insert(data.inflections, masculine_plurals)
		end
	end
}

return export