This module generates the content of many Ancient Greek headword-line templates: {{grc-verb}}
, {{grc-verb form}}
, {{grc-noun}}
, {{grc-noun form}}
, {{grc-proper noun}}
, {{grc-proper noun form}}
, {{grc-adj-1&2}}
, {{grc-adj-2nd}}
, {{grc-adj-1&3}}
, {{grc-adj-3rd}}
, {{grc-adjective form}}
, {{grc-part-1&2}}
, {{grc-part-1&3}}
, {{grc-adverb}}
, {{grc-num}}
, {{grc-preposition}}
, {{grc-particle}}
, {{grc-pronoun form}}
.
This module tracks the monophthongs α, ι, υ (a, i, u) without macrons, breves, circumflexes, or iota subscripts (◌̄, ◌̆, ◌͂, ◌ͅ) with the tracking template grc-headword/ambig, so that length can be marked as policy requires, and it categorizes all Ancient Greek words into categories for accent type, such as Ancient Greek oxytone terms.
Experimentation on new features is done in Module:grc-headword/sandbox.
local export = {}
local m_params = require("Module:parameters")
local m_grc_utils = require("Module:grc-utilities")
local m_str_utils = require("Module:string utilities")
local tokenize = m_grc_utils.tokenize
local find_ambig = m_grc_utils.findAmbig
local diacritic = mw.loadData("Module:grc-utilities/data").diacritic
local full_headword = require("Module:headword").full_headword
local get_accent_term = require("Module:grc-accent").get_accent_term
local serial_comma_join = require("Module:table").serialCommaJoin
local lang = require("Module:languages").getByCode("grc")
local canonical_name = lang:getCanonicalName()
local NAMESPACE = mw.title.getCurrentTitle().nsText
local PAGENAME = mw.loadData("Module:headword/data").pagename
local MAINSPACE = NAMESPACE == ""
local reconstructed_prefix = NAMESPACE == "Reconstruction" and "reconstructed " or ""
local toNFD = mw.ustring.toNFD
local ufind = m_str_utils.find
local umatch = m_str_utils.match
local pos_functions = {}
local legal_declension = {
= true,
= true,
= true,
= true,
= true,
}
-- Also used to validate genders.
local gender_names = {
= "masculine",
= "masculine",
= "masculine",
= "masculine",
= "feminine",
= "feminine",
= "feminine",
= "feminine",
= "neuter",
= "neuter",
= "neuter",
= "neuter",
= "unknown gender",
= "unknown gender",
= "unknown gender",
= "unknown gender",
}
local function quote(text)
return "“" .. text .. "”"
end
local function format(array, concatenater)
if not array then
return ""
else
return "; ''" .. table.concat(array, concatenater) .. "''"
end
end
-- Process arg the way ] would.
local function process_arg(val)
if val == "" then
val = nil
end
if val then
val = mw.text.trim(val)
end
return val
end
-- Returns true if text contains one character from the Greek and Coptic or
-- Greek Extended blocks.
local function contains_Greek(text)
-- Matches any character in Greek and Coptic block except the first line:
-- ͰͱͲͳʹ͵Ͷͷͺͻͼͽ;Ϳ
local basic_Greek = ""
-- Exactly matches entire Greek Extended block.
local Greek_extended = "\225"
return (string.find(text, basic_Greek) or string.find(text, Greek_extended)) and true or false
end
-- A cheaper version of makeEntryName. Doesn't remove underties, which should
-- not appear in headwords, or convert curly apostrophes, spacing smooth
-- breathings, and spacing coronides to straight apostrophes.
local function remove_macron_breve(text)
return toNFD(text):gsub("\204", "")
end
local function remove_links(text)
if text:find("%[%[") then
return (text
:gsub("%+|(]+)%]%]", "%1")
:gsub("%]+)%]%]", "%1"))
else
return text
end
end
local U = m_str_utils.char
local macron = U(0x304)
local breve = U(0x306)
local rough = U(0x314)
local smooth = U(0x313)
local diaeresis = U(0x308)
local acute = U(0x301)
local grave = U(0x300)
local circumflex = U(0x342)
local subscript = U(0x345)
local diacritic_patt = table.concat{
"[",
macron, breve,
rough, smooth, diaeresis,
acute, grave, circumflex,
subscript,
"]"
}
local accent_patt = ""
-- Controls whether or not the headword can be provided in the first numbered parameter.
local function needs_headword(text)
local lengthDiacritic = ""
local aiu_diacritic = "^()(" .. diacritic_patt .. "*)$"
text = remove_links(text)
-- If page name has straight apostrophe, a headword with curly apostrophe should be provided.
if text:find("'") then
return true
end
-- breaks the word into units
for _, token in ipairs(tokenize(text)) do
local vowel, diacritics = umatch(token, aiu_diacritic)
if vowel and (diacritics == "" or
not ufind(diacritics, lengthDiacritic)) then
return true
end
end
return false
end
-- Process numbered parameters before using ], as
-- ] converts several named parameters into arrays, which
-- makes them more difficult to manipulate.
local function process_numbered_params(args, Greek_params, nonGreek_params)
if not nonGreek_params then
nonGreek_params = { false }
end
local max_Greek_param_index = #Greek_params
-- Clone args table so that its values can be modified.
args = require("Module:table").shallowcopy(args)
if args.head then
-- ]
require("Module:debug").track("grc-headword/head param")
end
local last_Greek_param_index = 0
for i, arg in ipairs(args) do
if arg == "-" or contains_Greek(arg) then
last_Greek_param_index = i
else
break
end
end
local head_in_arg1 = false
if last_Greek_param_index == max_Greek_param_index then
if not MAINSPACE or needs_headword(PAGENAME) then
head_in_arg1 = true
else
error(("The pagename does not have ambiguous vowels, so there cannot be "
.. max_Greek_param_index
.. " numbered parameter%s. See template documentation for more details.")
:format(max_Greek_param_index == 1 and "" or "s"))
end
elseif last_Greek_param_index > max_Greek_param_index then
error("Too many numbered parameters containing Greek text or hyphens. There can be at most "
.. max_Greek_param_index .. ".")
-- For indeclinable nouns: {{grc-noun|Ἰσρᾱήλ|m}}
-- First parameter is headword if equal to pagename when macrons and breves are removed.
elseif args and remove_macron_breve(args):gsub("’", "'") == toNFD(PAGENAME) then
if args.head then
error("Parameter 1 appears to be the headword, so the head parameter " .. quote(args.head) .. " is not needed.")
end
args.head, args = args, nil
else
table.remove(Greek_params, 1) -- Remove "head" parameter.
end
local function process_params(start_i, end_i, param_names)
local i = 1 -- Index in the table of parameter names.
for numbered = start_i, end_i do
local named = param_names
i = i + 1
if named then
-- Process parameters, as they have not been processed by ].
args, args =
process_arg(args), process_arg(args)
-- This should not happen, because the number of Greek parameters
-- has already been checked.
elseif args then
error("No purpose for parameter " .. numbered .. ".")
end
if args then
if named then
-- This fixes an error caused by the kludgy way in which the
-- numbered parameters of {{grc-preposition}} are handled.
if numbered ~= named then
if args then
error("Parameter " .. numbered .. " is not needed when parameter " .. named .. " is present.")
end
args, args = args, nil
end
else
error("Parameter " .. numbered .. ", " .. args .. ", has no purpose.")
end
end
end
end
process_params(1, last_Greek_param_index, Greek_params)
process_params(last_Greek_param_index + 1, #Greek_params + #nonGreek_params, nonGreek_params)
if args.head == "-" then
error("The headword cannot be absent.")
end
return args
end
local function process_heads(data, poscat)
data.no_redundant_head_cat = #data.heads == 0
if #data.heads == 0 then
table.insert(data.heads, PAGENAME)
end
local suffix = data.heads:find("^%*?%-") and true or false
for _, head in ipairs(data.heads) do
if suffix and head:sub(1, 1) ~= "-" then
error("The first headword has a hyphen, so headword #" .. i ..
", " .. quote(head) .. ", should as well.")
end
local accent = get_accent_term(head)
if accent then
table.insert(data.categories,
("%s %s terms"):format(canonical_name, accent))
elseif not ufind(toNFD(head), accent_patt) then
table.insert(data.categories,
("%s unaccented terms"):format(canonical_name))
else
table.insert(data.categories,
("%s terms with irregular accent"):format(canonical_name))
end
if MAINSPACE then
local _, vowel_set = find_ambig(head, false)
for vowel in pairs(vowel_set) do
require("Module:debug").track {
"grc-headword/ambig",
"grc-headword/ambig/" .. vowel
}
end
if not head:find(" ") and toNFD(head):find(grave) then
error("Head #" .. i .. ", " .. quote(head) ..
", contained a grave accent, but no space. Grave accent can only be used in multi-word terms.")
end
end
end
if suffix then
data.pos_category = "suffixes"
if not poscat:find "forms$" then
table.insert(data.categories, canonical_name .. " " .. poscat .. "-forming suffixes")
end
end
end
local function unlinked_form(label)
return { label = label, { nolink = true, term = "—" } }
end
local function add_gender_form(inflections, gender_arg, gender_name, allow_blank_forms)
if gender_arg then
if allow_blank_forms and not gender_arg and gender_arg == "-" then
table.insert(inflections, unlinked_form(gender_name))
else
gender_arg.label = gender_name
table.insert(inflections, gender_arg)
end
end
end
local function adj_and_part_forms(total_forms, args, inflections, allow_blank_forms)
if total_forms == 2 then
add_gender_form(inflections, args.f, "feminine", allow_blank_forms)
end
add_gender_form(inflections, args.n, "neuter", allow_blank_forms)
end
local function handle_degree_of_comparison(args, data, is_declined_form)
if args.deg ~= nil then
if args.deg == 'comp' then
data.pos_category = reconstructed_prefix .. "comparative adjectives"
elseif args.deg == 'super' then
data.pos_category = reconstructed_prefix .. "superlative adjectives"
else
error('Adjective degree ' .. quote(args.deg) .. ' not recognized.')
end
if is_declined_form then
data.pos_category = data.pos_category:gsub("adjectives", "adjective forms")
end
end
end
function export.show(frame)
local args = frame:getParent().args
local poscat = frame.args or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local subclass = frame.args
local data = {
lang = lang,
pos_category = reconstructed_prefix .. poscat,
categories = {}, heads = {}, genders = {}, inflections = {}
}
local appendix = {}
if pos_functions then
pos_functions(args, data, appendix, poscat, subclass)
end
return full_headword(data) .. format(appendix, ", ")
end
function export.test(frame_args, parent_args, pagename)
PAGENAME = pagename
local poscat = frame_args or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local subclass = frame_args
local data = {
pos_category = reconstructed_prefix .. poscat,
categories = {}, heads = {}, genders = {}, inflections = {}
}
local appendix = {}
if pos_functions then
pos_functions(parent_args, data, appendix, poscat, subclass)
end
return data
end
pos_functions = function(args, data, appendix, poscat)
args = process_numbered_params(args, { "head", "gen" }, { "g", "decl" })
local params = {
-- Numbered parameters 1, 2, 3, 4 handled above.
head = { list = true },
gen = { list = true },
g = { list = true, default = '?' },
dim = { list = true },
decl = { list = true },
sort = {}, -- for ]; please do not use otherwise
}
args = m_params.process(args, params, nil, "grc-headword", "nouns")
data.heads = args.head
process_heads(data, "noun")
for _, g in ipairs(args.g) do
local gender_name = gender_names
if gender_name then
table.insert(data.genders, g)
table.insert(data.categories,
("%s %s %s"):format(canonical_name, gender_name, poscat))
else
error("Gender " .. quote(g) .. " is not an valid " .. canonical_name .. " gender.")
end
end
if not args.gen then
table.insert(data.inflections, { label = "]" })
table.insert(data.categories,
("%s indeclinable %s")
:format(canonical_name, poscat))
for _, g in ipairs(args.g) do
table.insert(data.categories,
("%s %s indeclinable %s")
:format(canonical_name, gender_names, poscat))
end
if args.decl then
error("Declension class " .. quote(args.decl)
.. " has been given, but no genitive form has been given, so the word cannot belong to a declension class.")
end
else
if not args.gen and args.gen == "-" then
table.insert(data.inflections, unlinked_form("genitive"))
else
args.gen.label = "genitive"
table.insert(data.inflections, args.gen)
end
if args.decl then
table.insert(data.inflections, { label = 'variously declined' })
table.insert(data.categories,
("%s %s with multiple declensions")
:format(canonical_name, poscat))
elseif not args.decl then
table.insert(appendix, "? declension")
end
for _, decl_class in ipairs(args.decl) do
if legal_declension then
local not_irregular = decl_class ~= "irregular"
if not_irregular then
table.insert(appendix,
("]")
:format(canonical_name, decl_class, decl_class))
table.insert(data.categories,
("%s %s-declension %s")
:format(canonical_name, decl_class, poscat))
else
table.insert(appendix,
("%s declension"):format(decl_class))
table.insert(data.categories,
("%s irregular %s"):format(canonical_name, poscat))
end
if not_irregular then
for _, g in ipairs(args.g) do
table.insert(data.categories,
("%s %s %s in the %s declension")
:format(canonical_name, gender_names, poscat, decl_class))
end
end
else
error("Declension " .. quote(decl_class) .. " is not a legal " ..
canonical_name .. " declension. Choose “first”, “second”, “third”, or “irregular”.")
end
end
end
-- Check first-declension endings and gender.
if args.decl == "first" then
local alpha = "α??"
local eta = "η?"
local gender = args.g
local alpha_ending, eta_ending
if gender == "f" then
alpha_ending = alpha .. "$"
eta_ending = eta .. "$"
elseif gender == "m" then
alpha_ending = alpha .. "ς$"
eta_ending = eta .. "ς$"
else
gender = nil
require("Module:debug").track("grc-noun/1st/incorrect or no gender")
end
if gender then
for _, head in ipairs(data.heads) do
head = toNFD(remove_links(head))
if not (ufind(head, eta_ending) or ufind(head, alpha_ending)) then
require("Module:debug").track("grc-noun/1st/" .. gender .. " with incorrect ending")
end
end
end
end
if args.dim then
args.dim.label = "diminutive"
table.insert(data.inflections, args.dim)
end
end
pos_functions = pos_functions
pos_functions = function(args, data)
args = process_numbered_params(args, { "head" })
local params = {
head = { list = true }
}
local args = m_params.process(args, params, nil, "grc-headword", "verbs")
data.heads = args.head
process_heads(data, "verb")
end
pos_functions = function(args, data)
args = process_numbered_params(args, { "head", "comp", "super" }, { "type" })
local params = {
head = { list = true },
comp = { list = true },
super = { list = true },
type = { list = true },
}
local args = m_params.process(args, params, nil, "grc-headword", "adverbs")
data.heads = args.head
process_heads(data, "adverb")
-- Show comparative and superlative. If comparative or superlative is absent
-- while the other form is present, show "no comparative" or "no superlative".
if args.comp then
args.comp.label = 'comparative'
table.insert(data.inflections, args.comp)
elseif args.super then
table.insert(data.inflections, { label = 'no comparative' })
end
if args.super then
args.super.label = 'superlative'
table.insert(data.inflections, args.super)
elseif args.comp then
table.insert(data.inflections, { label = 'no superlative' })
end
if args.type then
local adverb_types = require "Module:table".listToSet {
"demonstrative", "indefinite", "interrogative", "relative",
}
for _, type in ipairs(args.type) do
if adverb_types then
table.insert(data.categories, canonical_name .. " " .. type .. " adverbs")
else
error(quote(type) .. " is not a valid subcategory of adverb.")
end
end
end
end
pos_functions = function(args, data)
args = process_numbered_params(args, { "head", "f", "n" })
local params = {
head = { list = true },
f = { list = true },
n = { list = true },
car = { list = true },
ord = { list = true },
adv = { list = true },
coll = { list = true },
}
local args = m_params.process(args, params, nil, "grc-headword", "numerals")
data.heads = args.head
process_heads(data, "numeral")
adj_and_part_forms(2, args, data.inflections, false)
local num_type_names = {
car = "cardinal", ord = "ordinal", adv = "adverbial", coll = "collective",
}
for _, num_type in ipairs { "car", "ord", "adv", "coll" } do
if args then
args.label = num_type_names
table.insert(data.inflections, args)
end
end
end
pos_functions = function(args, data, appendix, _, subclass)
if subclass == "1&2" or subclass == "1&3" then
pos_functions(args, data, appendix)
else
error('Participle subclass ' .. quote(subclass) .. ' not recognized.')
end
end
pos_functions = function(args, data, appendix)
args = process_numbered_params(args, { "head", "f", "n" })
local params = {
-- Parameters 1, 2, and 3 handled above.
head = { list = true },
f = { list = true, required = true },
n = { list = true, required = true },
}
local args = m_params.process(args, params, nil, "grc-headword", "part-1&2")
data.heads = args.head
process_heads(data, "participle")
table.insert(data.genders, "m")
table.insert(appendix, "[[Appendix:" .. canonical_name ..
" first declension|first]]/[[Appendix:" .. canonical_name ..
" second declension|second declension]]")
adj_and_part_forms(2, args, data.inflections, false)
end
pos_functions = function(args, data, appendix)
args = process_numbered_params(args, { "head", "f", "n" })
local params = {
-- Parameters 1, 2, and 3 handled above.
head = { list = true },
f = { list = true, required = true },
n = { list = true, required = true },
}
local args = m_params.process(args, params, nil, "grc-headword", "part-1&3")
data.heads = args.head
process_heads(data, "participle")
table.insert(data.genders, "m")
table.insert(appendix, "[[Appendix:" .. canonical_name ..
" first declension|first]]/[[Appendix:" .. canonical_name ..
" third declension|third declension]]")
adj_and_part_forms(2, args, data.inflections, false)
end
pos_functions = function(args, data, appendix, _, subclass)
local subclasses = {
= true, = true, = true, = true
}
if subclasses then
pos_functions(args, data, appendix)
else
error('Adjective subclass ' .. quote(subclass) .. ' not recognized.')
end
end
pos_functions = function(args, data, appendix)
args = process_numbered_params(args, { "head", "f", "n" })
local params = {
-- Parameters 1, 2, and 3 handled above.
head = { list = true },
f = { list = true, required = true },
n = { list = true, required = true },
deg = {},
}
local args = m_params.process(args, params, nil, "grc-headword", "adj-1&2")
data.heads = args.head
process_heads(data, "adjective")
table.insert(data.genders, "m")
table.insert(appendix, "[[Appendix:" .. canonical_name ..
" first declension|first]]/[[Appendix:" .. canonical_name ..
" second declension|second declension]]")
handle_degree_of_comparison(args, data, false)
adj_and_part_forms(2, args, data.inflections, true)
end
pos_functions = function(args, data, appendix)
args = process_numbered_params(args, { "head", "f", "n" })
local params = {
-- Parameters 1, 2, and 3 handled above.
head = { list = true },
f = { list = true, required = true },
n = { list = true, required = true },
}
local args = m_params.process(args, params, nil, "grc-headword", "adj-1&3")
data.heads = args.head
process_heads(data, "adjective")
table.insert(data.genders, "m")
table.insert(appendix, "[[Appendix:" .. canonical_name ..
" first declension|first]]/[[Appendix:" .. canonical_name ..
" third declension|third declension]]")
adj_and_part_forms(2, args, data.inflections, true)
end
pos_functions = function(args, data, appendix)
args = process_numbered_params(args, { "head", "n" })
local params = {
-- Parameters 1 and 2 handled above.
head = { list = true },
n = { list = true, required = true },
}
local args = m_params.process(args, params, nil, "grc-headword", "adj-2nd")
data.heads = args.head
process_heads(data, "adjective")
table.insert(data.genders, "m")
table.insert(data.genders, "f")
table.insert(appendix, "]")
adj_and_part_forms(1, args, data.inflections, true)
end
pos_functions = function(args, data, appendix)
args = process_numbered_params(args, { "head", "n" })
local params = {
-- Parameters 1 and 2 handled above.
head = { list = true },
n = { list = true, required = true },
deg = {},
}
local args = m_params.process(args, params, nil, "grc-headword", "adj-3rd")
data.heads = args.head
process_heads(data, "adjective")
table.insert(data.genders, "m")
table.insert(data.genders, "f")
table.insert(appendix, "]")
handle_degree_of_comparison(args, data, false)
adj_and_part_forms(1, args, data.inflections, true)
end
local case_abbreviations = {
nom = 'nominative',
gen = 'genitive',
dat = 'dative',
acc = 'accusative',
voc = 'vocative',
}
pos_functions = function(args, data, appendix)
-- This allows up to 4 numbered parameters, which is the number of cases
-- that can appear after prepositions.
args = process_numbered_params(args, { "head" }, { 1, 2, 3 })
local params = {
= { list = true },
head = { list = true },
}
local args = m_params.process(args, params, nil, "grc-headword", "prepositions")
data.heads = args.head
process_heads(data, "preposition")
if args then
local cases = {}
for _, case in ipairs(args) do
if case_abbreviations then
table.insert(data.categories, canonical_name .. " " .. case_abbreviations .. " prepositions")
table.insert(cases, " .. "|" .. case_abbreviations .. "]]")
else
error('Case abbreviation ' .. quote(case) ..
' not recognized. Please choose from ' ..
serial_comma_join(
require("Module:fun").map(
quote,
{ "gen", "dat", "acc" }),
{ dontTag = true })
.. '.')
end
end
table.insert(data.inflections, { label = 'governs the ' .. serial_comma_join(cases) })
end
end
pos_functions = function(args, data)
args = process_numbered_params(args, { "head" })
local params = {
head = { list = true },
disc = { type = 'boolean' },
mod = { type = 'boolean' },
inter = { type = 'boolean' },
neg = { type = 'boolean' },
}
local args = m_params.process(args, params, nil, "grc-headword", "particles")
data.heads = args.head
process_heads(data, "particles")
for _, item in ipairs{ { "disc", "discourse" }, { "mod", "modal" }, { "inter", "interrogative" }, { "neg", "negative" } } do
if args] then
local descriptor = item
table.insert(data.categories, canonical_name .. " " .. descriptor .. " particles")
table.insert(data.inflections, { label = descriptor .. ' particle' })
end
end
end
local valid_pos
setmetatable(pos_functions, {
__index = function (self, key)
if not key:find(" forms$") then
return nil
end
valid_pos = valid_pos or require "Module:table".listToSet{
"adjective", "determiner", "noun", "numeral", "participle",
"proper noun", "verb", "pronoun",
}
local pos = key:match("^(.+) forms$")
if not valid_pos then
error ("No function for the POS " .. quote(key) .. ".")
end
-- POS function for "noun forms", "verb forms", etc.
return function(args, data)
args = process_numbered_params(args, { "head" },
(pos == "noun" or pos == "proper noun") and { "g" })
local params = {
head = { list = true },
}
if pos == "noun" or pos == "proper noun" then
params.g = { list = true }
elseif pos == "adjective" then
params.deg = {}
end
local args = m_params.process(args, params, nil, "grc-headword", "forms")
data.heads = args.head
process_heads(data, key)
if args.g then
for _, g in ipairs(args.g) do
if gender_names then
table.insert(data.genders, g)
else
error("Gender " .. quote(g) .. " is not an valid " .. canonical_name .. " gender.")
end
end
end
handle_degree_of_comparison(args, data, true)
mw.logObject(data)
end
end
})
return export