Module:av-nominal
Appearance
- This module lacks a documentation subpage. Please create it.
- Useful links: subpage list • links • transclusions • testcases • sandbox
local export = {}
local lang = require("Module:languages").getByCode("av")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_table = require("Module:table")
local m_string_utilities = require("Module:string utilities")
local m_para = require("Module:parameters")
local current_title = mw.title.getCurrentTitle()
local NAMESPACE = current_title.nsText
local PAGENAME = current_title.text
local m_noun_decl = require("Module:av-noun/data")
local m_noun_table = require("Module:av-noun/table")
local m_la_utilities = require("Module:av-utilities")
local rsplit = mw.text.split
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rgmatch = mw.ustring.gmatch
local rsubn = mw.ustring.gsub
local ulen = mw.ustring.len
local uupper = mw.ustring.upper
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
local cases = {
"nom", "erg", "gen", "dat", "ins"
}
local nums = {
"sg", "pl"
}
local genders = {
"m", "f", "n"
}
local declension_to_english = {
["1"] = "first",
["2"] = "second",
["3"] = "third",
["4"] = "fourth",
}
local number_to_english = {
"one", "two", "three", "four"
}
local linked_prefixes = {
"", "linked_"
}
-- List of adjective slots for which we generate linked variants. Include
-- feminine and neuter variants because they will be needed if the adjective
-- is part of a multiword feminine or neuter noun.
local potential_adj_lemma_slots = {
"abs_sg_m",
"abs_pl_m",
"abs_sg_f",
"abs_pl_f",
"abs_sg_n",
"abs_pl_n"
}
local linked_to_non_linked_adj_slots = {}
for _, slot in ipairs(potential_adj_lemma_slots) do
linked_to_non_linked_adj_slots["linked_" .. slot] = slot
end
local potential_noun_lemma_slots = {
"abs_sg",
"abs_pl"
}
local linked_to_non_linked_noun_slots = {}
for _, slot in ipairs(potential_noun_lemma_slots) do
linked_to_non_linked_noun_slots["linked_" .. slot] = slot
end
local function iter_noun_slots(overridable_only)
local case = 1
local num = 1
local linked_variant = 0
local function iter()
linked_variant = linked_variant + 1
local max_linked_variant = overridable_only and 1 or cases[case] == "abs" and 2 or 1
if linked_variant > max_linked_variant then
linked_variant = 1
num = num + 1
if num > #nums then
num = 1
case = case + 1
if case > #cases then
return nil
end
end
end
return linked_prefixes[linked_variant] .. cases[case] .. "_" .. nums[num]
end
return iter
end
-- Iterate over all the "slots" associated with an adjective declension, where a slot
-- is a particular case/number/gender combination. If overridable_only, don't include the
-- "linked_" variants (linked_nom_sg_m, linked_nom_pl_m, etc.), which aren't overridable.
local function iter_adj_slots(overridable_only)
local case = 1
local num = 1
local gen = 1
local linked_variant = 0
local function iter()
linked_variant = linked_variant + 1
local max_linked_variant = overridable_only and 1 or cases[case] == "abs" and genders[gen] == "m" and 2 or 1
if linked_variant > max_linked_variant then
linked_variant = 1
gen = gen + 1
if gen > #genders then
gen = 1
num = num + 1
if num > #nums then
num = 1
case = case + 1
if case > #cases then
return nil
end
end
end
end
return linked_prefixes[linked_variant] .. cases[case] .. "_" .. nums[num] .. "_" .. genders[gen]
end
return iter
end
-- Iterate over all the "slots" associated with a noun or adjective declension (depending on
-- the value of IS_ADJ), where a slot is a particular case/number combination (in the case of
-- nouns) or case/number/gender combination (in the case of adjectives). If OVERRIDABLE_ONLY
-- is specified, only include overridable slots (not including linked_ variants).
local function iter_slots(is_adj, overridable_only)
if is_adj then
return iter_adj_slots(overridable_only)
else
return iter_noun_slots(overridable_only)
end
end
local function concat_forms_in_slot(forms)
if forms and forms ~= "" and forms ~= "—" and #forms > 0 then
local new_vals = {}
for _, v in ipairs(forms) do
table.insert(new_vals, rsub(v, "|", "<!>"))
end
return table.concat(new_vals, ",")
else
return nil
end
end
local function glossary_link(anchor, text)
text = text or anchor
return "[[Appendix:Glossary#" .. anchor .. "|" .. text .. "]]"
end
local function track(page)
require("Module:debug").track("av-nominal/" .. page)
return true
end
local function set_union(sets)
local union = {}
for _, set in ipairs(sets) do
for key, _ in pairs(set) do
union[key] = true
end
end
return union
end
local function set_difference(set1, set2)
local diff = {}
for key, _ in pairs(set1) do
if not set2[key] then
diff[key] = true
end
end
return diff
end
-- If a form is set as '*', that means its unattested
-- but should still be generated
local function unattested_forms(data, args, is_adj)
data.unattested = {}
for slot in iter_slots(is_adj) do
if args[slot] == '*' then
data.unattested[slot] = true
args[slot] = nil
end
end
end
-- Make a link only if the form is attested
local function link_if_attested(form, accel, is_unattested)
if is_unattested then
return m_links.full_link({ lang = lang, alt = '*' .. form })
else
return m_links.full_link({ lang = lang, term = form, accel = accel })
end
end
local function process_noun_forms_and_overrides(data, args)
local redlink = false
unattested_forms(data, args, is_adj);
-- Process overrides and canonicalize forms.
for slot in iter_noun_slots() do
local val = nil
if args[slot] then
val = args[slot]
data.user_specified[slot] = true
else
-- Overridding nom_sg etc. should override linked_nom_sg so that
-- the correct value gets displayed in the headword, which uses
-- linked_nom_sg.
local non_linked_equiv_slot = linked_to_non_linked_noun_slots[slot]
if non_linked_equiv_slot and args[non_linked_equiv_slot] then
val = args[non_linked_equiv_slot]
data.user_specified[slot] = true
else
val = data.forms[slot]
end
end
if val then
if type(val) == "string" then
val = mw.text.split(val, "/")
end
if (data.num == "pl" and slot:find("sg")) or (data.num == "sg" and slot:find("pl")) then
data.forms[slot] = ""
elseif val[1] == "" or val[1] == "-" or val[1] == "—" then
data.forms[slot] = "—"
else
data.forms[slot] = val
end
end
end
-- Compute the lemma for accelerators. Do this after processing
-- overrides in case we overrode the lemma form(s).
local accel_lemma
if data.num and data.num ~= "" then
accel_lemma = data.forms["nom_" .. data.num]
else
accel_lemma = data.forms["nom_sg"]
end
if type(accel_lemma) == "table" then
accel_lemma = accel_lemma[1]
end
-- Set the accelerators, and determine if there are red links.
for slot in iter_noun_slots() do
local val = data.forms[slot]
if val and val ~= "" and val ~= "—" and #val > 0 then
for i, form in ipairs(val) do
local accel_form = slot
accel_form = accel_form:gsub("_([sp])[gl]$", "|%1")
data.accel[slot] = {form = accel_form, lemma = accel_lemma}
if not redlink and NAMESPACE == '' then
local title = (lang:makeEntryName(form))
local t = mw.title.new(title)
if t and not t.exists then
table.insert(data.categories, "Avar" .. data.pos .. " with red links in their inflection tables")
redlink = true
end
end
end
end
end
end
local function process_adj_forms_and_overrides(data, args)
local redlink = false
unattested_forms(data, args, true)
-- Process overrides and canonicalize forms.
for slot in iter_adj_slots() do
-- If noneut=1 passed, clear out all neuter forms.
if data.noneut and slot:find("_n") then
data.forms[slot] = nil
end
-- If nomf=1 passed, clear out all masculine and feminine forms.
if data.nomf and (slot:find("_m") or slot:find("_f")) then
data.forms[slot] = nil
end
local val = nil
if args[slot] then
val = args[slot]
data.user_specified[slot] = true
else
-- Overridding nom_sg_m etc. should override linked_nom_sg_m so that
-- the correct value gets displayed in the headword, which uses
-- linked_nom_sg_m.
local non_linked_equiv_slot = linked_to_non_linked_adj_slots[slot]
if non_linked_equiv_slot and args[non_linked_equiv_slot] then
val = args[non_linked_equiv_slot]
data.user_specified[slot] = true
else
val = data.forms[slot]
end
end
if val then
if type(val) == "string" then
val = mw.text.split(val, "/")
end
if (data.num == "pl" and slot:find("sg")) or (data.num == "sg" and slot:find("pl")) then
data.forms[slot] = ""
elseif val[1] == "" or val[1] == "-" or val[1] == "—" then
data.forms[slot] = "—"
else
data.forms[slot] = val
end
end
end
-- Compute the lemma for accelerators. Do this after processing
-- overrides in case we overrode the lemma form(s).
local accel_lemma, accel_lemma_f
if data.num and data.num ~= "" then
accel_lemma = data.forms["abs_" .. data.num .. "_m"]
accel_lemma_f = data.forms["abs_" .. data.num .. "_f"]
else
accel_lemma = data.forms["abs_sg_m"]
accel_lemma_f = data.forms["abs_sg_f"]
end
if type(accel_lemma) == "table" then
accel_lemma = accel_lemma[1]
end
if type(accel_lemma_f) == "table" then
accel_lemma_f = accel_lemma_f[1]
end
-- Set the accelerators, and determine if there are red links.
for slot in iter_adj_slots() do
local val = data.forms[slot]
if val and val ~= "" and val ~= "—" and #val > 0 then
for i, form in ipairs(val) do
local accel_form = slot
accel_form = accel_form:gsub("_([sp])[gl]_", "|%1|")
if data.noneut then
-- If noneut=1, we're being asked to do a noun like
-- Aquītānus or Rōmānus that has masculine and feminine
-- variants, not an adjective. In that case, make the
-- accelerators correspond to nominal case/number forms
-- without the gender, and use the feminine as the
-- lemma for feminine forms.
if slot:find("_f") then
data.accel[slot] = {form = accel_form:gsub("|f$", ""), lemma = accel_lemma_f}
else
data.accel[slot] = {form = accel_form:gsub("|m$", ""), lemma = accel_lemma}
end
else
if not data.forms.nom_sg_n and not data.forms.nom_pl_n then
-- use multipart tags if called for
accel_form = accel_form:gsub("|m$", "|m//f//n")
elseif not data.forms.nom_sg_f and not data.forms.nom_pl_f then
accel_form = accel_form:gsub("|m$", "|m//f")
end
-- use the order nom|m|s, which is more standard than nom|s|m
accel_form = accel_form:gsub("|(.-)|(.-)$", "|%2|%1")
data.accel[slot] = {form = accel_form, lemma = accel_lemma}
end
if not redlink and NAMESPACE == '' then
local title = (lang:makeEntryName(form))
local t = mw.title.new(title)
if t and not t.exists then
table.insert(data.categories, "Avar" .. data.pos .. " with red links in their inflection tables")
redlink = true
end
end
end
end
end
-- See if the masculine and feminine/neuter are the same across all slots.
-- If so, blank out the feminine/neuter so we use a table that combines
-- masculine and feminine, or masculine/feminine/neuter.
for _, gender in ipairs({"f", "n"}) do
local other_is_masc = true
for _, case in ipairs(cases) do
for _, num in ipairs(nums) do
if not m_table.deepEquals(data.forms[case .. "_" .. num .. "_" .. gender],
data.forms[case .. "_" .. num .. "_m"]) then
other_is_masc = false
break
end
end
if not other_is_masc then
break
end
end
if other_is_masc then
for _, case in ipairs(cases) do
for _, num in ipairs(nums) do
data.forms[case .. "_" .. num .. "_" .. gender] = nil
end
end
end
end
end
-- Convert data.forms[slot] for all slots into displayable text. This is
-- an older function, still currently used for nouns but not for adjectives.
-- For adjectives, the adjective table module has special code to combine
-- adjacent slots, and needs the original forms plus other text that will
-- go into the displayable text for the slot; this is handled below by
-- partial_show_forms() and finish_show_form().
local function show_forms(data, is_adj)
local noteindex = 1
local notes = {}
local seen_notes = {}
for slot in iter_slots(is_adj) do
local val = data.forms[slot]
if val and val ~= "" and val ~= "—" then
for i, form in ipairs(val) do
local link = link_if_attested(form, data.accel[slot], data.unattested[slot])
local this_notes = data.notes[slot .. i]
if this_notes and not data.user_specified[slot] then
if type(this_notes) == "string" then
this_notes = {this_notes}
end
local link_indices = {}
for _, this_note in ipairs(this_notes) do
local this_noteindex = seen_notes[this_note]
if not this_noteindex then
-- Generate a footnote index.
this_noteindex = noteindex
noteindex = noteindex + 1
table.insert(notes, '<sup style="color: red">' .. this_noteindex .. '</sup>' .. this_note)
seen_notes[this_note] = this_noteindex
end
m_table.insertIfNot(link_indices, this_noteindex)
end
val[i] = link .. '<sup style="color: red">' .. table.concat(link_indices, ",") .. '</sup>'
else
val[i] = link
end
end
-- FIXME, do we want this difference?
data.forms[slot] = table.concat(val, is_adj and ", " or "<br />")
end
end
for _, footnote in ipairs(data.footnotes) do
table.insert(notes, footnote)
end
data.footnotes = table.concat(notes, "<br />")
end
-- Generate the display form for a set of slots with identical content. We
-- verify that the slots are actually identical, and throw an assertion error
-- if not. The display form is as in show_forms() but combines together all the
-- accelerator forms for all the slots.
local function finish_show_form(data, slots, is_adj)
assert(#slots > 0)
local slot1 = slots[1]
local forms = data.forms[slot1]
local notetext = data.notetext[slot1]
for _, slot in ipairs(slots) do
if not m_table.deepEquals(data.forms[slot], forms) then
error("data.forms[" .. slot1 .. "] = " .. (concat_forms_in_slot(forms) or "nil") ..
", but data.forms[" .. slot .. "] = " .. (concat_forms_in_slot(data.forms[slot]) or "nil"))
end
assert(m_table.deepEquals(data.notetext[slot], notetext))
end
if not forms then
return "—"
else
local accel_forms = {}
local accel_lemma = data.accel[slot1].lemma
for _, slot in ipairs(slots) do
assert(data.accel[slot].lemma == accel_lemma)
table.insert(accel_forms, data.accel[slot].form)
end
local combined_accel_form = table.concat(accel_forms, "|;|")
local accel = {form = combined_accel_form, lemma = accel_lemma}
local formtext = {}
for i, form in ipairs(forms) do
table.insert(formtext, link_if_attested(form, accel, data.unattested[slot1]) .. notetext[i])
end
-- FIXME, do we want this difference?
return table.concat(formtext, is_adj and ", " or "<br />")
end
end
-- Used by the adjective table module. This does some of the work of
-- show_forms(); in particular, it converts all empty forms of any format
-- (nil, "", "—") to nil and, if the forms aren't empty, generates the footnote
-- text associated with each form.
local function partial_show_forms(data, is_adj)
local noteindex = 1
local notes = {}
local seen_notes = {}
data.notetext = {}
-- Store this function in DATA so that it can be called from the adjective
-- table module without needing to require this module, which will (or
-- could) lead to recursive module requiring.
data.finish_show_form = finish_show_form
for slot in iter_slots(is_adj) do
local val = data.forms[slot]
if not val or val == "" or val == "—" then
data.forms[slot] = nil
else
local notetext = {}
for i, form in ipairs(val) do
local this_notes = data.notes[slot .. i]
if this_notes and not data.user_specified[slot] then
if type(this_notes) == "string" then
this_notes = {this_notes}
end
local link_indices = {}
for _, this_note in ipairs(this_notes) do
local this_noteindex = seen_notes[this_note]
if not this_noteindex then
-- Generate a footnote index.
this_noteindex = noteindex
noteindex = noteindex + 1
table.insert(notes, '<sup style="color: red">' .. this_noteindex .. '</sup>' .. this_note)
seen_notes[this_note] = this_noteindex
end
m_table.insertIfNot(link_indices, this_noteindex)
end
table.insert(notetext, '<sup style="color: red">' .. table.concat(link_indices, ",") .. '</sup>')
else
table.insert(notetext, "")
end
end
data.notetext[slot] = notetext
end
end
for _, footnote in ipairs(data.footnotes) do
table.insert(notes, footnote)
end
data.footnotes = table.concat(notes, "<br />")
end
local function make_noun_table(data)
if data.num == "sg" then
return m_noun_table.make_table_sg(data)
elseif data.num == "pl" then
return m_noun_table.make_table_pl(data)
else
return m_noun_table.make_table(data)
end
end
local function concat_forms(data, is_adj, include_props)
local ins_text = {}
for slot in iter_slots(is_adj) do
local formtext = concat_forms_in_slot(data.forms[slot])
if formtext then
table.insert(ins_text, slot .. "=" .. formtext)
end
end
if include_props then
if data.gender then
table.insert(ins_text, "g=" .. mw.ustring.lower(data.gender))
end
local num = data.num
if not num or num == "" then
num = "both"
end
table.insert(ins_text, "num=" .. num)
end
return table.concat(ins_text, "|")
end
-- Given an ending (or possibly a full regex matching the entire lemma, if
-- a regex group is present), return the base minus the ending, or nil if
-- the ending doesn't match.
local function extract_base(lemma, ending)
if ending:find("%(") then
return rmatch(lemma, ending)
else
return rmatch(lemma, "^(.*)" .. ending .. "$")
end
end
return export