Module:User:Erutuon/grc-decl

From Wiktionary, the free dictionary
Jump to navigation Jump to search

This module needs documentation.
Please document this module by describing its purpose and usage on the documentation page.


Testcases

[edit]

Module error: No such module "grc-decl/sandbox".


local export = {}

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

local m_grc_decl_table = require(module_path .. '/table')
local m_grc_decl_decl = require(module_path .. '/decl')
local m_params = mw.loadData(module_path .. "/params")

local m_table = require('Module:table')

local usub = mw.ustring.sub
local ufind = mw.ustring.find
local decompose = mw.ustring.toNFD

local function check_track_arg(argI, arg)
	require('libraryUtil').checkType("track", argI, arg, "string")
end

local function track(template, code)
	check_track_arg(1, template)
	if code then
		check_track_arg(2, code)
		require("Module:debug").track(template .. "/" .. code)
	else
		return function(code)
			check_track_arg(1, code)
			require("Module:debug").track(template .. "/" .. code)
		end
	end
end

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

local function handle_unrecognized_args(unrecognized_args, adjective)
	local track = track(adjective and 'grc-adecl' or 'grc-decl')
	
	-- Next returns nil if table is empty.
	if next(unrecognized_args) then
		track("unrecognized args")
		
		local unrecognized_list = m_table.keysToList(unrecognized_args)
		
		local agreement = ""
		if #unrecognized_list > 1 then
			agreement = "s"
		end
		
		mw.log("unrecognized arg" .. agreement .. ": " .. table.concat(unrecognized_list, ", ") ..
				"; see Module:grc-decl/params for a full list of recognized args")
	end
end

local function swap_args(args, suffix)
	--This function has undefined behavior if both <arg> and <arg><suffix> are
	--specified; use e.g. <arg>1 and <arg>2 instead.
	local args_ = args
	args = {}
	for code, value in pairs(args_) do
		if type(code) == 'number' then
			args[code] = value
		else
			-- Removes suffix from the end of string keys.
			args[code:gsub(suffix .. '$', '')] = value
		end
	end
end

local function interpret_form(form_param, is_adjective)
	if type(form_param) ~= 'string' then
		return {}, {}, nil, nil
	end
	
	local no_article = form_param:find('X') and true or false
	if no_article and is_adjective then
		error('Adjectives cannot have articles. Remove ' .. quote('X') ..
			' option in the ' .. quote('form') .. ' parameter.')
	end
	
	-- Convert sing, dual, plur to S, D, P.
	-- Remove other lowercase letters. This removes "con" and "open".
	local number_codes = { sing = true, dual = true, plur = true }
	local contracted, comparative
	local new_form = form_param:gsub('((%l)%l+)', function(wholematch, initial)
			if wholematch == 'con' then
				contracted = true
			elseif wholematch  == 'open' then
				contracted = false
			elseif wholematch == 'comp' then
				comparative = true
			elseif number_codes[wholematch] then
				return initial:upper()
			end
			return ''
		end)
	
	--[[
		Returns tables containing all genders and numbers.
		For instance, for nouns that are variably masculine or feminine:
			args.gender		{ "M", "F", ["M"] = true, ["F"] = true }
			args.number		{ "S", "D", "P", ["S"] = true, ["D"] = true, ["P"] = true }
		Not sure if the sequential entries are needed.
	]]
	
	-- Find contiguous gender abbreviations.
	local genders = new_form:match("[MFN]+")
	
	if is_adjective and genders then
		error("Adjectives cannot have gender specified in the form parameter.")
	end
	
	-- Find number abbreviations.
	-- If no number, assume all numbers will be displayed.
	local numbers = new_form:gsub('[^SDP]+', '')
	if numbers == '' then
		numbers = 'SDP'
	end
	
	if genders then
		genders = mw.text.split(genders, "")
		for i, gender in ipairs(genders) do
			if gender == "" then
				genders[i] = nil
			else
				genders[gender] = true
			end
		end
		if genders.N and ( genders.M or genders.F ) then
			error("A noun cannot be neuter and another gender at the same time.")
		end
	else
		genders = {}
	end
	if numbers then
		numbers = mw.text.split(numbers, "")
		for i, number in ipairs(numbers) do
			if number == "" then
				numbers[i] = nil
			else
				numbers[number] = true
			end
		end
		if numbers.S and numbers.D and numbers.P then
			numbers.F = true
		end
	else
		numbers = {}
	end
	
	return genders, numbers, no_article, contracted, comparative
end
--[=[
	Need to check if the chosen dialect actually has forms defined for it.
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dial]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dialtilde]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dialslash]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dial/not Attic]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-adecl/dial]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-adecl/dialtilde]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-adecl/dialslash]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-adecl/dial/not Attic]]
	
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dialect alias]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/hom to epi]]
	
	Tracking for particular dialects:
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dial/att]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dial/ion]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/dial/epi]]
	...
]=]
local function handle_dialect(dialect, adjective)
	local track = track(adjective and 'grc-adecl' or 'grc-decl')
	
	if dialect then
		track('dial')
		if dialect:find('~') then
			track('dialtilde')
			if dialect:find('att') then
				dialect = nil
				track('dial/not Attic')
			else
				dialect = 'att'
			end
		elseif dialect:find('/') then
			track('dialslash')
			dialect = 'att'
		end
	else
		track('no dialect')
		return nil
	end
	
	local m_dialects = require("Module:grc:Dialects")
	local alias_of = m_dialects.aliases[dialect]
	if alias_of then
		track('dialect alias')
		dialect = alias_of
	end
	
	if dialect == 'hom' then
		track('hom to epi')
		dialect = 'epi'
	end
	
	if dialect then
		track('dial/' .. dialect)
	end
	
	return dialect
end

local function handle_unmarked_length(arg1, arg2, adjective)
	local old_arg1, old_arg2 = arg1, arg2
	if arg2 then
		local m_accent = require("Module:grc-accent")
		local standard_diacritics = require("Module:grc-utilities").standardDiacritics
		if arg1 == 'irreg' or arg1 == 'indecl' then
			arg2 = m_accent.mark_implied_length(standard_diacritics(arg2))
		else
			arg1, arg2 = m_accent.harmonize_length(standard_diacritics(arg1), standard_diacritics(arg2))
		end
	end
	
	local track = track(adjective and 'grc-adecl' or 'grc-decl')
	
	--[=[
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/length marked on arg1]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-decl/length marked on arg2]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-adecl/length marked on arg1]]
	[[Special:WhatLinksHere/Wiktionary:Tracking/grc-adecl/length marked on arg2]]
	]=]
	if old_arg1 and arg1 ~= decompose(old_arg1) then
		track('length marked on arg1')
	end
	
	if old_arg2 and arg2 ~= decompose(old_arg2) then
		track('length marked on arg2')
	end
	
	return arg1, arg2
end

local function get_args(args, is_adjective)
	-- Have to process args[1] before 'irreg' or 'indecl' is checked for.
	local arg1 = mw.text.trim(args[1])
	if arg1 == '' then arg1 = nil end
	
	local irreg = arg1 == 'irreg'
	local indecl = arg1 == 'indecl'
	
	local form_param = args.form
	
	-- [[Special:WhatLinksHere/Wiktionary:Tracking/grc-adecl/empty comp or super]]
	if is_adjective then
		if args.deg then
			if not (args.deg == 'comp' or args.deg == 'super') then
				error('Adjective degree ' .. quote(args.deg) .. ' not recognized.')
			end
			
			if args.comp or args.super then
				error("When the |deg= parameter is set, the parameters |comp= or |super= are not needed.")
			end
		end
		
		if args.comp and args.comp:find('^%s*$') or args.super and args.super:find('^%s*$') then
			require('Module:debug').track("grc-adecl/empty comp or super")
		end
	end
	
	local dialect_code
	local params = m_params[(irreg and 'irreg_' or '') ..
		(irreg and form_param and form_param:find('N') and 'N_' or '') ..
		(is_adjective and 'adj_' or 'noun_') ..
		'params']
	local args, unrecognized_args = require('Module:parameters').process(args, params, true)
	
	handle_unrecognized_args(unrecognized_args, is_adjective)
	form_param, dialect_code = args.form, args.dial
	args.maxindex = m_table.length(params)
	
	if irreg then
		require('Module:debug').track('grc-decl/irreg')
	end
	
	if is_adjective then
		args.adjective = true
		args.atable = {}
		
		if args.hp then
			track('grc-adecl', 'hp')
		end
	end
	
	args[1], args[2] = handle_unmarked_length(args[1], args[2], is_adjective)
	local arg2 = args[2]
	args.dial = handle_dialect(dialect_code, is_adjective)
	args.gender, args.number, args.no_article, args.contracted, args.comparative =
		interpret_form(form_param, is_adjective)
	
	if args.comparative then
		if (arg2 or args.dial ~= 'att' or not arg1:find("ον$")) then
			error("|form=comp is only meant for Attic third-declension comparatives with a stem in " .. quote("-ον") .. ".")
		end
		
		if args.deg then
			error('|form=comp not needed when |deg=comp is set.')
		end
	end
	
	if args.deg == 'comp' then
		args.comparative = true
	elseif args.deg == 'super' then
		args.superlative = true
	end
	
	args.indeclinable, args.irregular = indecl, irreg
	
	args.categories = {}
	
	local track = track(is_adjective and 'grc-adecl' or 'grc-decl')
	
	if ((arg1 or '') .. (arg2 or '')):find('˘') then
		track('manual-breve')
	end
	
	if args.titleapp then
		track('titleapp')
	end
	
	for _, v in ipairs({ 'titleapp', 'titleapp1', 'titleapp2' }) do
		if args[v] then
			args[v] = mw.text.split(args[v], '%s*[/,]%s*')
		else
			args[v] = {}
		end
	end
	
	for _, v in ipairs({ 'notes', 'notes1', 'notes2' }) do
		if args[v] then
			args['user_' .. v] = args[v] --convert 'notes' to 'user_notes'
			args[v] = {}
		else
			args[v] = {}
		end
	end
	args.form_cache = {}
	
	return args
end

--[=[
-- This function is an entry point for testing noun functionality only
-- ]=]
function export.test_decl(frame)
	local args = get_args(frame, false)
	m_grc_decl_decl.get_decl(args)
	m_grc_decl_decl.make_decl(args, args.decl_type, args.root)
	return args
end

-- These conditions should be mutually exclusive so that we don't need two
-- separate functions.
local function uncontracted_condition(dialect, contracted)
	return dialect == 'ion' or dialect == 'epi' or not (contracted == true or dialect == 'att')
end

local function contracted_condition(dialect, contracted)
	return not (dialect == 'ion' or dialect == 'epi' or contracted == false)
end

function export.decl(frame)
	local args = get_args(frame:getParent().args, false)
	m_grc_decl_decl.get_decl(args)
	
	if args.root:sub(1, 1) == '-' and not args.form:find('[MFN]') then
		args.no_article = true
	end
	if args.decl_type:find('2nd') and not args.decl_type:find('N') and not args.form:find('[MFN]') then
		table.insert(args.categories, 'Ancient Greek second-declension nouns without gender specified')
	end
	
	if mw.title.getCurrentTitle().nsText == 'User' then
		args = setmetatable({ __args = args }, {
			__index = function (self, key)
				mw.log('getting "' .. tostring(key) .. '" from args.')
				return self.__args[key] or rawget(self, key)
			end,
			__newindex = function (self, key, value)
				mw.log('setting field "' .. tostring(key) .. '" of args to "'
					.. tostring(value) .. '".')
				return rawset(self, key, value)
			end,
		})
	end
	
	if args.decl_type:find('κλῆς') or ufind(args.decl_type, '[ᾰε]σ') then
		args.titleapp = {}
		local titleapp_addition, swap_args_suffix
		
		if uncontracted_condition(args.dial, args.contracted) then
			swap_args_suffix = 1
			titleapp_addition = 'uncontracted'
		end
		
		if contracted_condition(args.dial, args.contracted) then
			swap_args_suffix = 2
			titleapp_addition =  '[[Appendix:Ancient Greek contraction|contracted]]'
		end
		
		if not titleapp_addition then
			error('Failed to decide whether noun is contracted or not!')
		else
			swap_args(args, swap_args_suffix)
			table.insert(args.titleapp, titleapp_addition)
		end
	end
	
	m_grc_decl_decl.make_decl(args, args.decl_type, args.root)
	args.article = m_grc_decl_decl.infl_art(args)
	return m_grc_decl_table.make_table(args)
end

--[=[
-- This function is an entry point for testing adjective functionality only
-- ]=]
function export.test_adecl(frame)
	local args = get_args(frame, true)
	m_grc_decl_decl.get_decl_adj(args)
	args.act = m_grc_decl_decl.adjinflections[args.decl_type]
	m_grc_decl_decl.make_decl_adj(args, m_grc_decl_decl.adjinflections[args.decl_type])
	return args
end

function export.adecl(frame)
	local args = get_args(frame:getParent().args, true)
	m_grc_decl_decl.get_decl_adj(args)
	
	if m_grc_decl_decl.adjinflections_con[args.decl_type] then
		args.titleapp, args.notes, args.categories = {}, {}, {}
		
		local titleapp_addition, swap_args_suffix
		
		if uncontracted_condition(args.dial, args.contracted) then
			swap_args_suffix = 1
			titleapp_addition = 'uncontracted'
			args.act = m_grc_decl_decl.adjinflections[args.decl_type]
		end
		
		if contracted_condition(args.dial, args.contracted) then
			swap_args_suffix = 2
			titleapp_addition = '[[Appendix:Ancient Greek contraction|contracted]]'
			args.act = m_grc_decl_decl.adjinflections_con[args.decl_type]
		end
		
		if titleapp_addition then
			table.insert(args.titleapp, titleapp_addition)
			swap_args(args, swap_args_suffix)
		else
			error('Could not decide whether noun, pronoun, participle, or determiner '
				.. 'was contracted or uncontracted!')
		end
	else
		args.act = m_grc_decl_decl.adjinflections[args.decl_type]
	end
	
	m_grc_decl_decl.make_decl_adj(args, args.act)
	return m_grc_decl_table.make_table_adj(args)
end


local function tag(text)
	local lang = require("Module:languages").getByCode("grc")
	return require("Module:script utilities").tag_text(text, lang)
end

function export.show_noun_forms(frame)
	local args = get_args(frame.args[1] and frame.args or frame:getParent().args, false)
	m_grc_decl_decl.get_decl(args)
	
	local success, message = pcall(m_grc_decl_decl.make_decl, args, args.decl_type, args.root)
	
	if not success then
		return 'Declension generation failed for ' .. args[1] .. (args[2] and ', ' .. args[2] or '') .. ': ' ..
			message
	end
	
	-- mw.logObject(args)
	
	local inflections = args.ctable
	
	local cases = { "N", "A", "V", "G", "D" }
	local numbers = { "S", "D", "P" }
	
	local out = { "\n* " .. tag(args[1]) .. ", " .. tag(args[2]) }
	for _, number in ipairs(numbers) do
		table.insert(out, "\n** ")
		local number_forms = {}
		
		for _, case in ipairs(cases) do
			local code = case .. number
			local form = inflections[code]
			
			if form then
				table.insert(number_forms, tag(form))
			end
		end
		
		number_forms = table.concat(number_forms, ", ")
		
		table.insert(out, number_forms)
	end
	
	return table.concat(out)
end

function export.show_adj_forms(frame)
	local args = get_args(frame.args[1] and frame.args or frame:getParent().args, true)
	m_grc_decl_decl.get_decl_adj(args)
	
	args.act = m_grc_decl_decl.adjinflections[args.decl_type]
	m_grc_decl_decl.make_decl_adj(args, m_grc_decl_decl.adjinflections[args.decl_type])
	
	-- mw.logObject(args)
	
	local function print(key, value)
		return key .. " = " .. "'" .. value .. "', "
	end
	
	local inflections = args.atable
	
	local genders = { "M", "F", "N" }
	local numbers = { "S", "D", "P" }
	local cases = { "N", "A", "V", "G", "D" }
	
	local out = require('Module:array')()
	
	out:insert("{ '" .. args[1] .. "'")
	if args[2] then
		out:insert(", '" .. args[2] .. "'")
	end
	out:insert(" },\n{")
	
	for _, gender in pairs(genders) do
		out:insert("\n\t")
		for _, number in pairs(numbers) do
			for _, case in pairs(cases) do
				local code = gender .. case .. number
				local form = inflections[code]
				
				if form then
					out:insert(print(code, form))
				end
			end
		end
	end
	
	out:insert("\n\t")
	
	local forms = { "adv", "comp", "super" }
	for _, form in pairs(forms) do
		if inflections[form] then
			out:insert(print(form, inflections[form]))
		end
	end
	
	out:insert("\n},")
	
	return frame:extensionTag{ content = out:concat(), name = "source", args = { lang = "lua" } }
end

return export