Module:av-nominal

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 "" .. 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, '' .. this_noteindex .. ' ' .. this_note) seen_notes[this_note] = this_noteindex end m_table.insertIfNot(link_indices, this_noteindex) end val[i] = link .. '' .. table.concat(link_indices, ",") .. ' '				else val[i] = link end end -- FIXME, do we want this difference? data.forms[slot] = table.concat(val, is_adj and ", " or " ") end end for _, footnote in ipairs(data.footnotes) do		table.insert(notes, footnote) end data.footnotes = table.concat(notes, " ") 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 " ") 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, '' .. this_noteindex .. ' ' .. this_note) seen_notes[this_note] = this_noteindex end m_table.insertIfNot(link_indices, this_noteindex) end table.insert(notetext, '' .. table.concat(link_indices, ",") .. ' ') 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, " ") 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