မဝ်ဂျူ:grc-decl/decl

နူ ဝိက်ရှေန်နရဳ

Documentation for this module may be created at မဝ်ဂျူ:grc-decl/decl/doc

local module_path = 'Module:grc-decl/decl'

local m_classes = mw.loadData(module_path .. '/classes')
local m_paradigms = mw.loadData(module_path .. '/staticdata/paradigms')
local m_dialect_groups = mw.loadData(module_path .. '/staticdata/dialects')
local m_decl_data = require(module_path .. '/data')
local m_accent = require('Module:grc-accent')

local usub = mw.ustring.sub
local decompose = mw.ustring.toNFD
local compose = mw.ustring.toNFC

-- Equivalent to mw.ustring.len.
local function ulen(str)
	local _, length = string.gsub(str, '[\1-\127\194-\244][\128-\191]*', '')
	return length
end

-- Basic string functions can be used when there are no character sets or
-- quantifiers.
local ufind = mw.ustring.find
local ugsub = mw.ustring.gsub

local export = {
	inflections = m_decl_data.inflections,
	adjinflections = m_decl_data.adjinflections,
	adjinflections_con = m_decl_data.adjinflections_con,
}

local function quote(text)
	return "“" .. text .. "”"
end

local function get_accent_info(form)
	-- Get position of accent (nth vowel from beginning of word).
	local accent = {}
	local is_suffix = form:sub(1, 1) == '-'
	accent.position, accent.type = m_accent.detect_accent(form)
	-- Position: position of accent from beginning of word (number) or nil.
	-- Accent: accent name (string) or nil.
	
	-- Form must have an accent, unless it is a suffix.
	if not is_suffix and not next(accent) then
		error('No accent detected on ' .. quote(form) ..
				'. Please add an accent by copying this template and placing ' ..
				quote('/') .. ' for acute or ' .. quote('~') ..
				' for circumflex after the vowel that should be accented: {{subst:chars|grc|' .. form .. '}}.')
	end
	
	-- Accent term as proxy for distinguishing between oxytone and perispomenon.
	accent.term = m_accent.get_accent_term(form)
	
	return accent, is_suffix
end

local form_redirects = {
	AS = 'NS', VS = 'NS',
	DD = 'GD', AD = 'ND', VD = 'ND',
	AP = 'NP', VP = 'NP',
}

local form_metatable = {
	__index = function (self, form_code)
		if type(form_code) ~= 'string' then return nil end
		if form_redirects[form_code] then
			return self[form_redirects[form_code]]
		elseif form_redirects[form_code:sub(2)] then
			return self[form_code:sub(1, 1) .. form_redirects[form_code:sub(2)]]
		-- If this is a neuter form but not in the nominative case,
		-- use the corresponding masculine form.
		elseif form_code:match('N[^N].') then
			return self['M' .. form_code:sub(2)]
		end
	end,
}

local function add_redirects(form_table)
	return setmetatable(form_table, form_metatable)
end

local function add_forms(args)
	if not args.irregular then
		--add stem to forms
		local function add_stem(forms)
			return forms:gsub('%$', args.stem)
		end
		
		-- args.suffix indicates that this is a paradigm for an unaccented suffix,
		-- such as [[-εια]].
		if args.indeclinable then
			for k, v in pairs(args.ctable) do
				if k:find('[NGDAV][SDP]') then -- only format case-number forms
					args.ctable[k] = args[2]
				end
			end
		elseif args.suffix and not next(args.accent) then
			for k, v in pairs(args.ctable) do
				if k:find('[NGDAV][SDP]') then -- only format case-number forms
					args.ctable[k] = add_stem(v)
				end
			end
		else
			-- If the term is not a suffix and no accent was detected, then
			-- get_accent_info above must throw an error,
			-- or else there will be an uncaught error here.
			local add_circumflex = args.accent.type == 'circumflex'
			local recessive = -3
			
			-- Force recessive accent in the Lesbian dialect.
			local accent_position = args.dial == 'les' and recessive
					or args.accent.position
			
			-- Circumflex on monosyllabic DS and AS in consonant-stem third-
			-- declension nouns: for example, Τρῷ and Τρῶ, DS and AS of Τρώς.
			local DS_AS = args.accent_alternating == true
			
			-- Added by "kles" function: for example, Περίκλεις.
			local VS = args.recessive_VS and recessive
			local synaeresis = args.synaeresis
			
			local add_accent = m_accent.add_accent
			local function add_accent_to_forms(forms, code)
				return ugsub(forms,
					'[^/]+',
					function(form)
						return add_accent(form,
							code == 'VS' and VS or accent_position,
							{
								synaeresis = synaeresis,
								circumflex = (code == 'DS' or code == 'AS') and DS_AS or add_circumflex,
								short_diphthong = true,
								force_antepenult = args.force_antepenult,
							})
					end)
			end
			
			for k, v in pairs(args.ctable) do
				if k:find('[NGDAV][SDP]') then -- only format case-number forms
					args.ctable[k] = add_accent_to_forms(add_stem(v), k)
				end
			end
		end
	end
end

local gender_codes = { 'M', 'F', 'N' }
local case_codes = { 'N', 'G', 'D', 'A', 'V' }
local number_codes = { 'S', 'D', 'P' }
local function handle_noun_overrides(form_table, override_table)
	for _, case in ipairs(case_codes) do
		for _, number in ipairs(number_codes) do
			local key = case .. number
			if override_table[key] then 
				require('Module:debug').track('grc-decl/form-override')
			end
			form_table[key] = override_table[key] or form_table[key]
		end
	end
end

local function handle_adjective_overrides(form_table, override_table)
	for _, gender in ipairs(gender_codes) do
		for _, case in ipairs(case_codes) do
			for _, number in ipairs(number_codes) do
				local key = gender .. case .. number
				if override_table[key] then 
					require('Module:debug').track('grc-adecl/form-override')
				end
				form_table[key] = override_table[key] or form_table[key]
			end
		end
	end
end

--[=[
	Gets stem-ending combinations from [[Module:grc-decl/decl/data]]
	and [[Module:grc-decl/decl/staticdata]]. Called a single time to get forms
	of a noun, and two or three times by make_decl_adj for each of the genders
	of an adjective.
]=]
function export.make_decl(args, decl, root, is_adjective)
	if not export.inflections[decl] then
		error('Inflection type ' .. quote(decl) .. ' not found in [[' .. module_path .. "/data]].")
	end
	if args.adjective and args.irregular then
		error('Irregular adjectives are not handled by make_decl.')
	end
	
	if not root then
		error('No root for ' .. args[1] .. '.')
	end
	args.stem = root
	
	export.inflections[decl](args)
	args.gender[1] = args.gender[1] or args.ctable['g']
	args.declheader = args.declheader or args.ctable['decl']
	add_forms(args)
	if not is_adjective then
		handle_noun_overrides(args.ctable, args)
	end
	add_redirects(args.ctable)
end

-- String (comparative ending), function (input: root; output: comparative),
-- false (the declension has no comparative form), or nil (use the if-statements
-- to determine a comparative form).
local decl_to_comp = {
	['1&3-ᾰν'] = 'ᾰ́ντερος',
	['1&3-εν'] = 'έντερος',
	['1&3-εσσ'] = 'έστερος', -- hopefully this is general?
	['1&3-ups'] = 'ῠ́τερος',
	['3rd-cons'] = function(root)
		local last2 = usub(root, -2)
		if last2 == 'ον' then
			return root .. 'έστερος'
		elseif last2 == 'εσ' then
			return root:gsub('εσ$', 'έστερος')
		else
			return false
		end
	end,
	['1&3-οτ'] = false,
	['3rd-εσ'] = 'έστερος',
	['3rd-εσ-open'] = 'έστερος',
	['1&2-alp-con'] = 'εώτερος', -- I assume—though I can't find examples
	['1&2-eta-con'] = 'εώτερος',
}

local function retrieve_comp(root, decl_type)
	local data = decl_to_comp[decl_type]
	if not data then
		return data
	elseif type(data) == 'string' then
		return root .. data
	elseif type(data) == 'function' then
		return data(root)
	else
		error('Data for ' .. decl_type .. ' is invalid.')
	end
end

-- Constructs an adverb, comparative, and superlative.
function export.make_acs(args)
	-- input:
	-- strings
	local root, decl_type = args.root, args.decl_type
	-- tables
	local accent, atable = args.accent, args.atable
	-- output:
	local comp = retrieve_comp(root, decl_type)
	local super, adv
	
	if comp == nil then
		local alpha_nonultima = decl_type == '1&2-alp' and
				accent.term ~= 'oxytone' and
				accent.term ~= 'perispomenon'
		local last3 = usub(root, -3)
		if alpha_nonultima and last3 == 'τερ' then
			comp = '(' .. atable['MNS'] .. ')'
			adv = atable['NNS']
			-- ?
			-- comp = nil
		elseif alpha_nonultima and last3 == 'τᾰτ' then
			super = '(' .. atable['MNS'] .. ')'
			adv = atable['NNP']
			-- ?
			-- super = nil
		elseif decl_type:find('ντ') then
			comp = nil -- participles
			super = nil
		elseif (m_accent.get_weight(root, 1) == "light" or decl_type:find('att$')) then
			comp = root .. 'ώτερος'
		else
			comp = root .. 'ότερος'
		end
	end
	atable.adv = adv
		-- Actually neuter accusative singular. This is correct for -τερος and
		-- for μείζων; also for all comparatives in -ων?
		or args.comparative and atable.NNS
		-- actually neuter accusative plural
		or args.superlative and atable.NNP
		or atable.MGP and atable.MGP:gsub('ν$', 'ς'):gsub('ν<', 'ς<')
	atable.comp = comp
	atable.super = super or comp and comp:gsub('ερος$', 'ᾰτος')
	
	-- Remove comparative and superlative if adjective is a comparative or superlative.
	-- Parameters that trigger this condition are |deg=comp, |deg=super, and the
	-- deprecated |form=comp.
	if args.comparative or args.superlative then
		atable.comp, atable.super = nil, nil
	end
	
	for _, form in ipairs { 'adv', 'comp', 'super' } do
		if args[form] == "-" then
			atable[form] = nil
		elseif args[form] then
			atable[form] = args[form]
		end
	end
end

--[[
	noun_table contains case-number forms.
	adjective_table will contain gender-case-number forms.
	override_table contains gender-case-number forms that will override the
	forms in noun_table.
]]
local function transfer_forms_to_adjective_table(adjective_table, noun_table, gender_code)
	for case_and_number_code, form in pairs(noun_table) do
		adjective_table[gender_code .. case_and_number_code] = form
	end
end

--[=[
	Interprets the table for the adjective's inflection type
	in [[Module:grc-decl/decl/staticdata]].
]=]
function export.make_decl_adj(args, ct)
	if args.irregular then
		return export.inflections['irreg-adj'](args)
	end
	
	--[[
		Two possibilities, with the indices of the table of endings
		and the stem augmentation that they use:
			- masculine–feminine (1, a1), neuter (2, a2)
			- masculine (1, a1), feminine (2, a2), neuter (3, a1)
	]]
	-- Masculine or masculine and feminine forms.
	export.make_decl(args, ct[1], args.root .. (ct.a1 or ''), true)
	transfer_forms_to_adjective_table(args.atable, args.ctable, 'M')
	
	-- Feminine or neuter forms.
	if ct[2] then
		export.make_decl(args, ct[2], args.fstem or (args.root .. (ct.a2 or '')), true)
		transfer_forms_to_adjective_table(args.atable, args.ctable, 'F')
	end
	
	export.make_decl(args, ct[3], args.root .. (ct.a1 or ''), true)
	transfer_forms_to_adjective_table(args.atable, args.ctable, 'N')
	
	add_redirects(args.atable)
	args.ctable = nil
	export.make_acs(args)
	handle_adjective_overrides(args.atable, args)
	args.adeclheader = ct.adeclheader or 'Declension'
end

-- This function requires NFC forms for [[Module:grc-decl/decl/classes]],
-- but NFD forms for [[Module:grc-decl/decl/data]].
function export.get_decl(args)
	if args.indeclinable then
		if not args[2] then error("Specify the indeclinable form in the 2nd parameter.") end
		args.decl_type, args.root = 'indecl', ''
		return
	elseif args.irregular then
		if args.gender[1] == "N" then
			args.decl_type, args.root = 'irregN', ''
		else
			args.decl_type, args.root = 'irreg', ''
		end
		return
	elseif not (args[1] and args[2]) then
		error("Use the 1st and 2nd parameters for the nominative and genitive singular.")
	end
	
	local infl_info = m_classes.infl_info.noun
	
	args[1] = compose(args[1])
	args[2] = compose(args[2])
	
	local arg1, arg2 = args[1], args[2]
	
	local nom_without_accent = compose(m_accent.strip_tone(arg1))
	local gen_without_accent = compose(m_accent.strip_tone(arg2))
	
	local decl_types_by_genitive_ending, decl_type, root
	local nominative_matches = {}
	for i = -infl_info.longest_nominative_ending, -1 do
		local nominative_ending = usub(nom_without_accent, i)
		local decl_types_by_genitive_ending = infl_info[nominative_ending]
		-- If decl_types_by_genitive_ending is a string, then it is the key of
		-- another table in infl_info, a nominative ending with a macron or
		-- breve (ι → ῐ).
		if type(decl_types_by_genitive_ending) == "string" then
			decl_types_by_genitive_ending = infl_info[decl_types_by_genitive_ending]
		end
		
		if decl_types_by_genitive_ending then
			table.insert(nominative_matches, decl_types_by_genitive_ending)
			root = usub(nom_without_accent, 1, -1 - ulen(nominative_ending))
			
			for i = -6, -1 do
				local genitive_ending = usub(gen_without_accent, i)
				local name = decl_types_by_genitive_ending[genitive_ending]
				if name then
					decl_type = name
					break
				end
			end
			
			if decl_type then
				break
			end
		end
	end
	
	args.accent, args.suffix = get_accent_info(arg1)
	
	if decl_type and root then
		if args.contracted == 'false' and not decl_type:find('open') then
			decl_type = decl_type .. '-open'
		end
		args.decl_type, args.root = decl_type, decompose(root)
		
		return
	elseif gen_without_accent:find('ος$') then
		local root = decompose(usub(gen_without_accent, 1, -3))
		if args.gender[1] == "N" or ufind(root, 'α[̆̄]τ$') and not (args.gender[1] == "M" and args.gender[2] == "F") then
			args.decl_type, args.root = '3rd-N-cons', root
		else
			args.decl_type, args.root = '3rd-cons', root
		end
		
		return
	end
	
	if nominative_matches[1] then
		local m_table = require 'Module:table'
		local fun, grc =
			require 'Module:fun', require 'Module:languages'.getByCode 'grc'
		local make_sort_key = fun.memoize(
			function (term)
				return grc:makeSortKey(term)
			end)
		
		if nominative_matches[2] then
			local new_nominative_matches = {}
			for _, matches in ipairs(nominative_matches) do
				for k, v in pairs(matches) do
					new_nominative_matches[k] = v
				end
			end
			nominative_matches = new_nominative_matches
		else
			nominative_matches = nominative_matches[1]
		end
		
		local gens = require 'Module:fun'.map(
			function (gen)
				return quote("-" .. gen)
			end,
			m_table.keysToList(
				nominative_matches,
				function (gen1, gen2)
					local sort_key1, sort_key2 =
						make_sort_key(gen1), make_sort_key(gen2)
					if sort_key1 == sort_key2 then
						return gen1 < gen2
					else
						return sort_key1 < sort_key2
					end
				end))
		
		local agreement
		if #gens > 1 then
			agreement = { "Declensions were", "s ", " do" }
		else
			agreement = { "A declension was", " ", " does" }
		end
		gens = table.concat(gens, ", ")
		
		error(agreement[1] .. " found that matched the ending of the nominative form " .. quote(arg1) ..
				", but the genitive ending" .. agreement[2] .. gens ..
				agreement[3] .. " not match the genitive form " .. quote(arg2) .. ".")
	else
		for nom, gens in pairs(m_classes.ambig_forms) do
			if arg1:find(nom .. "$") then
				for gen, _ in pairs(gens) do
					if arg2:find(gen .. "$") then
						error("No declension found for nominative " .. quote(arg1) .. " and genitive " .. quote(arg2) ..
								". There are two declensions with nominative " .. quote("-" .. nom) ..
								" and genitive " .. quote("-" .. gen) ..
								". To indicate which one you mean, mark the vowel length of the endings with a macron or breve.")
					end
				end
			end
		end
		
		error("Can’t find a declension type for nominative " .. quote(arg1) .. " and genitive " .. quote(arg2) .. ".")
	end
end

-- This function requires NFC forms for [[Module:grc-decl/decl/classes]],
-- but NFD forms for [[Module:grc-decl/decl/data]].
function export.get_decl_adj(args)
	if args.irregular then
		args.decl_type, args.root = 'irreg', ''
		return
	elseif not args[1] then
		error('Use the 1st and 2nd parameters for the masculine and the ' ..
			'feminine or neuter nominative singular, or the first parameter ' ..
			' alone for the 3rd declension stem.')
	end
	
	args[1] = compose(args[1])
	if args[2] then
		args[2] = compose(args[2])
	end
	
	local arg1, arg2 = args[1], args[2]
	
	local mstrip = compose(m_accent.strip_tone(arg1))
	local fstrip
	if arg2 then
		fstrip = compose(m_accent.strip_tone(arg2))
	else
		args.accent, args.suffix = get_accent_info(arg1)
		
		args.decl_type, args.root = '3rd-cons', decompose(mstrip)
		
		return
	end
	
	local infl_info = m_classes.infl_info.adj
	
	-- See if last three or two characters of masc have an entry.
	local masc, decl
	for i = -infl_info.longest_masculine_ending, -2 do
		local ending = usub(mstrip, i)
		local data = infl_info[ending]
		if data then
			masc = ending
			decl = data
			break
		end
	end
	
	-- Allows redirecting, so that macrons or breves can be omitted for instance.
	if type(decl) == "string" then
		decl = infl_info[decl]
	end
	
	if decl then
		-- Look for a feminine ending that matches the end of the feminine form.
		local fem, name
		for feminine, decl_name in pairs(decl) do
			if fstrip:find(feminine .. '$') then
				fem = feminine
				name = decl_name:gsub("%d$", "")
				break
			end
		end
		
		if fem then
			args.accent, args.suffix = get_accent_info(arg1)
			
			-- The only indication that λέγων, λέγουσᾰ (stem λεγοντ-) and
			-- ποιῶν, ποιοῦσᾰ (stem ποιουντ-) have different stems is the
			-- accentuation of the masculine form.
			if name == '1&3-οντ' and args.accent.term == 'perispomenon' then
				name = '1&3-οντ-con'
			end
			
			if not export.adjinflections[name] then
				error('Inflection recognition failed. Function for generated inflection code ' ..
						quote(name) .. ' not found in [[' .. module_path .. "/data]].")
			end
			
			args.decl_type, args.root = name, decompose(mstrip:gsub(masc .. "$", ""))
			
			return
		else
			-- No declension type found.
			local fems = {}
			local is_neuter = false
			for fem in pairs(decl) do
				if fem == "ον" then
					is_neuter = true
				end
				table.insert(fems, quote("-" .. fem))
			end
			fems = table.concat(fems, ", ")
			local agreement = { "A declension was", " ", " does" }
			if #fems > 1 then
				agreement = { "Declensions were", "s ", " do" }
			end
			error(agreement[1] .. " found that matched the ending of the masculine " .. quote(arg1) ..
					", but the corresponding feminine" .. (is_neuter and " and neuter" or "") .. " ending" .. agreement[2] .. fems ..
					agreement[3] .. " not match the feminine " .. quote(arg2) .. ".")
		end
	end
	error("Can’t find a declension type for masculine " .. quote(arg1) .. " and feminine or neuter " .. quote(arg2) .. ".")
end

--[[
	Returns a table containing the inflected forms of the article,
	to be placed before each inflected noun form.
]]
function export.infl_art(args)
	if args.dial == 'epi' or args.adjective or args.no_article then
		return {}
	end
	
	local art = {}
	local arttable
	
	if args.gender[1] then
		arttable = m_paradigms.art_att[args.gender[1]]
	else
		error('Gender not specified.')
	end
	for code, suffix in pairs(arttable) do
		if (args.gender[1] == "M" and args.gender[2] == "F") and
				m_paradigms.art_att.M[code] ~= m_paradigms.art_att.F[code] then
			art[code] = m_paradigms.art_att.M[code] .. ', ' .. m_paradigms.art_att.F[code]
		else
			art[code] = suffix
		end
	end
	
	if args.gender[1] == 'F' then
		if m_dialect_groups['nonIA'][args.dial] then
			art['NS'] = 'ᾱ̔' -- 104.1-4
			art['GS'] = 'τᾶς'
			art['DS'] = 'τᾷ'
			art['AS'] = 'τᾱ̀ν'
		end
		
		if args.dial == 'the' or args.dial == 'les' then
			art['DS'] = 'τᾶ' -- 39
		elseif args.dial == 'boi' or args.dial == 'ara' or args.dial == 'ele' then
			art['DS'] = 'ται' -- 104.3
		end
		
		if m_dialect_groups['nonIA'][args.dial] then
			art['GP'] = 'τᾶν' -- 104.6
		end
		
		if args.dial == 'ato' then
			art['DP'] = 'τῆσῐ(ν)' -- 104.7
		elseif args.dial == 'ion' then
			art['DP'] = 'τῇσῐ(ν)' -- 104.7
		end
		
		if m_dialect_groups['buck78'][args.dial] then
			art['AP'] = 'τᾰ̀ς' -- 104.8
		elseif args.dial == 'kre' or args.dial == 'arg' then
			art['AP'] = 'τὰνς'
		elseif args.dial == 'les' then
			art['AP'] = 'ταῖς'
		elseif args.dial == 'ele' then
			art['AP'] = 'ταὶρ'
		end
		
		if args.dial == 'kre' or args.dial == 'les' or args.dial == 'kyp' then
			art['NS'] = 'ᾱ̓' -- 57
			art['NP'] = 'αἰ'
		elseif args.dial == 'ele' then
			art['NS'] = 'ᾱ̓'
			art['NP'] = 'ταὶ'
		elseif args.dial == 'boi' then
			art['NP'] = 'τὴ' -- 104.5
		elseif m_dialect_groups['west'][args.dial] then --boeotian is covered above
			art['NP'] = 'ταὶ'
		end
	elseif args.gender[1] == 'M' or args.gender[1] == 'N' then
		if args.dial == 'the' then
			art['GS'] = 'τοῖ' -- 106.1
			art['DS'] = 'τοῦ' -- 23
			art['ND'] = 'τοὺ'
			art['GP'] = 'τοῦν'
		end
		
		if args.dial == 'les' then
			art['DS'] = 'τῶ' -- 106.2
		elseif args.dial == 'boi' or args.dial == 'ara' or args.dial == 'ele' or args.dial == 'eub' then
			art['DS'] = 'τοι' -- 106.2
		end
		
		if args.dial == 'ato' or args.dial == 'ion' then
			art['DP'] = 'τοῖσῐ(ν)' -- 106.4
		end
		
		if args.gender[1] == 'M' then
			if m_dialect_groups['buck78'][args.dial] then
				art['AP'] = 'τὸς' -- 106.5
			elseif args.dial == 'kre' or args.dial == 'arg' then
				art['AP'] = 'τὸνς'
			elseif args.dial == 'les' then
				art['AP'] = 'τοῖς'
			elseif args.dial == 'ele' then
				art['AP'] = 'τοὶρ'
			elseif m_dialect_groups['severe'][args.dial] or args.dial == 'boi' then
				art['AP'] = 'τὼς'
			end
			
			if args.dial == 'kre' or args.dial == 'les' or args.dial == 'kyp' then
				art['NS'] = 'ὀ' -- 57
				art['NP'] = 'οἰ'
			elseif args.dial == 'ele' then
				art['NS'] = 'ὀ'
				art['NP'] = 'τοὶ'
			elseif m_dialect_groups['west'][args.dial] or args.dial == 'boi' then
				art['NP'] = 'τοὶ'
			end
		end
		
		if args.dial == 'ele' then
			art['GD'] = 'τοίοις'
			--		elseif args.dial == 'ara' then
			--			art['GD'] = 'τοιυν'
		end
	end
	
	return art
end
	
local lang = require("Module:languages").getByCode("grc")
local function tag(text)
	return require("Module:script utilities").tag_text("-" .. text, lang)
end

local function print_detection_table(detection_table, labels, noun)
	local out = require('Module:array')()
	
	local function sort(item1, item2)
		-- Put 'longest_nominative_ending' and 'longest_masculine_ending' first.
		if item1:find '^longest' or item2:find '^longest' then
			return item1:find '^longest' ~= nil
		end
		
		local sort1, sort2 = lang:makeSortKey(item1), lang:makeSortKey(item2)
		local decomp_length1, decomp_length2 = ulen(decompose(item1)), ulen(decompose(item2))
		
		if sort1 == sort2 then
			-- Sort ᾱ or ᾰ before α.
			if decomp_length1 > decomp_length2 then
				return true
			else
				return false
			end
		else
			return sort1 < sort2
		end
	end
	
	for key1, value1 in require("Module:table").sortedPairs(detection_table, sort) do
		if key1:find '^longest' then
			out:insert('* ' .. key1:gsub('_', ' ') .. ': ' .. value1 .. ' characters')
		else
			table.insert(out, "\n* " .. labels[1] .. " " .. tag(key1))
			if type(value1) == "string" then
				out:insert(" &rarr; " .. tag(value1))
			elseif type(value1) == "table" then
				for key2, value2 in require("Module:table").sortedPairs(value1, sort) do
					-- mw.log(len(key1), len(key2))
					out:insert("\n** " ..
							(noun and labels[2] or key2 == "ον" and "neuter" or "feminine") ..
							" " .. tag(key2) .. ": <code>" .. value2 .. "</code>")
					if noun then
						out:insert(" (<code>" .. (m_classes.conversion[value2] or "?") .. "</code>)")
					end
				end
			end
		end
	end
	
	return out:concat()
end
	

function export.show_noun_categories(frame)
	return print_detection_table(m_classes.infl_info.noun, { "nominative", "genitive" }, true)
end


function export.show_adj_categories(frame)
	return print_detection_table(m_classes.infl_info.adj, { "masculine", "feminine or neuter" })
end

return export