Module:ro-adjective

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

This module implements {{ro-decl-adj}}.


local export = {}
local data = {}
data.forms = {}

local form_names = {
	"na_indef_sg_m",
	"na_indef_sg_f",
	"na_indef_pl_m",
	"na_indef_pl_f",
	"na_def_sg_m",
	"na_def_sg_f",
	"na_def_pl_m",
	"na_def_pl_f",
	"gd_indef_sg_m",
	"gd_indef_sg_f",
	"gd_indef_pl_m",
	"gd_indef_pl_f",
	"gd_def_sg_m",
	"gd_def_sg_f",
	"gd_def_pl_m",
	"gd_def_pl_f",
}

local consonants = {
	i = {
		str = 'ștr';
		st = 'șt';
		sc = 'șt';
		d = 'z';
		s = 'ș';
		t = 'ț';
		x = 'cș';
	},
	e = {
		sc = 'șt';
	}
}

-- these adjectives have different defaults
local endings = {
	{pattern = "(([stz])or)$", fs = "%2oare", mp = "%2ori", fp = "%2oare"},
	{pattern = "os$", fs = "oasă", mp = "oși", fp = "oase"},
	{pattern = "iesc$", fs = "iască", mp = "iești", fp = "iești"},
	{pattern = "esc$", fs = "ească", mp = "ești", fp = "ești"},
	{pattern = "(([iy])an)$", fs = "%2ană", mp = "%2eni", fp = "%2ene"},
	{pattern = "ean$", fs = "eană", mp = "eni", fp = "ene"},
	{pattern = "eț$", fs = "eață", mp = "eți", fp = "ețe"},
	{pattern = "iu$", fs = "ie", mp = "ii", fp = "ii"},
}

local m_links = require("Module:links")
local lang = require("Module:languages").getByCode("ro")

local PAGENAME = mw.title.getCurrentTitle().text

local function make_definite(word, gender)
	local definite
	
	if gender == "m" or gender == "n" then
		if word:find("u$") then
			definite = word .. "l"
		elseif word:find("e$") then
			definite = word .. "le"
		else
			definite = word .. "ul"
		end
	elseif gender == "f" then
		if word:find("ă$") then
			definite = word:gsub("ă$", "a")
		elseif mw.ustring.find(word, "[aeiouăâî]ie") then
			definite = word:gsub("e$", "a")
		elseif mw.ustring.find(word, "ie$") then
			definite = word:gsub("e$", "a")
		elseif mw.ustring.find(word, "e$") then
			definite = word .. "a"
		elseif mw.ustring.find(word, "a$") then
			definite = word .. "ua"
		end
	else
		error("Invalid or missing gender")
	end

	return definite
end

local function make_feminine(word)
	local f
	
	if word:find("e$") then
		f = word
	elseif word:find("[aăâiîeou]i$") then
		f = word .. "e"
	elseif word:find("iu$") then
		f = mw.ustring.gsub(word, "u$", "e")
	elseif word:find("u$") then
		f = word:sub(1, -2) .. "ă"
	elseif word:match("[cg]h?i$") then
		f = word:sub(1, -2) .. "e"
	elseif word:match("[^aioăâî]$") then
		f = word .. "ă"
	else
		error("Feminine of \"" .. word .. "\" could not be generated automatically")
	end
	
	if f and f:match("[âei]ă$") then
		f = f:gsub("ă$", "e")
	end
	
	return f
end

local function split(word)
	local stem, cons
	
	if word:match('[aeiouăâî][iu]$') then
		stem, cons = word:match('^(.-[aeiouăâî]-)([iu])$')
	else
		stem, cons = word:match('^(.-[aeiouăâî]-)([bcdfghj-np-tv-zșț]-)$')
	end
	
	return stem, cons
end

local function find_cons(cons, mode)
	for i = -3, -1 do
		if consonants[mode][mw.ustring.sub(cons, i)] and not mw.ustring.match(mw.ustring.sub(cons, 1, i-1), "^[șj]$") then
			cons = mw.ustring.sub(cons, 1, i-1) .. consonants[mode][mw.ustring.sub(cons, i)]
			break
		end
	end

	return cons
end

local function make_plural(word, g)
	local pl
	local suf1, suf2, pre1 = mw.ustring.sub(word, -1), mw.ustring.sub(word, -2), mw.ustring.sub(word, 1, -2)
	
	if g == 'm' then
		if suf1 == 'i' then
			pl = word
		else
			local stem, cons = split(word:match('^(.-)[eu]?$'))
			cons = find_cons(cons, 'i')
			pl = stem .. cons .. 'i'
		end
	elseif g == 'f' then
		if suf1 == 'a' then
			if suf2 == 'ea' then
				pl = pre1 .. 'le'
			elseif suf2 == 'ia' then
				pl = pre1 .. 'ele'
			else
				pl = word .. 'le'
			end
		else
			if suf1 == 'e' then
				pl = word
			elseif suf1 == 'ă' then
				local stem, cons = split(word:match('^(.-)ă$'))
				cons = find_cons(cons, 'e')
				pl = stem .. cons .. 'e'
			end
		end
	end

	return pl
end

local function remove_duplicates(array)
	local existing = {}
	
	for i, val in ipairs(array) do
		if existing[val] then
			table.remove(array, i)
			
			if array.maxindex then
				array.maxindex = array.maxindex - 1
			end
		else
			existing[val] = true
		end
	end
end

local function make_indef_sg_m(lemma, only)
	--Try to recreate the masculine singular indefinite form if it's
	--not the lemma. Many of these situations will never happen.
	local ret = lemma

	if only.def then
		if only.pl then
			if only.f then
				--def-pl-f
				ret = lemma:match("(.+)ele$")
			else
				--def-pl-m
				ret = lemma:match("(.+)ii$")
			end
		else
			if only.f then
				--def-sg-f
				ret = lemma:match("(.+)a$")
			else
				--def-sg-m
				ret = lemma:match("(.+)ul$")
			end
		end
	else
		if only.pl then
			if only.f then
				--indef-pl-f
				ret = lemma:match("(.+)e$")
			else
				--indef-pl-m
				ret = lemma:match("(.+)i")
			end
		else
			if only.f then
				--indef-sg-f
				ret = lemma:match("(.+)ă")
			end
		end
	end

	if not ret then
		error("Could not recreate the masculine indefinite singular form.")
	end

	return ret
end

--also used in [[Module:ro-headword]]
function export.make_basic_forms(ms, fs, mp, fp, i_mode, only)
	local base = make_indef_sg_m(ms[1], only)
	local ending = false
	local param_track = {redundant = false, needed = false}
	
	for i, val in ipairs(endings) do
		if base:match(val.pattern) then
			local match = base:match(val.pattern)
			
			require("Module:debug").track("ro-adjective/" .. match)
			
			if fs[1] or mp[1] or fp[1] or i_mode then
				require("Module:debug").track("ro-adjective/" .. match .. "/custom")
				require("Module:debug").track("ro-adjective/custom")
			else
				require("Module:debug").track("ro-adjective/" .. match .. "/default")
				require("Module:debug").track("ro-adjective/default")
			end
			
			ending = i
			
			break
		end
	end
	
	if not fs[1] then
		if ending then
			local form = mw.ustring.gsub(base, endings[ending].pattern, endings[ending].fs)
			table.insert(fs, 1, form)
		else
			table.insert(fs, 1, make_feminine(base))
		end
		
		if only.def then
			fs[1] = make_definite(fs[1], "f")
		end
		
		if fs.maxindex == 0 then
			fs.maxindex = 1
		end
		
		remove_duplicates(fs)
	else
		if (ending and mw.ustring.gsub(base, endings[ending].pattern, endings[ending].fs)
		or make_feminine(base)) == fs[1] then
			param_track.redundant = true
		else
			param_track.needed = true
		end
	end
	
	if not mp[1] then
		if ending then
			local form = mw.ustring.gsub(base, endings[ending].pattern, endings[ending].mp)
			table.insert(mp, 1, form)
		else
			table.insert(mp, 1, make_plural(base, "m"))
		end
		
		if only.def then
			mp[1] = mp[1] .. "i"
		end
		
		if mp.maxindex == 0 then
			mp.maxindex = 1
		end
		
		remove_duplicates(mp)
	else
		if (ending and mw.ustring.gsub(base, endings[ending].pattern, endings[ending].mp)
		or make_plural(base, "m")) == mp[1] then
			param_track.redundant = true
		else
			param_track.needed = true
		end
	end
	
	if i_mode then
		--mw.clone does not work
		for i = 1, mp.maxindex do
			fp[i] = mp[i]
		end
		
		fp.maxindex = mp.maxindex
	else
		if only.def then
			if not fp[1] then
				table.insert(fp, 1, make_plural(make_feminine(base), "f") .. "le")
				
				if fp.maxindex == 0 then
					fp.maxindex = 1
				end
			end
		else
			--for each feminine singular form, generate a feminine plural form if
			--one has not already been provided
			local newindex = 0
			
			for i, form in ipairs(fs) do
				if fp[i] == "-" then
					fp[i] = nil
				else
					newindex = i
					
					if not fp[i] then
						if ending and i == 1 then
							local form = mw.ustring.gsub(base, endings[ending].pattern, endings[ending].fp)
							table.insert(fp, 1, form)
						else
							table.insert(fp, i, make_plural(form, "f"))
						end
						
						if only.def then
							fp[i] = fp[i] .. "le"
						end
						
						newindex = i
					else
						if ((ending and i == 1) and mw.ustring.gsub(base, endings[ending].pattern, endings[ending].fp)
						or make_plural(form, "f")) == fp[i] then
							param_track.redundant = true
						else
							param_track.needed = true
						end
					end
				end
			end
		
			fp.maxindex = newindex
			
			if fp.maxindex == 0 then
				error("There must be at least one feminine plural form")
			end
		end
			
		remove_duplicates(fp)
	end
	
	if param_track.redundant then
		require("Module:debug").track("ro-adjective/parameters/redundant")
	end
	
	if param_track.needed then
		require("Module:debug").track("ro-adjective/parameters/needed")
	end
	
	if not param_track.redundant and not param_track.needed then
		require("Module:debug").track("ro-adjective/parameters/none")
	end
end

--Process forms that were given explicitly
local function process_explicit_forms(data, extra_args)
	for arg, val in pairs(extra_args) do
		local arg_parts, count, valid = {}, 0, false
		
		for i, part in ipairs(mw.text.split(arg, "_")) do
			arg_parts[part] = true
			count = count + 1
		end
		
		for i, form in ipairs(form_names) do
			local counter = 0
			
			for j, part in ipairs(mw.text.split(form, "_")) do
				if arg_parts[part] then
					counter = counter + 1
				end
			end
			
			if count == counter then
				if val == "-" then
					data.forms[form] = nil
				else
					data.forms[form] = {val}
				end
				
				valid = true
			end
		end
		
		if not valid then
			error("The parameter \"" .. arg .. "\" is not used by this template.")
		end
	end
end

local function make_table(data)
	data.info = table.concat(data.info, ", ")
	if data.info == "" then data.info = nil end
	
	local function show_form(form)
		if not form then
			return "—"
		elseif type(form) ~= "table" then
			error("a non-table value was given in the list of inflected forms.")
		elseif #form == 0 then
			return "—"
		end
		
		local ret = {}
		
		for key, subform in ipairs(form) do
			table.insert(ret, m_links.full_link({lang = lang, term = subform}))
		end
		
		return table.concat(ret, "<br/>")
	end
	
	local function repl(param)
		if param == "lemma" then
			return m_links.full_link({lang = lang, alt = data.lemma}, "term")
		elseif param == "info" then
			return data.info and " (" .. data.info .. ")" or ""
		else
			return show_form(data.forms[param])
		end
	end
	
	local result = [=[
	<div class="NavFrame">
	<div class="NavHead" style="background:#b0bed3" align=center>Declension of {{{lemma}}}{{{info}}}</div>
	<div class="NavContent">
	
	{| border="1px solid #000000" style="border-collapse:collapse; width:100%; background:#F0F0F0; text-align:center" class="inflection-table"
	
	|-
	! colspan="2" style="background:#b0bed3;width:28%" |
	! colspan="3" style="background:#b0bed3;width:36%" | singular
	! colspan="3" style="background:#b0bed3;width:36%" | plural
	
	|-
	! style="width:20%; background:#c0cfe4" |
	! style="width:16%; background:#d0d0d0" |
	! style="width:8%; background:#d0d0d0" | masculine
	! style="width:8%; background:#d0d0d0" | neuter
	! style="width:16%; background:#d0d0d0" | feminine
	! style="width:16%; background:#d0d0d0" | masculine
	! style="width:8%; background:#d0d0d0" | neuter
	! style="width:8%; background:#d0d0d0" | feminine
	
	|-
	! rowspan="2" style="height:4em;background:#c0cfe4" | nominative/ <br/> accusative
	! style="background:#d0d0d0" | indefinite
	| colspan="2" | {{{na_indef_sg_m}}}
	| {{{na_indef_sg_f}}}
	| {{{na_indef_pl_m}}}
	| colspan="2" | {{{na_indef_pl_f}}}
	
	|-
	! style="background:#d0d0d0" | definite
	| colspan="2" | {{{na_def_sg_m}}}
	| {{{na_def_sg_f}}}
	| {{{na_def_pl_m}}}
	| colspan="2" | {{{na_def_pl_f}}}
	
	|-
	! rowspan="2" style="height:4em;background:#c0cfe4" | genitive/ <br/> dative
	! style="background:#d0d0d0" | indefinite
	| colspan="2" | {{{gd_indef_sg_m}}}
	| {{{gd_indef_sg_f}}}
	| {{{gd_indef_pl_m}}}
	| colspan="2" | {{{gd_indef_pl_f}}}
	|-
	! style="background:#d0d0d0" | definite
	| colspan="2" | {{{gd_def_sg_m}}}
	| {{{gd_def_sg_f}}}
	| {{{gd_def_pl_m}}}
	| colspan="2" | {{{gd_def_pl_f}}}
	
	|}</div></div>]=]

	return (mw.ustring.gsub(result, "{{{([a-z0-9_]+)}}}", repl))
end

function export.show(frame)
	local args = frame:getParent().args
	
	if args[12] then
		require("Module:debug").track("ro-decl-adj/old")
		
		local params = {
			[1] = {list = true, allow_holes = true},
		}
		
		args = require("Module:parameters").process(args, params)
		
		data.info = {}
		
		local form_order = {
			"na_indef_sg_m",
			"na_indef_sg_f",
			"na_indef_pl_m",
			"na_indef_pl_f",
			"na_def_sg_m",
			"na_def_sg_f",
			"na_def_pl_m",
			"na_def_pl_f",
			"gd_def_sg_m",
			"gd_def_sg_f",
			"gd_def_pl_m",
			"gd_def_pl_f",
		}
		
		for key, form in ipairs(form_order) do
			data.forms[form] = {args[1][key]}
		end
		
		data.forms.gd_indef_sg_m = mw.clone(data.forms.na_indef_sg_m)
		data.forms.gd_indef_sg_f = mw.clone(data.forms.na_indef_pl_f)
		data.forms.gd_indef_pl_m = mw.clone(data.forms.na_indef_pl_m)
		data.forms.gd_indef_pl_f = mw.clone(data.forms.na_indef_pl_f)
		
		data.lemma = PAGENAME
	else
		require("Module:debug").track("ro-decl-adj/new")
		
		local params = {
			[1] = {},
			["f"] = {list = true, allow_holes = true},
			["mp"] = {list = true, allow_holes = true},
			["fp"] = {list = true, allow_holes = true},
			["pl"] = {list = true, allow_holes = true},
			["lemma"] = {},
			["only"] = {},
		}
		
		local extra_args
		
		args, extra_args = require("Module:parameters").process(args, params, true)
		
		data.lemma = args["lemma"] or PAGENAME
		data.info = {}
		data.only = {}
		
		if args.only then
			for i, val in ipairs(mw.text.split(args.only, "-")) do
				data.only[val] = true
			end
		end
		
		local f = data.forms
		
		for i, form in ipairs(form_names) do
			f[form] = {}
		end
		
		f.na_indef_sg_m = {make_indef_sg_m(data.lemma, data.only)}
		
		local modes = {}
		
		if args[1] then
			for key, val in ipairs(mw.text.split(args[1], "-")) do
				modes[val] = true
			end
		end
		
		if modes.det then
			data.only.indef = true
		end
		
		local only_modes = {
			["indef"] = "indefinite",
			["def"] = "definite",
			["sg"] = "singular",
			["pl"] = "plural",
			["m"] = "masculine",
			["f"] = "feminine",
		}
		
		if modes.inv then
			table.insert(data.info, "invariable")
			
			for key, form in ipairs(form_names) do
				if form:find("indef") then
					f[form] = {data.lemma}
				end
			end
		else
			f.na_indef_sg_f, f.na_indef_pl_m, f.na_indef_pl_f = args["f"], args["pl"].maxindex > 0 and args["pl"] or args["mp"], args["pl"].maxindex > 0 and args["pl"] or args["fp"]
			
			export.make_basic_forms(f.na_indef_sg_m, f.na_indef_sg_f, f.na_indef_pl_m, f.na_indef_pl_f, modes.i, {})
			
			if modes.det then
				table.insert(data.info, "determiner declension")
				
				f.gd_indef_sg_m = {f.na_indef_sg_m[1]:match("^(.*)[eu]?$") .. "ui"}
				f.gd_indef_sg_f = {f.na_indef_sg_m[1]:match("^(.*)[eu]?$") .. "ei"}
				f.gd_indef_pl_m = {f.na_indef_sg_m[1]:match("^(.*)[eu]?$") .. "or"}
				f.gd_indef_pl_f = mw.clone(f.gd_indef_pl_m)
			else
				--forms derived from the masculine singular
				f.na_def_sg_m = {make_definite(f.na_indef_sg_m[1], "m")}
				f.gd_def_sg_m = {f.na_def_sg_m[1]:find("e$") and f.na_def_sg_m[1]:sub(1, -2) .. "ui" or f.na_def_sg_m[1] .. "ui"}
				f.gd_indef_sg_m = {f.na_indef_sg_m[1]}
				
				--forms derived from the feminine singular
				for i = 1, f.na_indef_sg_f.maxindex do
					if f.na_indef_sg_f[i] ~= nil then
						table.insert(f.na_def_sg_f, make_definite(f.na_indef_sg_f[i], "f"))
					end
				end
				
				--forms derived from the masculine plural
				for i = 1, f.na_indef_pl_m.maxindex do 
					if f.na_indef_pl_m[i] ~= nil then
						table.insert(f.na_def_pl_m, f.na_indef_pl_m[i] .. "i")
						table.insert(f.gd_indef_pl_m, f.na_indef_pl_m[i])
						table.insert(f.gd_def_pl_m, f.na_indef_pl_m[i] .. "lor")
					end
				end
				
				--forms derived from the feminine plural
				for i = 1, f.na_indef_pl_f.maxindex do 
					if f.na_indef_pl_f[i] ~= nil then		
						table.insert(f.na_def_pl_f, f.na_indef_pl_f[i] .. "le")
						table.insert(f.gd_indef_sg_f, f.na_indef_pl_f[i])
						table.insert(f.gd_indef_pl_f,f.na_indef_pl_f[i])
						table.insert(f.gd_def_pl_f, f.na_indef_pl_f[i] .. "lor")
						
						if f.na_indef_pl_f[i]:find("[^aăâeiîou]ii$") then
							--this could cause issues if the feminine singular and
							--feminine plural are not in sync
							table.insert(f.gd_def_sg_f, f.na_indef_sg_f[i] .. "i")
						else
							table.insert(f.gd_def_sg_f, f.na_indef_pl_f[i] .. "i")
						end
					end
				end
			end
			
			process_explicit_forms(data, extra_args)
		end
		
		for key, name in pairs(only_modes) do
			if data.only[key] then
				table.insert(data.info, name .. " only")
				
				for i, form in ipairs(form_names) do
					if not ("_" .. form .. "_"):find("_" .. key .. "_") then
						f[form] = nil
					end
				end
			end
		end
	end
	
	return make_table(data)
end

return export