Module:ca-headword
Documentation for this module may be created at Module:ca-headword/doc
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local m_table = require("Module:table")
local com = require("Module:ca-common")
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local romut_module = "Module:romance utilities"
local ca_verb_module = "Module:ca-verb"
local ca_IPA_module = "Module:ca-IPA"
local lang = require("Module:languages").getByCode("ca")
local langname = lang:getCanonicalName()
local list_to_text = mw.text.listToText
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local usub = mw.ustring.sub
local rsub = com.rsub
local function track(page)
require("Module:debug/track")("ca-headword/" .. page)
return true
end
-----------------------------------------------------------------------------------------
-- Main entry point --
-----------------------------------------------------------------------------------------
-- 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},
["id"] = {},
["splithyph"] = {type = "boolean"},
["nolinkhead"] = {type = "boolean"},
["json"] = {type = "boolean"},
["pagename"] = {}, -- for testing
}
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(frame:getParent().args, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolinkhead then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
if head == auto_linked_head then
track("redundant-head")
end
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local is_suffix = false
if pagename:find("^%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = require("Module:string utilities").singularize(poscat)
table.insert(data.categories, langname .. " " .. singular_poscat .. "-forming suffixes")
table.insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data, is_suffix)
end
if args.json then
return require("Module:JSON").toJSON(data)
end
local post_note = data.post_note and "; " .. data.post_note or ""
return require("Module:headword").full_headword(data) .. post_note
end
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function glossary_link(entry, text)
text = text or entry
return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end
local function fetch_qualifiers(qual, existing)
if not qual then
return existing
end
if not existing then
return {qual}
end
local retval = {}
for _, e in ipairs(existing) do
table.insert(retval, e)
end
table.insert(retval, qual)
return retval
end
local function process_terms_with_qualifiers(terms, quals)
local infls = {}
for i, term in ipairs(terms) do
table.insert(infls, {term = term, q = fetch_qualifiers(quals[i])})
end
return infls
end
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture replace
-- expression.
lemma = lemma:gsub("%%", "%%%%")
-- Assign to a variable to discard second return value.
term = term:gsub("#", lemma)
return term
end
local function check_all_missing(data, forms, plpos)
for _, form in ipairs(forms) do
if type(form) == "table" then
form = form.term
end
end
end
local function insert_ancillary_inflection(data, forms, quals, label, plpos)
if forms and #forms > 0 then
local terms = process_terms_with_qualifiers(forms, quals)
check_all_missing(data, terms, plpos)
terms.label = label
table.insert(data.inflections, terms)
end
end
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
local function do_adjective(args, data, pos, is_suffix, is_superlative)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
if is_suffix then
pos = "suffix"
end
local plpos = require("Module:string utilities").pluralize(pos)
if not is_suffix then
data.pos_category = plpos
end
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
table.insert(indicators, "'" .. indic .. "'")
end
table.sort(indicators)
error("Special inflection indicator beginning can only be " ..
list_to_text(indicators) .. ": " .. args.sp)
end
end
local lemma = data.pagename
local function insert_inflection(forms, label, accel)
if #forms > 0 then
if forms[1].term == "-" then
table.insert(data.inflections, {label = "no " .. label})
else
forms.label = label
forms.accel = {form = accel}
table.insert(data.inflections, forms)
end
end
end
if args.f[1] == "ind" or args.f[1] == "inv" then
-- invariable adjective
table.insert(data.inflections, {label = glossary_link("invariable")})
table.insert(data.categories, langname .. " indeclinable " .. plpos)
if args.sp or #args.f > 1 or #args.pl > 0 or #args.mpl > 0 or #args.fpl > 0 then
error("Can't specify inflections with an invariable " .. pos)
end
elseif args.fonly then
-- feminine-only
if #args.f > 0 then
error("Can't specify explicit feminines with feminine-only " .. pos)
end
if #args.pl > 0 then
error("Can't specify explicit plurals with feminine-only " .. pos .. ", use fpl=")
end
if #args.mpl > 0 then
error("Can't specify explicit masculine plurals with feminine-only " .. pos)
end
local argsfpl = args.fpl
if #argsfpl == 0 then
argsfpl = {"+"}
end
for i, fpl in ipairs(argsfpl) do
local quals = fetch_qualifiers(args.fpl_qual[i])
if fpl == "+" then
-- Generate default feminine plural.
local defpls = com.make_plural(lemma, "f", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
for _, defpl in ipairs(defpls) do
table.insert(feminine_plurals, {term = defpl, q = quals})
end
else
table.insert(feminine_plural, {term = replace_hash_with_lemma(fpl, lemma), q = quals})
end
end
check_all_missing(data, feminine_plurals, plpos)
table.insert(data.inflections, {label = "feminine-only"})
insert_inflection(feminine_plurals, "feminine plural", "f|p")
else
-- Gather feminines.
local argsf = args.f
if #argsf == 0 then
argsf = {"+"}
end
for i, f in ipairs(argsf) do
if f == "mf" then
f = lemma
elseif f == "+" then
-- Generate default feminine.
f = com.make_feminine(lemma, args.sp)
else
f = replace_hash_with_lemma(f, lemma)
end
table.insert(feminines, {term = f, q = fetch_qualifiers(args.f_qual[i])})
end
local fem_like_lemma = #feminines == 1 and feminines[1].term == lemma and not feminines[1].q
if fem_like_lemma then
table.insert(data.categories, langname .. " epicene " .. plpos)
end
local argsmpl = args.mpl
local argsfpl = args.fpl
if #args.pl > 0 then
if #argsmpl > 0 or #argsfpl > 0 or args.mpl_qual.maxindex > 0 or args.fpl_qual.maxindex > 0 then
error("Can't specify both pl= and mpl=/fpl=")
end
argsmpl = args.pl
args.mpl_qual = args.pl_qual
argsfpl = args.pl
args.fpl_qual = args.pl_qual
end
if #argsmpl == 0 then
argsmpl = {"+"}
end
if #argsfpl == 0 then
argsfpl = {"+"}
end
for i, mpl in ipairs(argsmpl) do
local quals = fetch_qualifiers(args.mpl_qual[i])
if mpl == "+" then
-- Generate default masculine plural.
local defpls
-- First, some special hacks based on the feminine singular.
if not fem_like_lemma and not args.sp and not lemma:find(" ") then
for _, f in ipairs(feminines) do
if f.term:find("ssa$") then
-- If the feminine ends in -ssa, assume that the -ss- is also in the
-- masculine plural form
defpls = {rsub(f.term, "a$", "os")}
break
elseif f.term == lemma .. "na" then
defpls = {lemma .. "ns"}
break
elseif lemma:find("ig$") and f.term:find("ja$") then
-- Adjectives in -ig have two masculine plural forms, one derived from
-- the m.sg. and the other derived from the f.sg.
defpls = {lemma .. "s", rsub(f.term, "ja$", "jos")}
break
end
end
end
defpls = defpls or com.make_plural(lemma, "m", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
for _, defpl in ipairs(defpls) do
table.insert(masculine_plurals, {term = defpl, q = quals})
end
else
table.insert(masculine_plurals, {term = replace_hash_with_lemma(mpl, lemma), q = quals})
end
end
for i, fpl in ipairs(argsfpl) do
if fpl == "+" then
-- First, some special hacks based on the feminine singular.
if fem_like_lemma and not args.sp and not lemma:find(" ") and lemma:find("[çx]$") then
-- Adjectives ending in -ç or -x behave as mf-type in the singular, but
-- regular type in the plural.
local defpls = com.make_plural(lemma .. "a", "f")
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "a'")
end
local fquals = fetch_qualifiers(args.fpl_qual[i])
for _, defpl in ipairs(defpls) do
table.insert(feminine_plurals, {term = defpl, q = fquals})
end
else
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local defpls = com.make_plural(f.term, "f", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. f.term .. "'")
end
local fquals = fetch_qualifiers(args.fpl_qual[i], f.q)
for _, defpl in ipairs(defpls) do
table.insert(feminine_plurals, {term = defpl, q = fquals})
end
end
end
else
fpl = replace_hash_with_lemma(fpl, lemma)
table.insert(feminine_plurals, {term = fpl, q = fetch_qualifiers(args.fpl_qual[i])})
end
end
check_all_missing(data, feminines, plpos)
check_all_missing(data, masculine_plurals, plpos)
check_all_missing(data, feminine_plurals, plpos)
local fem_pl_like_masc_pl = #masculine_plurals > 0 and #feminine_plurals > 0 and
m_table.deepEquals(masculine_plurals, feminine_plurals)
local masc_pl_like_lemma = #masculine_plurals == 1 and masculine_plurals[1].term == lemma and
not masculine_plurals[1].q
if fem_like_lemma and fem_pl_like_masc_pl and masc_pl_like_lemma then
-- actually invariable
table.insert(data.inflections, {label = glossary_link("invariable")})
table.insert(data.categories, langname .. " indeclinable " .. plpos)
else
-- Make sure there are feminines given and not same as lemma.
if not fem_like_lemma then
insert_inflection(feminines, "feminine", "f|s")
else
data.genders = {"mf"}
end
if fem_pl_like_masc_pl then
insert_inflection(masculine_plurals, "masculine and feminine plural", "p")
else
insert_inflection(masculine_plurals, "masculine plural", "m|p")
insert_inflection(feminine_plurals, "feminine plural", "f|p")
end
end
end
if args.irreg and is_superlative then
table.insert(data.categories, langname .. " irregular superlative " .. plpos)
end
end
local function get_adjective_params(adjtype)
local params = {
["sp"] = {}, -- special indicator: "first", "first-last", etc.
["f"] = {list = true}, --feminine form(s)
[1] = {alias_of = "f"},
["f_qual"] = {list = "f\1_qual", allow_holes = true},
["pl"] = {list = true}, --plural override(s)
["pl_qual"] = {list = "pl\1_qual", allow_holes = true},
["mpl"] = {list = true}, --masculine plural override(s)
["mpl_qual"] = {list = "mpl\1_qual", allow_holes = true},
["fpl"] = {list = true}, --feminine plural override(s)
["fpl_qual"] = {list = "fpl\1_qual", allow_holes = true},
}
if adjtype == "base" then
params["comp"] = {list = true} --comparative(s)
params["comp_qual"] = {list = "comp\1_qual", allow_holes = true}
params["sup"] = {list = true} --superlative(s)
params["sup_qual"] = {list = "sup\1_qual", allow_holes = true}
params["dim"] = {list = true} --diminutive(s)
params["dim_qual"] = {list = "dim\1_qual", allow_holes = true}
params["aug"] = {list = true} --augmentative(s)
params["aug_qual"] = {list = "aug\1_qual", allow_holes = true}
params["fonly"] = {type = "boolean"} -- feminine only
params["hascomp"] = {} -- has comparative
end
if adjtype == "sup" then
params["irreg"] = {type = "boolean"}
end
return params
end
-- Display additional inflection information for an adjective
pos_functions["adjectives"] = {
params = get_adjective_params("base"),
func = function(args, data, is_suffix)
do_adjective(args, data, "adjective", is_suffix)
end,
}
pos_functions["past participles"] = {
params = get_adjective_params("part"),
func = function(args, data, is_suffix)
do_adjective(args, data, "participle", is_suffix)
data.pos_category = "past participles"
end,
}
pos_functions["determiners"] = {
params = get_adjective_params("det"),
func = function(args, data, is_suffix)
do_adjective(args, data, "determiner", is_suffix)
end,
}
pos_functions["pronouns"] = {
params = get_adjective_params("pron"),
func = function(args, data, is_suffix)
do_adjective(args, data, "pronoun", is_suffix)
end,
}
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet(
{"m", "f", "mf", "mfbysense", "n", "m-p", "f-p", "mf-p", "mfbysense-p", "n-p", "?", "?-p"}
)
local function process_genders(data, genders, g_qual)
for i, g in ipairs(genders) do
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
if g_qual[i] then
table.insert(data.genders, {spec = g, qualifiers = {g_qual[i]}})
else
table.insert(data.genders, g)
end
end
end
local function do_noun(args, data, pos, is_suffix, is_proper)
local is_plurale_tantum = false
local has_singular = false
if is_suffix then
pos = "suffix"
end
local plpos = require("Module:string utilities").pluralize(pos)
data.genders = {}
local saw_m = false
local saw_f = false
local gender_for_default_plural, gender_for_irreg_ending
process_genders(data, args[1], args.g_qual)
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
end
end
if saw_m and saw_f then
gender_for_default_plural = "m"
gender_for_irreg_ending = "mf"
elseif saw_f then
gender_for_default_plural = "f"
gender_for_irreg_ending = "f"
else
gender_for_default_plural = "m"
gender_for_irreg_ending = "m"
end
local lemma = data.pagename
local function insert_inflection(list, term, accel, qualifiers, no_inv)
local infl = {q = qualifiers, accel = accel}
--if term == lemma and not no_inv then
-- infl.label = glossary_link("invariable")
--else
infl.term = term
--end
infl.term_for_further_inflection = term
table.insert(list, infl)
end
-- Plural
local plurals = {}
local args_mpl = args.mpl
local args_fpl = args.fpl
local args_pl = args[2]
if is_plurale_tantum and not has_singular then
if #args_pl > 0 then
error("Can't specify plurals of plurale tantum " .. pos)
end
table.insert(data.inflections, {label = glossary_link("plural only")})
else
if is_plurale_tantum then
-- both singular and plural
table.insert(data.inflections, {label = "sometimes " .. glossary_link("plural only") .. ", in variation"})
end
-- If no plurals, use the default plural if not a proper noun.
if #args_pl == 0 and not is_proper then
args_pl = {"+"}
end
-- If only ~ given (countable and uncountable), add the default plural after it.
if #args_pl == 1 and args_pl[1] == "~" then
args_pl = {"~", "+"}
end
-- Gather plurals, handling requests for default plurals
for i, pl in ipairs(args_pl) do
local function insert_pl(term)
local quals = fetch_qualifiers(args.pl_qual[i])
if term == lemma and i == 1 and #args_pl == 1 then
table.insert(data.inflections, {label = glossary_link("invariable"), q = quals})
table.insert(data.categories, langname .. " indeclinable " .. plpos)
else
insert_inflection(plurals, term, nil, quals)
end
table.insert(data.categories, langname .. " countable " .. plpos)
end
local function make_plural_and_insert(form, special)
local pls = com.make_plural(lemma, gender_for_default_plural, special)
if pls then
for _, pl in ipairs(pls) do
insert_pl(pl)
end
end
end
if pl == "+" then
make_plural_and_insert(lemma)
elseif pl:find("^%+") then
pl = require(romut_module).get_special_indicator(pl)
make_plural_and_insert(lemma, pl)
elseif pl == "?" or pl == "!" then
if i > 1 or #args_pl > 1 then
error("Can't specify ? or ! with other plurals")
end
if pl == "?" then
-- Plural is unknown
-- Better not to display anything
-- table.insert(data.inflections, {label = "plural unknown or uncertain"})
table.insert(data.categories, langname .. " " .. plpos .. " with unknown or uncertain plurals")
else
-- Plural is not attested
table.insert(data.inflections, {label = "plural not attested"})
table.insert(data.categories, langname .. " " .. plpos .. " with unattested plurals")
end
elseif pl == "-" then
if i > 1 then
error("Plural specifier - must be first")
end
-- Uncountable noun; may occasionally have a plural
table.insert(data.categories, langname .. " uncountable " .. plpos)
-- If plural forms were given explicitly, then show "usually"
if #args_pl > 1 then
table.insert(data.inflections, {label = "usually " .. glossary_link("uncountable")})
table.insert(data.categories, langname .. " countable " .. plpos)
else
table.insert(data.inflections, {label = glossary_link("uncountable")})
end
elseif pl == "~" then
if i > 1 then
error("Plural specifier ~ must be first")
end
-- Countable and uncountable noun; will have a plural
table.insert(data.categories, langname .. " countable " .. plpos)
table.insert(data.categories, langname .. " uncountable " .. plpos)
table.insert(data.inflections, {label = glossary_link("countable") .. " and " .. glossary_link("uncountable")})
else
insert_pl(replace_hash_with_lemma(pl, lemma))
end
end
end
if #plurals > 1 then
table.insert(data.categories, langname .. " " .. plpos .. " with multiple plurals")
end
-- Gather masculines/feminines.
local function handle_mf(mfs, qualifiers, inflect)
local retval = {}
for i, mf in ipairs(mfs) do
local function insert_infl(list, term, accel, existing_qualifiers)
insert_inflection(list, term, accel, fetch_qualifiers(qualifiers[i], existing_qualifiers), "no inv")
end
local function call_inflect(special)
if inflect then
-- Generate default feminine.
return inflect(lemma, special)
else
-- FIXME
error("Can't generate default masculine currently")
end
end
if mf == "+" then
mf = call_inflect()
else
mf = replace_hash_with_lemma(mf, lemma)
end
local special = require(romut_module).get_special_indicator(mf)
if special then
mf = call_inflect(special)
end
insert_infl(retval, mf)
end
return retval
end
local feminines = handle_mf(args.f, args.f_qual, com.make_feminine)
-- FIXME, write make_masculine()
local masculines = handle_mf(args.m, args.m_qual)
check_all_missing(data, plurals, plpos)
check_all_missing(data, feminines, plpos)
if #plurals > 0 then
plurals.label = "plural"
plurals.accel = {form = "p"}
table.insert(data.inflections, plurals)
end
if #masculines > 0 then
masculines.label = "masculine"
table.insert(data.inflections, masculines)
end
if #feminines > 0 then
feminines.label = "feminine"
feminines.accel = {form = "f"}
table.insert(data.inflections, feminines)
end
-- Is this a noun with an unexpected ending (for its gender)?
-- Only check if the term is one word (there are no spaces in the term).
local irreg_gender_lemma = rsub(lemma, " .*", "") -- only look at first word
if (gender_for_irreg_ending == "m" or gender_for_irreg_ending == "mf") and irreg_gender_lemma:find("a$") then
table.insert(data.categories, langname .. " masculine " .. plpos .. " ending in -a")
elseif (gender_for_irreg_ending == "f" or gender_for_irreg_ending == "mf") and not (
irreg_gender_lemma:find("a$") or irreg_gender_lemma:find("ió$") or irreg_gender_lemma:find("tat$") or
irreg_gender_lemma:find("tud$") or irreg_gender_lemma:find("[dt]riu$")) then
table.insert(data.categories, langname .. " feminine " .. plpos .. " with no feminine ending")
end
end
local function get_noun_params()
return {
[1] = {list = "g", required = true, default = "?"},
[2] = {list = "pl"},
["g_qual"] = {list = "g\1_qual", allow_holes = true},
["pl_qual"] = {list = "pl\1_qual", allow_holes = true},
["m"] = {list = true},
["m_qual"] = {list = "m\1_qual", allow_holes = true},
["f"] = {list = true},
["f_qual"] = {list = "f\1_qual", allow_holes = true},
["mpl"] = {list = true},
["mpl_qual"] = {list = "mpl\1_qual", allow_holes = true},
["fpl"] = {list = true},
["fpl_qual"] = {list = "fpl\1_qual", allow_holes = true},
}
end
pos_functions["nouns"] = {
params = get_noun_params(),
func = function(args, data, is_suffix)
do_noun(args, data, "noun", is_suffix)
end,
}
pos_functions["proper nouns"] = {
params = get_noun_params(),
func = function(args, data, is_suffix)
do_noun(args, data, "noun", is_suffix, "is proper")
end,
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["verbs"] = {
params = {
[1] = {},
["pres"] = {list = true}, --present
["pres_qual"] = {list = "pres\1_qual", allow_holes = true},
["pres3s"] = {list = true}, --third-singular present
["pres3s_qual"] = {list = "pres3s\1_qual", allow_holes = true},
["pret"] = {list = true}, --preterite
["pret_qual"] = {list = "pret\1_qual", allow_holes = true},
["part"] = {list = true}, --participle
["part_qual"] = {list = "part\1_qual", allow_holes = true},
["short_part"] = {list = true}, --short participle
["short_part_qual"] = {list = "short_part\1_qual", allow_holes = true},
["noautolinktext"] = {type = "boolean"},
["noautolinkverb"] = {type = "boolean"},
["attn"] = {type = "boolean"},
["pres_1_sg"] = {}, -- accept any ignore old-style param
["past_part"] = {}, -- accept any ignore old-style param
["root"] = {}, -- FIXME: Implement root-stressed vowel quality
},
func = function(args, data, tracking_categories, frame)
local preses, preses_3s, prets, parts, short_parts
if args.attn then
table.insert(tracking_categories, "Requests for attention concerning " .. langname)
return
end
local ca_verb = require(ca_verb_module)
local alternant_multiword_spec = ca_verb.do_generate_forms(args, "ca-verb", data.heads[1])
local specforms = alternant_multiword_spec.forms
local function slot_exists(slot)
return specforms[slot] and #specforms[slot] > 0
end
local function do_finite(slot_tense, label_tense)
-- Use pres_3s if it exists and pres_1s doesn't exist (e.g. impersonal verbs); similarly for pres_3p (only3p verbs);
-- but fall back to pres_1s if neither pres_1s nor pres_3s nor pres_3p exist (e.g. [[empedernir]]).
local has_1s = slot_exists(slot_tense .. "_1s")
local has_3s = slot_exists(slot_tense .. "_3s")
local has_3p = slot_exists(slot_tense .. "_3p")
if has_1s or (not has_3s and not has_3p) then
return {
slot = slot_tense .. "_1s",
label = ("first-person singular %s"):format(label_tense),
}, true
elseif has_3s then
return {
slot = slot_tense .. "_3s",
label = ("third-person singular %s"):format(label_tense),
}, false
else
return {
slot = slot_tense .. "_3p",
label = ("third-person plural %s"):format(label_tense),
}, false
end
end
local did_pres_1s
preses, did_pres_1s = do_finite("pres", "present")
preses_3s = {
slot = "pres_3s",
label = "third-person singular present",
}
prets = do_finite("pret", "preterite")
parts = {
slot = "pp_ms",
label = "past participle",
}
short_parts = {
slot = "short_pp_ms",
label = "short past participle",
}
if #args.pres > 0 or #args.pres3s > 0 or #args.pret > 0 or #args.part > 0 or #args.short_part > 0 then
track("verb-old-multiarg")
end
local function strip_brackets(qualifiers)
if not qualifiers then
return nil
end
local stripped_qualifiers = {}
for _, qualifier in ipairs(qualifiers) do
local stripped_qualifier = qualifier:match("^%[(.*)%]$")
if not stripped_qualifier then
error("Internal error: Qualifier should be surrounded by brackets at this stage: " .. qualifier)
end
table.insert(stripped_qualifiers, stripped_qualifier)
end
return stripped_qualifiers
end
local function do_verb_form(args, qualifiers, slot_desc, skip_if_empty)
local forms
local to_insert
if #args == 0 then
forms = specforms[slot_desc.slot]
if not forms or #forms == 0 then
if skip_if_empty then
return
end
forms = {{form = "-"}}
end
elseif #args == 1 and args[1] == "-" then
forms = {{form = "-"}}
else
forms = {}
for i, arg in ipairs(args) do
local qual = qualifiers[i]
if qual then
-- FIXME: It's annoying we have to add brackets and strip them out later. The inflection
-- code adds all footnotes with brackets around them; we should change this.
qual = {"[" .. qual .. "]"}
end
local form = arg
if not args.noautolinkverb then
-- [[Module:inflection utilities]] already loaded by [[Module:ca-verb]]
form = require(inflection_utilities_module).add_links(form)
end
table.insert(forms, {form = form, footnotes = qual})
end
end
if forms[1].form == "-" then
to_insert = {label = "no " .. slot_desc.label}
else
local into_table = {label = slot_desc.label}
for _, form in ipairs(forms) do
local qualifiers = strip_brackets(form.footnotes)
-- Strip redundant brackets surrounding entire form. These may get generated e.g.
-- if we use the angle bracket notation with a single word.
local stripped_form = rmatch(form.form, "^%[%[([^%[%]]*)%]%]$") or form.form
-- Don't include accelerators if brackets remain in form, as the result will be wrong.
-- FIXME: For now, don't include accelerators. We should use the new {{ca-verb form of}}.
-- local this_accel = not stripped_form:find("%[%[") and accel or nil
local this_accel = nil
table.insert(into_table, {term = stripped_form, q = qualifiers, accel = this_accel})
end
to_insert = into_table
end
table.insert(data.inflections, to_insert)
end
local skip_pres_if_empty
if alternant_multiword_spec.no_pres1_and_sub then
table.insert(data.inflections, {label = "no first-person singular present"})
table.insert(data.inflections, {label = "no present subjunctive"})
end
if alternant_multiword_spec.no_pres_stressed then
table.insert(data.inflections, {label = "no stressed present indicative or subjunctive"})
skip_pres_if_empty = true
end
if alternant_multiword_spec.only3s then
table.insert(data.inflections, {label = glossary_link("impersonal")})
elseif alternant_multiword_spec.only3sp then
table.insert(data.inflections, {label = "third-person only"})
elseif alternant_multiword_spec.only3p then
table.insert(data.inflections, {label = "third-person plural only"})
end
local has_vowel_alt
if alternant_multiword_spec.vowel_alt then
for _, vowel_alt in ipairs(alternant_multiword_spec.vowel_alt) do
if vowel_alt ~= "+" and vowel_alt ~= "í" and vowel_alt ~= "ú" then
has_vowel_alt = true
break
end
end
end
do_verb_form(args.pres, args.pres_qual, preses, skip_pres_if_empty)
-- We want to include both the pres_1s and pres_3s if there is a vowel alternation in the present singular. But we
-- don't want to redundantly include the pres_3s if we already included it.
if did_pres_1s and has_vowel_alt then
do_verb_form(args.pres3s, args.pres3s_qual, preses_3s, skip_pres_if_empty)
end
do_verb_form(args.pret, args.pret_qual, prets)
do_verb_form(args.part, args.part_qual, parts)
do_verb_form(args.short_part, args.short_part_qual, short_parts, "skip if empty")
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
table.insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:ca-verb]] in place of the user-specified or auto-generated head. This was copied from
-- [[Module:it-headword]], where doing this gets accents marked on the verb(s). We don't have accents marked on
-- the verb but by doing this we do get any footnotes on the infinitive propagated here. Don't do this if the
-- user gave multiple heads or gave a head with a multiword-linked verbal expression such as Italian
-- '[[dare esca]] [[al]] [[fuoco]]' (FIXME: give Catalan equivalent).
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
table.insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
if args.root then
local m_ca_IPA = require(ca_IPA_module)
local parsed_respellings = {}
local function set_parsed_respelling(dialect, parsed)
-- Validate the individual root vowel specs.
for _, termobj in ipairs(parsed.terms) do
if not rfind(termobj.words[1].term, "^" .. m_ca_IPA.mid_vowel_hint_c .. "$") then
error(("Root vowel spec '%s' should be one of the vowels %s"):format(
termobj.words[1].term, m_ca_IPA.mid_vowel_hints))
end
end
if not dialect then
for _, dial in ipairs(m_ca_IPA.dialects) do
-- Need to clone as we destructively modify each one later with the pronun.
parsed_respellings[dial] = m_table.deepcopy(parsed)
end
elseif m_ca_IPA.dialect_groups[dialect] then
for _, dial in ipairs(m_ca_IPA.dialect_groups[dialect]) do
-- Need to clone as we destructively modify each one later with the pronun.
parsed_respellings[dial] = m_table.deepcopy(parsed)
end
else
parsed_respellings[dialect] = parsed
end
end
local function check_dialect_or_dialect_group(dialect)
if not m_table.contains(m_ca_IPA.dialects, dialect) and not
m_ca_IPA.dialect_groups[dialect] then
local dialect_list = {}
for _, dial in ipairs(m_ca_IPA.dialects) do
table.insert(dialect_list, "'" .. dial .. "'")
end
dialect_list = list_to_text(dialect_list, nil, " or ")
local dialect_group_list = {}
for dialect_group, _ in pairs(m_ca_IPA.dialect_groups) do
table.insert(dialect_group_list, "'" .. dialect_group .. "'")
end
dialect_group_list = list_to_text(dialect_group_list, nil, " or ")
error(("Unrecognized dialect '%s': Should be a dialect %s or a dialect group %s"):format(
dialect, dialect_list, dialect_group_list))
end
end
-- Parse the root vowel specs.
if args.root:find("[<%[]") then
local put = require(parse_utilities_module)
-- Parse balanced segment runs involving either [...] (substitution notation) or <...> (inline
-- modifiers). We do this because we don't want commas or semicolons inside of square or angle brackets
-- to count as respelling delimiters. However, we need to rejoin square-bracketed segments with nearby
-- ones after splitting alternating runs on comma and semicolon.
local segments = put.parse_multi_delimiter_balanced_segment_run(args.root, {{"<", ">"}, {"[", "]"}})
local semicolon_separated_groups = put.split_alternating_runs(segments, "%s*;%s*")
for _, group in ipairs(semicolon_separated_groups) do
local first_element = group[1]
local dialect
if first_element:find("^[a-z]+:") then
-- a dialect-specific spec
local rest
dialect, rest = first_element:match("^([a-z]+):(.*)$")
check_dialect_or_dialect_group(dialect)
group[1] = rest
end
local comma_separated_groups = put.split_alternating_runs_on_comma(group)
-- Process each value.
local outer_container = m_ca_IPA.parse_comma_separated_groups(comma_separated_groups, true, args.root,
"root")
set_parsed_respelling(dialect, outer_container)
end
else
local function parse_err(msg)
error(msg .. ": root=" .. args.root)
end
for _, dialect_spec in ipairs(rsplit(args.root, "%s*;%s*")) do
local dialect
if dialect_spec:find("^[a-z]+:") then
-- a dialect-specific spec
local rest
dialect, rest = dialect_spec:match("^([a-z]+):(.*)$")
check_dialect_or_dialect_group(dialect)
dialect_spec = rest
end
local termobjs = {}
for _, word in ipairs(rsplit(dialect_spec, ",")) do
table.insert(termobjs, {words = {{term = word}}})
end
set_parsed_respelling(dialect, {
terms = termobjs,
})
end
end
-- Convert each canonicalized respelling to phonemic/phonetic IPA.
m_ca_IPA.generate_phonemic_phonetic(parsed_respellings)
-- Group the results.
local grouped_pronuns = m_ca_IPA.group_pronuns_by_dialect(parsed_respellings)
-- Format for display.
for _, grouped_pronun_spec in pairs(grouped_pronuns) do
local pronunciations = {}
local function ins(text)
table.insert(pronunciations, text)
end
-- Loop through each pronunciation. For each one, format the phonetic version "raw".
for j, pronun in ipairs(grouped_pronun_spec.pronuns) do
-- Add dialect tags to left accent qualifiers if first one
local as = pronun.a
if j == 1 then
if as then
as = m_table.deepcopy(as)
else
as = {}
end
for _, dialect in ipairs(grouped_pronun_spec.dialects) do
table.insert(as, m_ca_IPA.dialects_to_names[dialect])
end
else
ins(", ")
end
local slash_pron = "/" .. pronun.phonetic:gsub("ˈ", "") .. "/"
if as or pronun.q or pronun.qq or pronun.aa then
ins(require("Module:pron qualifier").format_qualifiers {
lang = lang,
text = slash_pron,
q = pronun.q,
a = as,
qq = pronun.qq,
aa = pronun.aa
})
else
ins(slash_pron)
end
if pronun.refs then
-- FIXME: Copied from [[Module:IPA]]. Should be in a module.
local refs = {}
if #pronun.refs > 0 then
for _, refspec in ipairs(pronun.refs) do
if type(refspec) ~= "table" then
refspec = {text = refspec}
end
local refargs
if refspec.name or refspec.group then
refargs = {name = refspec.name, group = refspec.group}
end
table.insert(refs, mw.getCurrentFrame():extensionTag("ref", refspec.text, refargs))
end
ins(table.concat(refs))
end
end
end
grouped_pronun_spec.formatted = table.concat(pronunciations)
end
-- Concatenate formatted results.
local formatted = {}
for _, grouped_pronun_spec in ipairs(grouped_pronuns) do
table.insert(formatted, grouped_pronun_spec.formatted)
end
data.post_note = "''root stress'': " .. table.concat(formatted, "; ")
end
end
}
-----------------------------------------------------------------------------------------
-- Numerals --
-----------------------------------------------------------------------------------------
-- Display additional inflection information for a numeral
pos_functions["numerals"] = {
params = {
[1] = {},
[2] = {},
},
func = function(args, data, is_suffix)
local feminine = args[1]
local noun_form = args[2]
if feminine then
table.insert(data.genders, "m")
table.insert(data.inflections, {label = "feminine", feminine})
if noun_form then
table.insert(data.inflections, {label = "noun form", noun_form})
end
else
table.insert(data.genders, "m")
table.insert(data.genders, "f")
end
end
}
-----------------------------------------------------------------------------------------
-- Phrases --
-----------------------------------------------------------------------------------------
pos_functions["phrases"] = {
params = {
["g"] = {list = true},
["g_qual"] = {list = "g\1_qual", allow_holes = true},
["m"] = {list = true},
["m_qual"] = {list = "m\1_qual", allow_holes = true},
["f"] = {list = true},
["f_qual"] = {list = "f\1_qual", allow_holes = true},
},
func = function(args, data, is_suffix)
data.genders = {}
process_genders(data, args.g, args.g_qual)
insert_ancillary_inflection(data, args.m, args.m_qual, "masculine", "phrases")
insert_ancillary_inflection(data, args.f, args.f_qual, "feminine", "phrases")
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true},
["g"] = {list = true},
["g_qual"] = {list = "g\1_qual", allow_holes = true},
},
func = function(args, data, is_suffix)
data.genders = {}
process_genders(data, args.g, args.g_qual)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
table.insert(suffix_type, typ .. "-forming suffix")
end
table.insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export