Module:en-headword: difference between revisions

From Wiktionary, the free dictionary
Jump to navigation Jump to search
Content deleted Content added
m make PAGENAME a module-global variable
should probably use ustring functions
(One intermediate revision by the same user not shown)
Line 71: Line 71:
table.insert(data.categories, lang:getCanonicalName() .. " words not following the I before E except after C rule")
table.insert(data.categories, lang:getCanonicalName() .. " words not following the I before E except after C rule")
end
end
if not mw.ustring.toNFD(PAGENAME):lower():find("[aeiouy]") then
if not mw.ustring.find(mw.ustring.lower(mw.ustring.toNFD(PAGENAME)), "[aeiouyæœ]") then
table.insert(data.categories, lang:getCanonicalName() .. " words without vowels")
table.insert(data.categories, lang:getCanonicalName() .. " words without vowels")
end
end

Revision as of 01:19, 31 December 2019

This module is used for English headword-line templates. This module currently implements {{en-noun}}, {{en-proper noun}}, {{en-verb}}, {{en-adj}}, {{en-adv}}, {{en-intj}} and {{en-con}} (for conjunctions). See the documentation of those templates for more information. Other English headword templates are in the process of being converted to use this module.

The module is always invoked the same way, by passing a single parameter to the "show" function. This parameter is the name of the part of speech, but in plural (examples given are for nouns, and for adjective forms respectively):

{{#invoke:en-headword|show|nouns}}
{{#invoke:en-headword|show|adjective forms}}

The template will, by default, accept the following parameters (specific parts of speech may accept or require others):

|head=, |head2=, |head3=, ...
Override the headword display; used to add links to individual words in a multiword term.
|id=
Sense ID for linking to this headword. See {{senseid}} for more information.
|nolink=1 or |nolinkhead=1
Don't link individual words in the headword of a multiword term. Useful for foreign or otherwise unanalyzable terms like a posteriori and yabba dabba doo where the expression functions as a whole in English but the individual parts are not English words.
|splithyph=1
Indicate that automatic splitting and linking of words should split on hyphens in multiword expressions with spaces in them, even if the hyphenated component would normally be linked as-is or with hyphens converted to spaces. See #Autosplitting below.
|nosplithyph=1
Indicate that automatic splitting and linking of words should not split on hyphens in multiword expressions with spaces in them, even if this would normally happen. See #Autosplitting below.
|hyphspace=1
Indicate that hyphenated components should be linked as a whole using the space-separated equivalent, even if this would not normally happen (i.e. because the space-separated equivalent is not defined as an English term). See #Autosplitting below.
|nosuffix=1
Prevent terms beginning with a hyphen from being interpreted as suffixes. See #Suffix handling below.
|nomultiwordcat=1
Prevent multiword terms (those with spaces or with hyphens in the middle) from being added to Category:English multiword terms.
|pagename=
Override the page name used to compute default values of various sorts. Useful when testing, for documentation pages, etc.
|sort=
Sort key. Rarely needs to be specified, as it is normally automatically generated.

Autosplitting

All templates using this module use an intelligent autosplitting algorithm to link portions of multipart and multiword expressions, as follows:

  • If there are spaces in the term but no apostrophes or hyphens, the module will automatically split and link distinct space-separated words, similarly to {{head}}; hence, absent without leave will be linked as [[absent]] [[without]] [[leave]].
  • If there are spaces and apostrophes but no hyphens, the module will likewise split and link distinct space-separated words, but may also split up words with apostrophes in them. Specifically:
    1. If a word ends in 's, the part before the 's will be linked as a word, and the 's will be linked separately to -'s, on the assumption that the 's is functioning as a possessive. For example, Abel's impossibility theorem will be linked as [[Abel]][[-'s|'s]] [[impossibility]] [[theorem]]. (An exception is made for one's, someone's, he's, she's and it's, which are linked as-is without splitting.)
    2. If a word ends in ', the apostrophe will be linked to -' (on the assumption that the ' is functioning as a plural possessive, similarly to above), and the part before will be separately linked. If the part before ends in an s, the module converts it to its singular equivalent and looks that up to see if it exists and has a definition as an English term. If so, the term is linked to the singular form; otherwise, it is linked to the plural form. (Converting to the singular means that -ies becomes -y; -es is dropped after sh, ch and x; and otherwise s is dropped.) For example, flies' graveyard will be linked as [[fly|flies]][[-'|']] [[graveyard]] because fly exists as an English term, but Achilles' heel will be linked as [[Achilles]][[-'|']] [[heel]] because Achille does not exist as an English term.
    3. All other terms containing apostrophes are linked unsplit.
  • If there are hyphens in the term but no spaces or apostrophes, the hyphenated components will be linked individually. For example, beggar-thy-neighbor will be linked as [[beggar]]-[[thy]]-[[neighbor]].
    • An exception to this occurs with certain recognized prefixes, which are linked with the hyphen included in the prefix. For example, Afro-American is linked as [[Afro-]][[American]] and co-occurrence is linked as [[co-]][[occurrence]], because Afro- and co- are in the list of recognized prefixes. (For the full list, see below.)
  • If there are hyphens and apostrophes but no spaces, the effect is similar to the situation with spaces and apostrophes. For example, beggar's-lice is linked as [[beggar]][[-'s|'s]]-[[lice]].
  • If there are both hyphens and spaces, the space-separated components that do not have hyphens will be linked separately, as above. Any hyphen-separated components may be linked in one of three ways:
    1. If |hyphspace=1 is specified or the hyphen-separated component exists as an English term when the hyphens are converted to spaces, it will be linked to that term. For example, closed-circuit television will be linked as [[closed circuit|closed-circuit]] [[television]] because closed circuit exists as an English term. (In this case, closed-circuit also exists but is approximately a soft redirect to closed circuit, as is often the case with such attributive compounds. This is why we prefer the space-separated variant.)
    2. If |nosplithyph=1 is specified or the hyphen-separated component exists as an English term in its unmodified form but not when the hyphens are converted to spaces, it will be linked as an unmodified whole. For example, coin-operated laundry will be linked as [[coin-operated]] [[laundry]] because coin-operated exists as an English term but coin operated does not. (An example that requires |nosplithyph=1 is close-up lens, where the default algorithm would incorrectly link the first component to close up. Here, close up [a verb] and close-up [an adjective] both exist but refer to different things.)
    3. If |splithyph=1 is specified or the hyphen-separated component does not exist as an English term (either unmodified or when the hyphens are converted to spaces), each hyphenated component is linked separately. Examples where this happens are adult-onset diabetes (linked as [[adult]]-[[onset]] [[diabetes]]) and Bombieri-Friedlander-Iwaniec theorem linked as [[Bombieri]]-[[Friedlander]]-[[Iwaniec]] [[theorem]]). Note that when separately linking hyphenated components, prefixes are recognized and handled specially, as documented below.

Special prefix handling

As described above, when splitting hyphenated components, if a component is not the last component and looks like one of the following prefixes, the following hyphen will be included inside of the link.

Suffix handling

If the term begins with a hyphen (-), it is assumed to be a suffix rather than a base form, and is categorized into Category:English suffixes and Category:English POS-forming suffixes rather than Category:English POSs (e.g. Category:English noun-forming suffixes rather than Category:English nouns). This can be overridden using |nosuffix=1. (An example where this is necessary is -ussification, which refers to a linguistic process of blending words with the suffix -ussy but is not itself a suffix.)

Link modifications

The default behavior described above under #Autosplitting is sufficient in most circumstances, but some multiword terms need special linking behavior to handle things like inflected terms (e.g. those ending in -ing or -s), capitalized terms, multiword subexpressions, etc. One way to handle that is to use |head= and spell out the entire headword, appropriately linked, effectively ignoring the default linking behavior. But this can be awkward for long multiword terms. For cases like this, a shortcut syntax is provided to apply link modifications on top of the autolinked term. To enable this, put a tilde (~) at the beginning of the value specified to |head=, followed by the changes to individual words.

For example, for the term acute necrotising ulcerative gingivitis, we would like to link necrotising to necrotise. This can be done as follows:

  • {{en-noun|head=~necrotising:necrotise}}

or more compactely as

This is equivalent to writing {{en-noun|head=[[acute]] [[necrotise|necrotising]] [[ulcerative]] [[gingivitis]]}}, but shorter. In general, syntax of the form prefix[from:to] is equivalent to writing prefixfrom:prefixto, and says to replace prefixfrom with prefixto in the default output produced by the #Autosplitting mechanism described above.

The same syntax works on the beginning of a word, which is especially useful when linking to the lowercase equivalent of a capitalized term. For example, for admiral of the Swiss Navy, use the following to link Navy to navy:

This is equivalent to writing {{en-noun|head=[[admiral]] [[of]] [[the]] [[Swiss]] [[navy|Navy]]}} but shorter.

Modifications need to match full words, but can be applied to multiple words. A ~ on the right-hand side is a shortcut that stands for the left-hand side, which is especially useful when multiple words are given on the left-hand side, and causes the words to be linked together. For example, for acute respiratory distress syndrome, to link respiratory distress as a single entity, use the following:

  • {{en-noun|head=~respiratory distress:~}}

which is equivalent to {{en-noun|head=[[acute]] [[respiratory distress]] [[syndrome]]}}. The right-hand side need not consist solely of a tilde, but can contain other surrounding text. For example, for Charlie Brown Christmas tree, use the following to link to the Wikipedia entry for Charlie Brown:

  • {{en-noun|head=~Charlie Brown:w:~}}

This is equivalent to writing {{en-noun|head=[[w:Charlie Brown|Charlie Brown]] [[Christmas]] [[tree]]}}.

Multiple modifications can be specified, separated by a semicolon (optionally with surrounding spaces). For example, for Admiral of the Fleet, use:

  • {{en-noun|head=~[A:a]dmiral; [F:f]leet}}

This is equivalent to writing {{en-noun|head=[[admiral|Admiral]] [[of]] [[the]] [[fleet|Fleet]]}}.


local export = {}
local pos_functions = {}

local lang = require("Module:languages").getByCode("en")
local PAGENAME = mw.title.getCurrentTitle().text

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 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, default = ""},
		["suff"] = {type = "boolean"},
	}
	
	local pos_data = pos_functions[poscat]
	if pos_data then
		for key, val in pairs(pos_data.params) do
			params[key] = val
		end
	end
	
	local args, unknown_args = require("Module:parameters").process(frame:getParent().args, params, pos_data.return_unknown)
	
	if unknown_args and next(unknown_args) then
		require "Module:debug".track { "en-headword/unknown args", "en-headword/unknown args/POS/" .. tostring(poscat) }
		for parameter, value in pairs(unknown_args) do
			require "Module:debug".track("en-headword/unknown args/param/" .. tostring(parameter))
			mw.log("unknown parameter in [[Module:headword]]: |" .. tostring(parameter) .. "=" .. tostring(value))
		end
	end
	
	local data = {lang = lang, pos_category = poscat, categories = {}, heads = args["head"], inflections = {}}
	
	if args["suff"] then
		data.pos_category = "suffixes"
		
		if poscat == "adjectives" or poscat == "adverbs" or poscat == "nouns" or poscat == "verbs" then
			table.insert(data.categories, ("%s %s-forming suffixes")
				:format(lang:getCanonicalName(), poscat:gsub("s$", "")))
		else
			error("No category exists for suffixes forming " .. poscat .. ".")
		end
	end
	
	if pos_data then
		pos_data.func(args, data)
	end
	
	local extra_categories = {}
	if PAGENAME:find("[Qq][^Uu]") or PAGENAME:find("[Qq]$") then
		table.insert(data.categories, lang:getCanonicalName() .. " words containing Q not followed by U")
	end
	if PAGENAME:find("([A-Za-z])%1%1") then
		table.insert(data.categories, lang:getCanonicalName() .. " words containing three consecutive instances of the same letter")
	end
	if PAGENAME:find("([A-Za-z])%1%1%1") then
		table.insert(data.categories, lang:getCanonicalName() .. " words containing four consecutive instances of the same letter")
	end
	if PAGENAME:find("[^c]ie") or PAGENAME:find("cei") then
		table.insert(data.categories, lang:getCanonicalName() .. " words following the I before E except after C rule")
	end
	if PAGENAME:find("[^c]ei") or PAGENAME:find("cie") then
		table.insert(data.categories, lang:getCanonicalName() .. " words not following the I before E except after C rule")
	end
	if not mw.ustring.find(mw.ustring.lower(mw.ustring.toNFD(PAGENAME)), "[aeiouyæœ]") then
		table.insert(data.categories, lang:getCanonicalName() .. " words without vowels")
	end
	if PAGENAME:find("yre$") then
		table.insert(data.categories, lang:getCanonicalName() .. ' words ending in "-yre"')
	end
	if not PAGENAME:find(" ") and PAGENAME:len() > 25 then
		table.insert(extra_categories, "Long " .. lang:getCanonicalName() .. ' words')
	end
	if PAGENAME:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
		table.insert(data.categories, lang:getCanonicalName() .. ' words that use all vowels in alphabetical order')
	end
	return require("Module:headword").full_headword(data)
		.. (#extra_categories > 0
			and require("Module:utilities").format_categories(extra_categories, lang)
			or "")
end

-- This function does the common work between adjectives and adverbs
function make_comparatives(params, data)
	local comp_parts = {label = glossary_link("comparative"), accel = {form = "comparative"}}
	local sup_parts = {label = glossary_link("superlative"), accel = {form = "superlative"}}
	
	if #params == 0 then
		table.insert(params, {"more"})
	end
	
	-- To form the stem, replace -(e)y with -i and remove a final -e.
	local stem = PAGENAME:gsub("([^aeiou])e?y$", "%1i"):gsub("e$", "")
	
	-- Go over each parameter given and create a comparative and superlative form
	for i, val in ipairs(params) do
		local comp = val[1]
		local sup = val[2]
		
		if comp == "more" and PAGENAME ~= "many" and PAGENAME ~= "much" then
			table.insert(comp_parts, "[[more]] " .. PAGENAME)
			table.insert(sup_parts, "[[most]] " .. PAGENAME)
		elseif comp == "further" and PAGENAME ~= "far" then
			table.insert(comp_parts, "[[further]] " .. PAGENAME)
			table.insert(sup_parts, "[[furthest]] " .. PAGENAME)
		elseif comp == "er" then
			table.insert(comp_parts, stem .. "er")
			table.insert(sup_parts, stem .. "est")
		elseif comp == "-" or sup == "-" then
			-- Allowing '-' makes it more flexible to not have some forms
			if comp ~= "-" then
				table.insert(comp_parts, comp)
			end
			if sup ~= "-" then
				table.insert(sup_parts, sup)
			end
		else
			-- If the full comparative was given, but no superlative, then
			-- create it by replacing the ending -er with -est.
			if not sup then
				if comp:find("er$") then
					sup = comp:gsub("er$", "est")
				else
					error("The superlative of \"" .. comp .. "\" cannot be generated automatically. Please provide it with the \"sup" .. (i == 1 and "" or i) .. "=\" parameter.")
				end
			end
			
			table.insert(comp_parts, comp)
			table.insert(sup_parts, sup)
		end
	end
	
	table.insert(data.inflections, comp_parts)
	table.insert(data.inflections, sup_parts)
end

pos_functions["adjectives"] = {
	params = {
		[1] = {list = true, allow_holes = true},
		["sup"] = {list = true, allow_holes = true},
		},
	func = function(args, data)
		local shift = 0
		local is_not_comparable = false
		local is_comparative_only = false
		
		-- If the first parameter is ?, then don't show anything, just return.
		if args[1][1] == "?" then
			return
		-- If the first parameter is -, then move all parameters up one position.
		elseif args[1][1] == "-" then
			shift = 1
			is_not_comparable = true
		-- If the only argument is +, then remember this and clear parameters
		elseif args[1][1] == "+" and args[1].maxindex == 1 then
			shift = 1
			is_comparative_only = true
		end
		
		-- Gather all the comparative and superlative parameters.
		local params = {}
		
		for i = 1, args[1].maxindex - shift do
			local comp = args[1][i + shift]
			local sup = args["sup"][i]
			
			if comp or sup then
				table.insert(params, {comp, sup})
			end
		end
		
		if shift == 1 then
			-- If the first parameter is "-" but there are no parameters,
			-- then show "not comparable" only and return.
			-- If there are parameters, then show "not generally comparable"
			-- before the forms.
			if #params == 0 then
				if is_not_comparable then
					table.insert(data.inflections, {label = "not " .. glossary_link("comparable")})
					table.insert(data.categories, lang:getCanonicalName() .. " uncomparable adjectives")
					return
				end
				if is_comparative_only then
					table.insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
					table.insert(data.categories, lang:getCanonicalName() .. " comparative-only adjectives")
					return
				end
			else
				table.insert(data.inflections, {label = "not generally " .. glossary_link("comparable")})
			end
		end
		
		-- Process the parameters
		make_comparatives(params, data)
	end
}

pos_functions["adverbs"] = {
	params = {
		[1] = {list = true, allow_holes = true},
		["sup"] = {list = true, allow_holes = true},
		},
	func = function(args, data)
		local shift = 0
		
		-- If the first parameter is ?, then don't show anything, just return.
		if args[1][1] == "?" then
			return
		-- If the first parameter is -, then move all parameters up one position.
		elseif args[1][1] == "-" then
			shift = 1
		end
		
		-- Gather all the comparative and superlative parameters.
		local params = {}
		
		for i = 1, args[1].maxindex - shift do
			local comp = args[1][i + shift]
			local sup = args["sup"][i]
			
			if comp or sup then
				table.insert(params, {comp, sup})
			end
		end
		
		if shift == 1 then
			-- If the first parameter is "-" but there are no parameters,
			-- then show "not comparable" only and return. If there are parameters,
			-- then show "not generally comparable" before the forms.
			if #params == 0 then
				table.insert(data.inflections, {label = "not " .. glossary_link("comparable")})
				table.insert(data.categories, lang:getCanonicalName() .. " uncomparable adverbs")
				return
			else
				table.insert(data.inflections, {label = "not generally " .. glossary_link("comparable")})
			end
		end
		
		-- Process the parameters
		make_comparatives(params, data)
	end
}

pos_functions["conjunctions"] = {
	params = {
		[1] = { alias_of = "head" },
	},
	return_unknown = true,
	func = function (args, data)
		
	end,
}

pos_functions["interjections"] = {
	params = {
		[1] = { alias_of = "head" },
	},
	return_unknown = true,
	func = function (args, data)
		
	end,
}

pos_functions["nouns"] = {
	params = {
		[1] = {list = true, allow_holes = true},
		
		-- TODO: This should really be a list parameter too...
		["plqual"] = {},
		["pl2qual"] = {},
		["pl3qual"] = {},
		["pl4qual"] = {},
		["pl5qual"] = {},
		},
	func = function(args, data)
		-- Gather all the plural parameters from the numbered parameters.
		local plurals = {}
		
		for i = 1, args[1].maxindex do
			local pl = args[1][i]
			
			if pl then
				local qual = args["pl" .. (i == 1 and "" or i) .. "qual"]
				
				if qual then
					table.insert(plurals, {term = pl, qualifiers = {qual}})
				else
					table.insert(plurals, pl)
				end
			end
		end
		
		-- Decide what to do next...
		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
		
		-- Plural is unknown
		if mode == "?" then
			table.insert(data.categories, lang:getCanonicalName() .. " nouns with unknown or uncertain plurals")
			return
		-- Plural is not attested
		elseif mode == "!" then
			table.insert(data.inflections, {label = "plural not attested"})
			table.insert(data.categories, lang:getCanonicalName() .. " nouns with unattested plurals")
			return
		-- Uncountable noun; may occasionally have a plural
		elseif mode == "-" then
			table.insert(data.categories, lang:getCanonicalName() .. " 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, lang:getCanonicalName() .. " countable nouns")
			else
				table.insert(data.inflections, {label = glossary_link("uncountable")})
			end
		-- Mixed countable/uncountable noun, always has a plural
		elseif mode == "~" then
			table.insert(data.inflections, {label = glossary_link("countable") .. " and " .. glossary_link("uncountable")})
			table.insert(data.categories, lang:getCanonicalName() .. " uncountable nouns")
			table.insert(data.categories, lang:getCanonicalName() .. " countable nouns")
			
			-- If no plural was given, add a default one now
			if #plurals == 0 then
				plurals = {"s"}
			end
		-- The default, always has a plural
		else
			table.insert(data.categories, lang:getCanonicalName() .. " countable nouns")
			
			-- If no plural was given, add a default one now
			if #plurals == 0 then
				plurals = {"s"}
			end
			if plural and not mw.title.new(plural).exists then
				table.insert(categories, "English nouns with missing plurals")
			end
		end
		
		-- If there are no plurals to show, return now
		if #plurals == 0 then
			return
		end
		
		-- There are plural forms to show, so show them
		local pl_parts = {label = "plural", accel = {form = "p"}}
		
		local function check_ies(pl, stem)
			local newplural, nummatches = stem:gsub("([^aeiou])y$","%1ies")
			return nummatches > 0 and pl == newplural
		end
		local stem = PAGENAME
		local irregular = false
		for i, pl in ipairs(plurals) do
			if pl == "s" then
				table.insert(pl_parts, stem .. "s")
			elseif pl == "es" then
				table.insert(pl_parts, stem .. "es")
			else
				table.insert(pl_parts, pl)
				if type(pl) == "table" then
					pl = pl.term
				end
				if not stem:find(" ") and not (pl == stem .. "s" or pl == stem .. "es" or check_ies(pl, stem)) then
					irregular = true
					if pl == stem then
						table.insert(data.categories, lang:getCanonicalName() .. " indeclinable nouns")
					end
				end
			end
		end
		if irregular then
			table.insert(data.categories, lang:getCanonicalName() .. " nouns with irregular plurals")
		end
		
		table.insert(data.inflections, pl_parts)
	end
}

pos_functions["proper nouns"] = {
	params = {
		[1] = {list = true},
		},
	func = function(args, data)
		local plurals = args[1]
		
		-- Decide what to do next...
		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
		
		-- Plural is unknown
		if mode == "?" then
			table.insert(data.categories, lang:getCanonicalName() .. " proper nouns with unknown or uncertain plurals")
			return
		-- Plural is not attested
		elseif mode == "!" then
			table.insert(data.inflections, {label = "plural not attested"})
			table.insert(data.categories, lang:getCanonicalName() .. " proper nouns with unattested plurals")
			return
		-- Uncountable noun; may occasionally have a plural
		elseif mode == "-" then
			-- 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, lang:getCanonicalName() .. " countable proper nouns")
			else
				table.insert(data.inflections, {label = glossary_link("uncountable")})
			end
		-- Mixed countable/uncountable noun, always has a plural
		elseif mode == "~" then
			table.insert(data.inflections, {label = glossary_link("countable") .. " and " .. glossary_link("uncountable")})
			table.insert(data.categories, lang:getCanonicalName() .. " countable proper nouns")
			
			-- If no plural was given, add a default one now
			if #plurals == 0 then
				plurals = {"s"}
			end
		elseif #plurals > 0 then
			table.insert(data.categories, lang:getCanonicalName() .. " countable proper nouns")
		end
		
		-- If there are no plurals to show, return now
		if #plurals == 0 then
			return
		end
		
		-- There are plural forms to show, so show them
		local pl_parts = {label = "plural", accel = {form = "p"}}
		
		local stem = PAGENAME
		
		for i, pl in ipairs(plurals) do
			if pl == "s" then
				table.insert(pl_parts, stem .. "s")
			elseif pl == "es" then
				table.insert(pl_parts, stem .. "es")
			else
				table.insert(pl_parts, pl)
			end
	
		end
		
		table.insert(data.inflections, pl_parts)
	end
}

pos_functions["verbs"] = {
	params = {
		[1] = {list = "pres_3sg", allow_holes = true},
		
		["pres_3sg_qual"] = {},
		["pres_3sg2_qual"] = {},
		["pres_3sg3_qual"] = {},
		["pres_3sg4_qual"] = {},
		["pres_3sg5_qual"] = {},
		
		[2] = {list = "pres_ptc", allow_holes = true},
		
		["pres_ptc_qual"] = {},
		["pres_ptc2_qual"] = {},
		["pres_ptc3_qual"] = {},
		["pres_ptc4_qual"] = {},
		["pres_ptc5_qual"] = {},
		
		[3] = {list = "past", allow_holes = true},
		
		["past_qual"] = {},
		["past2_qual"] = {},
		["past3_qual"] = {},
		["past4_qual"] = {},
		["past5_qual"] = {},
		
		[4] = {list = "past_ptc", allow_holes = true},
		
		["past_ptc_qual"] = {},
		["past_ptc2_qual"] = {},
		["past_ptc3_qual"] = {},
		["past_ptc4_qual"] = {},
		["past_ptc5_qual"] = {},
		},
	func = function(args, data)
		-- Get parameters
		local par1 = args[1][1]
		local par2 = args[2][1]
		local par3 = args[3][1]
		local par4 = args[4][1]
		
		local pres_3sg_forms = {label = "third-person singular simple present", accel = {form = "3|s|pres"}}
		local pres_ptc_forms = {label = "present participle", accel = {form = "pres|ptcp"}}
		local past_forms = {label = "simple past", accel = {form = "past"}}
		local pres_3sg_form = par1 or PAGENAME .. "s"
		local pres_ptc_form = par2 or PAGENAME .. "ing"
		local past_form = par3 or PAGENAME .. "ed"
		
		if par1 and not par2 and not par3 then
			-- This is the "new" format, which uses only the first parameter.
			if par1 == "es" then
				pres_3sg_form = PAGENAME .. "es"
				pres_ptc_form = PAGENAME .. "ing"
				past_form = PAGENAME .. "ed"
			elseif par1 == "ies" then
				if not mw.ustring.find(PAGENAME, "y$") then
					error("The first parameter is \"ies\" but the verb does not end in -y.")
				end
				
				local stem = mw.ustring.gsub(PAGENAME, "y$", "")
				pres_3sg_form = stem .. "ies"
				pres_ptc_form = stem .. "ying"
				past_form = stem .. "ied"
			elseif par1 == "d" then
				pres_3sg_form = PAGENAME .. "s"
				pres_ptc_form = PAGENAME .. "ing"
				past_form = PAGENAME .. "d"
			else
				pres_3sg_form = PAGENAME .. "s"
				pres_ptc_form = par1 .. "ing"
				past_form = par1 .. "ed"
			end
		else
			-- This is the "legacy" format, using the second and third parameters as well.
			-- It is included here for backwards compatibility and to ease the transition.
			if par3 then
				if par3 == "es" then
					require("Module:debug").track("en-headword/es3")
					pres_3sg_form = par1 .. par2 .. "es"
					pres_ptc_form = par1 .. par2 .. "ing"
					past_form = par1 .. par2 .. "ed"
				elseif par3 == "ing" then
					require("Module:debug").track("en-headword/ing3")
					pres_3sg_form = PAGENAME .. "s"
					pres_ptc_form = par1 .. par2 .. "ing"
					
					if par2 == "y" then
						past_form = PAGENAME .. "d"
					else
						past_form = par1 .. par2 .. "ed"
					end
				elseif par3 == "ed" then
					require("Module:debug").track("en-headword/ed3")
					
					if par2 == "i" then
						pres_3sg_form = par1 .. par2 .. "es"
						pres_ptc_form = PAGENAME .. "ing"
					else
						pres_3sg_form = PAGENAME .. "s"
						pres_ptc_form = par1 .. par2 .. "ing"
					end
					
					past_form = par1 .. par2 .. "ed"
				elseif par3 == "d" then
					require("Module:debug").track("en-headword/d3")
					pres_3sg_form = PAGENAME .. "s"
					pres_ptc_form = par1 .. par2 .. "ing"
					past_form = par1 .. par2 .. "d"
				else
					require("Module:debug").track("en-headword/xxx3")
				end
			else
				if par2 == "es" then
					require("Module:debug").track("en-headword/es2")
					pres_3sg_form = par1 .. "es"
					pres_ptc_form = par1 .. "ing"
					past_form = par1 .. "ed"
				elseif par2 == "ies" then
					require("Module:debug").track("en-headword/ies2")
					
					if par1 .. "y" ~= PAGENAME then
						require("Module:debug").track("en-headword/ies2/par1 not pagename")
					end
					
					pres_3sg_form = par1 .. "ies"
					pres_ptc_form = par1 .. "ying"
					past_form = par1 .. "ied"
				elseif par2 == "ing" then
					require("Module:debug").track("en-headword/ing2")
					pres_3sg_form = PAGENAME .. "s"
					pres_ptc_form = par1 .. "ing"
					past_form = par1 .. "ed"
				elseif par2 == "ed" then
					require("Module:debug").track("en-headword/ed2")
					pres_3sg_form = PAGENAME .. "s"
					pres_ptc_form = par1 .. "ing"
					past_form = par1 .. "ed"
				elseif par2 == "d" then
					require("Module:debug").track("en-headword/d2")
					
					if par1 ~= PAGENAME then
						require("Module:debug").track("en-headword/d2/par1 not pagename")
					end
					
					pres_3sg_form = PAGENAME .. "s"
					pres_ptc_form = par1 .. "ing"
					past_form = par1 .. "d"
				else
					require("Module:debug").track("en-headword/xxx2")
				end
			end
		end
		
		local pres_3sg_qual = args["pres_3sg_qual"]
		local pres_ptc_qual = args["pres_ptc_qual"]
		local past_qual = args["past_qual"]
		
		table.insert(pres_ptc_forms, {term = pres_ptc_form, qualifiers = {pres_ptc_qual}})
		table.insert(pres_3sg_forms, {term = pres_3sg_form, qualifiers = {pres_3sg_qual}})
		table.insert(past_forms, {term = past_form, qualifiers = {past_qual}})
		
		-- Present 3rd singular
		for i = 2, args[1].maxindex do
			local form = args[1][i]
			local qual = args["pres_3sg" .. i .. "_qual"]
			
			if form then
				table.insert(pres_3sg_forms, {term = form, qualifiers = {qual}})
			end
		end
		
		-- Present participle
		for i = 2, args[2].maxindex do
			local form = args[2][i]
			local qual = args["pres_ptc" .. i .. "_qual"]
			
			if form then
				table.insert(pres_ptc_forms, {term = form, qualifiers = {qual}})
			end
		end
		
		-- Past
		for i = 2, args[3].maxindex do
			local form = args[3][i]
			local qual = args["past" .. i .. "_qual"]
			
			if form then
				table.insert(past_forms, {term = form, qualifiers = {qual}})
			end
		end
		
		-- Past participle
		local found_past_ptc = false
		local past_ptc_forms = {label = "past participle", accel = {form = "past|ptcp"}}
		local qual = args["past_ptc_qual"]; if qual == "" then qual = nil end
		table.insert(past_ptc_forms, {term = par4 or past_form, qualifiers = {qual}})
		if par4 or qual then
			found_past_ptc = true
		end
		for i = 2, args[4].maxindex do
			local form = args[4][i]
			local qual = args["past_ptc" .. i .. "_qual"]
			if form then
				table.insert(past_ptc_forms, {term = form, qualifiers = {qual}})
				found_past_ptc = true
			end
		end
		
		-- Are the past forms identical to the past participle forms?
		local identical = true
		
		if #past_forms ~= #past_ptc_forms then
			identical = false
		else
			for key, val in ipairs(past_forms) do
				if past_ptc_forms[key].term ~= val.term or past_ptc_forms[key].qual ~= val.qual then
					identical = false
					break
				end
			end
		end
		
		-- Insert the forms
		table.insert(data.inflections, pres_3sg_forms)
		table.insert(data.inflections, pres_ptc_forms)
		
		if not found_past_ptc or identical then
			past_forms.label = "simple past and past participle"
			past_forms.accel = {form = "past|and|past|ptcp"}
			table.insert(data.inflections, past_forms)
		else
			table.insert(data.inflections, past_forms)
			table.insert(data.inflections, past_ptc_forms)
		end
	end
}

return export