Modul:en-utilities

local export = {}

local require_when_needed = require("Module:require when needed")

local add_suffix -- Defined below.
local find = string.find
local match = string.match
local reverse = string.reverse
local sub = string.sub
local toNFD = mw.ustring.toNFD
local ugsub = require_when_needed("Module:string utilities", "gsub")
local ulower = require_when_needed("Module:string utilities", "lower")
local umatch = require_when_needed("Module:string utilities", "match")

local vowels, diacritics = "aæᴀᴁɐɑɒ@eᴇǝⱻəɛɘɜɞɤiıɪɨᵻoøœᴏɶɔᴐɵuᴜʉᵾɯꟺʊʋʌyʏ"

-- Normalize a string, so that case and diacritics are ignored.
local function normalize(str)
	if not diacritics then
		diacritics = mw.loadData("Module:headword/data").page.comb_chars.diacritics_all .. "+"
	end
	return ulower(ugsub(toNFD(str), diacritics, ""))
end

local function default_epenthetic_e(stem)
	return sub(stem, -1) ~= "e"
end

local suffixes = {}

-- FIXME, a subrule of rule #1 below says the -ies ending doesn't
-- apply to proper nouns, hence "the Gettys", "the public Ivys".
-- We should maybe consider applying this rule here; but it may not
-- be important as this function is almost always called on common nouns
-- (e.g. parts of speech, place types).
suffixes[""] = { --Fungsi untuk menambah -s pada akhir kata, sudah dinyahaktifkan
	final_y_is_i = true,
	epenthetic_e = function(stem, term)
		if stem ~= term then
			return true
		end
		local ch = sub(stem, -1)
		if ch == "s" or ch == "x" or ch == "z" then
			return true
		elseif ch ~= "h" then
			return false
		end
		ch = sub(stem, -2, -2)
		return ch == "c" or ch == "s" or ch == "z"
	end,
}

suffixes["ing"] = {
	final_consonant_is_doubled = true,
	remove_silent_e = true,
}

suffixes["d"] = {
	final_y_is_i = true,
	final_consonant_is_doubled = true,
	epenthetic_e = default_epenthetic_e,
}

suffixes["dst"] = suffixes["d"]
suffixes["st.verb"] = suffixes["d"]
suffixes["th"] = suffixes["d"]

suffixes["n"] = {
	final_y_is_i = true,
	final_consonant_is_doubled = true,
	epenthetic_e = function(stem)
		local final = sub(stem, -1)
		return not (
			final == "e" or
			final == "w" or
			final == "r" and umatch(normalize(sub(stem, -2, -2)), "[" .. vowels .. "]")
		)
	end,
}

suffixes["r"] = {
	final_y_is_i = true,
	final_ey_is_i = true,
	final_consonant_is_doubled = true,
	epenthetic_e = default_epenthetic_e
}

suffixes["st.superlative"] = suffixes["r"]

-- Returns the stem used for suffixes that sometimes convert final "y"
-- into "i", such as "-es" ("-ies"), e.g. "penny" → "penni" ("pennies").
-- If `final_ey_is_i` is true, final "ey" may also be converted, e.g.
-- "plaguey" → "plagui"; this is needed for "-er" ("-ier") and "-est"
-- ("-iest").
local function convert_final_y_to_i(str, final_ey_is_i)
	if sub(str, -1) ~= "y" then
		return str
	end
	local final2 = sub(str, -2)
	-- If `final_ey_is_i` is true, treat final "-ey" can also be reduced.
	if final_ey_is_i and final2 == "ey" then
		local final3 = sub(str, -3)
		-- Special case: treat "eey" as "ee" + "y" (e.g. "treey" →
		-- "treeiest"). This doesn't apply if `final_ey_is_i` is not true, as
		-- `final_ey_is_i` assumes final "(e)y" is likely to be syllabic.
		if final3 == "eey" then
			return sub(str, 1, -2) .. "i"
		end
		local base_stem = sub(str, 1, -3)
		-- Special case: allow final "-ey" ("potato-ey" → "potato-iest").
		if final3 == "-ey" then
			return base_stem .. "i"
		end
		-- Final "ey" becomes "i" iff the term is polysyllabic (e.g. not
		-- "grey"). "ey" is common if the base stem ends in a vowel
		-- ("echo" → "echoey"), so the presence of a vowel anywhere in the
		-- base stem is sufficient to deem it polysyllabic. ("echoey" →
		-- "echo" → "echoiest", "beigey" → "beig" → "beigiest", but
		-- "grey" → "gr" → "greyest"). The first "y" in "-yey" can be
		-- treated as a vowel as long as it's preceded by something
		-- ("clayey" → "clay" → "clayiest", "cryey" → "cry" → "cryiest"),
		-- so it needs to be treated as a special case.
		local normalized = normalize(base_stem)
		if sub(normalized, -1) == "y" then
			if umatch(normalized, "[%w@][yY]$") then
				return base_stem .. "i"
			end
		elseif umatch(normalized, "[" .. vowels .. "%d]%w*$") then
			return base_stem .. "i"
		end
	-- Special cases: match final "-y" ("bro-y" → "bro-iest"), and "quy"
	-- ("soliloquy" → "soliloquies").
	elseif final2 == "-y" or sub(str, -3) == "quy" then
	-- Otherwise, final "y" becomes "i" iff it's not preceded by a vowel
	-- ("shy" → "shiest", "horsy" → "horsies", but "day" → "days", "coy" →
	-- "coyest").
		return sub(str, 1, -2) .. "i"
	else
		local base_stem = sub(str, 1, -2)
		if umatch(normalize(base_stem), "[^%s%p" .. vowels .. "]$") then
			return base_stem .. "i"
		end
	end
	return str
end

local function double_final_consonant(str, final)
	local initial = umatch(normalize(sub(str, 1, -2)), "^.*%f[^%z%s-–]([%l%p]*)[" .. vowels .. "]$")
	if not initial then
		return str
	elseif initial == "" or initial == "y" then
		return str .. final
	elseif match(initial, "^.[\128-\191]*$") then
		return umatch(initial, "[^" .. vowels .. "]") and (str .. final) or str
	end
	return umatch(initial, "^[^" .. vowels .. "]*%f[^%l]$") and (str .. final) or str
end

local function remove_silent_e(str)
	local final2 = sub(str, -2)
	if final2 == "ie" then
		return sub(str, 1, -3) .. "y"
	end
	local base_stem = sub(str, 1, -2)
	if final2 == "ue" then
		return base_stem
	end
	return umatch(normalize(base_stem), "^.*[" .. vowels .. "][^" .. vowels .. "]+$") and base_stem or str
end

function export.add_suffix(term, suffix)
	local data = suffixes[suffix]
  suffix = match(suffix, "^([^.]*)")
	local final, stem = sub(term, -1)
	if data.final_y_is_i and final == "y" then
		stem = convert_final_y_to_i(term, data.final_ey_is_i)
	elseif data.final_consonant_is_doubled and match(final, "[bcdfgjklmnpqrstvz]") then
		stem = double_final_consonant(term, final)
	elseif data.remove_silent_e and final == "e" then
		stem = remove_silent_e(term)
	else
		stem = term
	end
	local epenthetic_e = data.epenthetic_e
	if epenthetic_e and epenthetic_e(stem, term) then
		return stem .. "e" .. suffix
	end
	return stem .. suffix
end
add_suffix = export.add_suffix

--[==[
Pluralize a word in a smart fashion, according to normal English rules.
# If the word ends in a consonant or "qu" + "-y", replace "-y" with "-ies".
# If the word ends in "s", "x", "z", "ch", "sh" or "zh", add "-es".
# Otherwise, add "-s".

This handles links correctly:
# If a piped link, change the second part appropriately.
# If a non-piped link and rule #1 above applies, convert to a piped link with the second part containing the plural.
# If a non-piped link and rules #2 or #3 above apply, add the plural outside the link.
]==]
function export.pluralize(str)
	-- Treat as a link if a "[[" is present and the string ends with "]]".
	if not (find(str, "[[", 1, true) and sub(str, -2) == "]]") then
		return add_suffix(str, "")
	end
	-- Find the last "[[" (in case there is more than one) by reversing
	-- the string.
	local str_rev = reverse(str)
	local open = find(str_rev, "[[", 3, true)
	-- If the last "[[" is followed by a "]]" which isn't at the end,
	-- then the final "]]" is just plaintext (e.g. "[[foo]]bar]]").
	local bad_close = find(str_rev, "]]", 3, true)
	-- Note: the bad "]]" will have a lower index than the last "[[" in
	-- the reversed string.
	if bad_close and bad_close < open then
		return add_suffix(str, "")
	end
	open = #str - open + 2
	-- Get the target and display text by searching from just after "[[".
	local target, display = match(str, "([^|]*)|?(.*)%]%]$", open)
	display = add_suffix(display ~= "" and display or target, "")
	-- If the link target is a substring of the display text, then
	-- use a trail (e.g. "[[foo]]" → "[[foo]]s", since "foo" is a substring
	-- of "foos").
	local index, trail = find(display, target, 1, true)
	if index == 1 then
		return sub(str, 1, open - 1) .. target .. "]]" .. sub(display, trail + 1)
	end
	-- Otherwise, return a piped link.
	return sub(str, 1, open - 1) .. target .. "|" .. display .. "]]"
end

return export