Bước tới nội dung

Mô đun:is-common

Từ điển mở Wiktionary

local export = {}

local string_char_module = "Module:string/char"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_replacement_escape_module = "Module:string/replacementEscape"
local inflection_utilities_module = "Module:inflection utilities"

local dump = mw.dumpObject
local rmatch = mw.ustring.match
local rsubn = mw.ustring.gsub
local usub = mw.ustring.sub
local uupper = mw.ustring.upper

local function pattern_escape(...)
	pattern_escape = require(string_pattern_escape_module)
	return pattern_escape(...)
end

local function replacement_escape(...)
	replacement_escape = require(string_replacement_escape_module)
	return replacement_escape(...)
end

local function u(...)
	u = require(string_char_module)
	return u(...)
end

-- Capitalize the first letter.
local function ucap(str)
	local first, rest = rmatch(str, "^(.)(.*)$")
	if first then
		return uupper(first) .. rest
	end
	return str
end

-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
	local retval = rsubn(term, foo, bar)
	return retval
end

local function track(track_id)
	require("Module:debug/track")("is-common/" .. track_id)
	return true
end

local AU_SUB = u(0xFFF0) -- temporary substitution for 'au'
local CAP_AU_SUB = u(0xFFF1) -- temporary substitution for 'Au'
local ALL_CAP_AU_SUB = u(0xFFF2) -- temporary substitution for 'AU'
local EY_SUB = u(0xFFF3) -- temporary substitution for 'ey'
local CAP_EY_SUB = u(0xFFF4) -- temporary substitution for 'Ey'
local ALL_CAP_EY_SUB = u(0xFFF5) -- temporary substitution for 'EY'
local UR_SUB = u(0xFFF6) -- temporary substitution for final 'ur'; should be treated as consonant
local lc_vowel = "aeiouyáéíóúýöæ"
local uc_vowel = uupper(lc_vowel)
export.vowel = lc_vowel .. uc_vowel .. AU_SUB .. CAP_AU_SUB .. ALL_CAP_AU_SUB
export.vowel_c = "[" .. export.vowel .. "]"
export.vowel_or_hyphen = export.vowel .. "%-"
export.vowel_or_hyphen_c = "[" .. export.vowel_or_hyphen .. "]"
export.non_vowel_c = "[^" .. export.vowel .. "]"
export.cons_c = "[^" .. export.vowel .. "]"

local V = export.vowel_c
local C = export.cons_c

export.umut_types = {"umut", "Umut", "uumut", "uUmut", "uUUmut", "u_mut"}
export.unumut_types = {}
for _, umut_type in ipairs(export.umut_types) do
	table.insert(export.unumut_types, "un" .. umut_type)
end

-- Maybe replace -au-, -ey- and/or final -ur with special characters so they won't get substituted. If `apply_au_sub`,
-- substitute -au-. If `apply_ey_sub`, substitute -ey-. If `apply_ur_sub`, substitute final -ur. Use undo_au_ey_ur_sub()
-- to reverse the substitution(s).
local function apply_au_ey_ur_sub(stem, apply_au_sub, apply_ey_sub, apply_ur_sub)
	if apply_au_sub then
		-- au doesn't u-mutate, while u does; easiest way to handle this is to temporarily convert au and variants to single
		-- characters
		stem = stem:gsub("au", AU_SUB)
			:gsub("Au", CAP_AU_SUB)
			:gsub("AU", ALL_CAP_AU_SUB)
	end
	if apply_ey_sub then
		-- ey doesn't reverse i-mutate, while y does; u-mutate; easiest way to handle this is to temporarily convert ey and
		-- variants to single characters
		stem = stem:gsub("ey", EY_SUB)
			:gsub("Ey", CAP_EY_SUB)
			:gsub("EY", ALL_CAP_EY_SUB)
	end
	if apply_ur_sub then
		-- There must be at least one vowel to treat -ur as a suffix, or we must be dealing with the suffix [[-ur]] itself;
		-- lemmas like [[bur]] don't count.
		stem = (rsub(stem, "^(.*" ..export.vowel_or_hyphen_c .. ".*)ur$", "%1" .. UR_SUB))
	end
	return stem
end

local function undo_au_ey_ur_sub(stem)
	return (stem:gsub(UR_SUB, "ur")
		:gsub(EY_SUB, "ey")
		:gsub(CAP_EY_SUB, "Ey")
		:gsub(ALL_CAP_EY_SUB, "EY")
		:gsub(AU_SUB, "au")
		:gsub(CAP_AU_SUB, "Au")
		:gsub(ALL_CAP_AU_SUB, "AU")
	)
end


local lc_i_mutation = {
	["a"] = "e", -- [[dagur]] "dat" -> dat sg [[degi]]; [[faðir]] "father" -> nom pl [[feður]]; [[maður]] "man" -> nom
				 -- pl [[menn]]; [[taka]] "to take" -> 1sg pres ind [[tek]]; [[langur]] "long" -> [[lengd]] "length"
	["á"] = "æ", -- [[háttur]] "way, manner" -> nom pl [[hættir]]; [[hár]] "high" -> comp [[hærri]]
	-- ["e"] = "i", -- I don't think there are any instances of this in inflections and it's wrong for strong verbs
	["o"] = "e", -- [[hnot]] "nut; small ball of yarn" -> nom pl [[hnetur]]; [[koma]] "to come" -> 1sg pres ind [[kem]]
	-- ["o"] = "y", -- [[sonur]] "son" -> nom pl [[synir]]; in the subjunctive of several verbs; needs explicit vowel
	["ö"] = "e", -- [[mölur]] "clothes moth" -> nom pl [[melir]]; [[köttur]] "cat" -> nom pl [[kettir]]; [[slökkva]]
				 -- "to extinguish" -> 1sg pres ind [[slekk]]; [[dökkur]] "dark" -> comp [[dekkri]]
	["ó"] = "æ", -- [[bók]] "book" -> nom pl [[bækur]]; [[stór]] "big" -> comp [[stærri]]; [[dómur]] "judgement" ->
				 -- [[dæmdur]] "judged"
	["u"] = "y", -- [[fullur]] "full" -> comp [[fyllri]]; [[þungur]] "heavy/weighty" -> [[þyngd]] "weight"
	["ú"] = "ý", -- [[mús]] "mouse" -> nom pl [[mýs]]; [[brú]] "bridge" -> nom pl [[brýr]]; [[búa]] "to reside" ->
				 -- 1sg pres ind [[bý]]; [[hús]] "house" -> [[hýsa]] "to house"
	["ja"] = "i", -- un-u-mutated version of jö; occurs in a logical sense in several nouns
	-- ["ja"] = "e", -- [[gjalda]] -> 1sg pres ind [[geld]], [[gjalla]] -> 1sg pres ind [[gell]], [[bjarga]] -> archaic
				  -- 1sg pres ind [[berg]]; need overrides
	-- ["já"] = "jæ", -- [[ljá]], [[tjá]] -> 1sg pres ind [[ljæ]], [[tjæ]]; this is automatic, hence not needed
	-- ["já"] = "é", -- [[sjá]] -> 1sg pres ind [[sé]]; irregular, needs explicit form
	-- ["já"] = "e", -- [[skjálfa]] -> 1sg pres ind [[skelf]]; irregular, needs explicit form
	["jö"] = "i", -- [[fjörður]] "fjord" -> dat sg [[firði]], nom pl [[firðir]]
	-- ["jö"] = "é", -- [[stjölur]] "rump, hind part (obsolete)" -> dat sg [[stéli]], nom pl [[stélir]]; needs explicit
				  -- vowel
	["jó"] = "ý", -- [[bjóða]] "to offer" -> 1sg pres ind [[býð]]; [[ljós]] "light" -> [[lýsa]] "to illuminate"
	-- ["ju"] = "y", -- [[við]] [[bjuggum]] "we lived" -> subjunctive [[við]] [[byggjum]]; in fact, the subjunctive
				  -- of these verbs has either y or jy or both; we should stick with expected jy
	["jú"] = "ý", -- [[ljúga]] "to lie" -> 1sg pres ind [[lýg]]
	["au"] = "ey", -- [[ausa]] "to dip, to scoop" -> 1sg pres ind [[eys]]; [[aumur]] "wretched" -> [[eymd]]
				   -- "wretchedness"
}

local i_mutation = {}
for k, v in pairs(lc_i_mutation) do
	i_mutation[k] = v
	i_mutation[ucap(k)] = ucap(v)
end

local lc_reverse_i_mutation = {
	["æ"] = "á", -- [[hættur]] nom pl "bedtime, quitting time" dat pl [[háttum]]; [[ær]] "ewe" acc/dat sg [[á]]
	["e"] = "a", -- [[ketill]] "kettle" dat sg [[katli]]; [[Egill]] (male given name) dat sg [[Agli]];
				 -- [[telja]] "to count" past ind [[taldi]]
	-- not i -> e; [[skilja]] "to understand" past ind [[skildi]]
	["ý"] = "ú", -- [[kýr]] "cow" acc/dat sg [[kú]]; [[gnýja]] "to storm, to rage" past ind [[gnúði]]
	["y"] = "u", -- [[mylja]] "to crush" past ind [[muldi]]
	-- not ey -> au; [[deyja]] "to die" past ind [[deyði]]
}

local reverse_i_mutation = {}
for k, v in pairs(lc_reverse_i_mutation) do
	reverse_i_mutation[k] = v
	reverse_i_mutation[ucap(k)] = ucap(v)
end

-- Apply i-mutation to the last vowel of `stem`, maybe excluding suffixal -ur. If `newv` is given, use that vowel (for
-- cases like [[sonur]] "son" nom pl [[synir]] but [[hnot]] "nut; small ball of yarn" nom pl [[hnetur]]); otherwise use
-- the appropriate default vowel. If `exclude_final_ur`, act as if suffixal -ur isn't present and mutate the previous
-- vowel. If `error_if_unmatchable`, throw an error if we are unable to mutate the vowel.
function export.apply_i_mutation(stem, newv, exclude_final_ur, error_if_unmatchable)
	if newv then
		track("i-mutation-newv")
	end
	local modstem
	local function subfunc(origv, post)
		return (newv or i_mutation[origv] or origv) .. post
	end
	stem = apply_au_ey_ur_sub(stem, false, false, exclude_final_ur)
	modstem = rsub(stem, "([Aa]u)(" .. C .. "*)$", subfunc)
	if modstem ~= stem then
		return undo_au_ey_ur_sub(modstem)
	end
	modstem = rsub(stem, "([Jj][aáoöóuú])(" .. C .. "*)$", subfunc)
	if modstem ~= stem then
		return undo_au_ey_ur_sub(modstem)
	end
	modstem = rsub(stem, "([aáoöóuúAÁOÖÓUÚ])(" .. C .. "*)$", subfunc)
	if modstem ~= stem then
		return undo_au_ey_ur_sub(modstem)
	end
	stem = undo_au_ey_ur_sub(stem)
	if error_if_unmatchable then
		error(("Stem '%s' does not contain an i-mutatable vowel as its last vowel"):format(stem))
	end
	return stem
end


-- Apply reverse i-mutation to the last vowel of `stem`, maybe excluding suffixal -ur. If `newv` is given, use that
-- vowel; otherwise use the appropriate default vowel. If `exclude_final_ur`, act as if suffixal -ur isn't present and
-- unmutate the previous vowel. If `error_if_unmatchable`, throw an error if we are unable to unmutate the vowel.
function export.apply_reverse_i_mutation(stem, newv, exclude_final_ur, error_if_unmatchable)
	if newv then
		track("reverse-i-mutation-newv")
	end
	local modstem
	local function subfunc(origv, post)
		return (newv or reverse_i_mutation[origv] or origv) .. post
	end
	stem = apply_au_ey_ur_sub(stem, false, true, exclude_final_ur)
	-- We need to include -i- even though we don't have a default mapping for it, to allow e.g. for nom pl Vestfirðir ->
	-- gen pl Vestfjarða.
	modstem = rsub(stem, "([æeiýyÆEIÝY])(" .. C .. "*)$", subfunc)
	if modstem ~= stem then
		return undo_au_ey_ur_sub(modstem)
	end
	stem = undo_au_ey_ur_sub(stem)
	if error_if_unmatchable then
		error(("Stem '%s' does not contain a reversible i-mutated vowel as its last vowel"):format(stem))
	end
	return stem
end


local lesser_u_mutation = {
	["a"] = "ö",
	["A"] = "Ö",
}

local lesser_reverse_u_mutation = {
	["ö"] = "a",
	["Ö"] = "A",
}

local greater_u_mutation = {
	["a"] = "u",
	["A"] = "U", -- FIXME, may not occur
}

local greater_reverse_u_mutation = {
	["u"] = "a",
	["U"] = "A", -- FIXME, may not occur
}

-- Apply u-mutation to `stem`, maybe excluding suffixal -ur. `typ` is the type of u-mutation:
-- * "umut" (mutate the last vowel if possible, with a -> ö);
-- * "Umut" (mutate the last vowel if possible, with a -> u);
-- * "uumut" (mutate the last two vowels if possible, with a -> ö in the second-to-last and a -> ö in the last);
-- * "uUmut" (mutate the last two vowels if possible, with a -> ö in the second-to-last and a -> u in the last);
-- * "uUUmut" (mutate the last three vowels if possible, with a -> ö in the third-to-last and a -> u in the last and
--             second-to-last; needed in superlatives of past-participle-derived adjectives like [[saltaður]] "salty"
--             with superlative [[saltaðastur]] whose nominative feminine singular is [[söltuðust]]);
-- * "u_mut" (mutate the second-to-last vowel if possible, with a -> ö, leaving alone the last vowel).
-- If `exclude_final_ur`, act as if suffixal -ur isn't present and mutate the previous vowel.
-- If `error_if_unmatchable`, throw an error if we are unable to mutate the vowel.
function export.apply_u_mutation(stem, typ, exclude_final_ur, error_if_unmatchable)
	local origstem = stem
	stem = apply_au_ey_ur_sub(stem, true, false, exclude_final_ur)
	if typ == "uUUmut" then
		local first, v1, mid1, v2, mid2, v3, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)(" .. V .. ")(" ..
			C .. "*)(" .. V .. ")(" .. C .. "*)$")
		if first then
			v1 = lesser_u_mutation[v1] or v1
		elseif stem:sub(1, 1) ~= "-" then
			if error_if_unmatchable then
				error(("Can't apply u-mutation of type '%s' because stem '%s' doesn't have three syllables"):
					format(typ, origstem))
			end
			return undo_au_ey_ur_sub(stem)
		else
			first, v2, mid2, v3, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)(" ..  V .. ")(" .. C .. "*)$")
			if not first then
				if error_if_unmatchable then
					error(("Can't apply u-mutation of type '%s' because suffix stem '%s' doesn't have two syllables"):
						format(typ, origstem))
				end
				return undo_au_ey_ur_sub(stem)
			end
			v1 = ""
			mid1 = ""
		end
		v2 = greater_u_mutation[v2] or v2
		v3 = greater_u_mutation[v3] or v3
		local retval = undo_au_ey_ur_sub(first .. v1 .. mid1 .. v2 .. mid2 .. v3 .. last)
		if retval == origstem and error_if_unmatchable then
			error(("Can't apply u-mutation of type '%s' to stem '%s'; result would be the same as the original"):
				format(typ, origstem))
		end
		return retval
	end
	if typ == "uUmut" or typ == "uumut" or typ == "u_mut" then
		local first, v1, middle, v2, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)(" .. V .. ")(" .. C .. "*)$")
		if first then
			v1 = lesser_u_mutation[v1] or v1
		elseif stem:sub(1, 1) ~= "-" then
			if error_if_unmatchable then
				error(("Can't apply u-mutation of type '%s' because stem '%s' doesn't have two syllables"):
					format(typ, origstem))
			end
			return undo_au_ey_ur_sub(stem)
		else
			first, v2, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)$")
			if not first then
				if error_if_unmatchable then
					error(("Can't apply u-mutation of type '%s' because suffix stem '%s' doesn't have even one syllable"):
						format(typ, origstem))
				end
				return undo_au_ey_ur_sub(stem)
			end
			v1 = ""
			middle = ""
		end
		v2 = typ == "u_mut" and v2 or (typ == "uUmut" and greater_u_mutation or lesser_u_mutation)[v2] or v2
		local retval = undo_au_ey_ur_sub(first .. v1 .. middle .. v2 .. last)
		if retval == origstem and error_if_unmatchable then
			error(("Can't apply u-mutation of type '%s' to stem '%s'; result would be the same as the original"):
				format(typ, origstem))
		end
		return retval
	end
	if typ ~= "umut" and typ ~= "Umut" then
		error(("Internal error: For stem '%s', saw unrecognized u-mutation type '%s'"):format(origstem, typ))
	end
	local first, v, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)$")
	if not first then
		if error_if_unmatchable then
			error(("Can't apply u-mutation of type '%s' because stem '%s' doesn't have a vowel"):format(typ, origstem))
		end
		return undo_au_ey_ur_sub(stem)
	end
	v = (typ == "Umut" and greater_u_mutation or lesser_u_mutation)[v] or v
	local retval = undo_au_ey_ur_sub(first .. v .. last)
	if retval == origstem and error_if_unmatchable then
		error(("Can't apply u-mutation of type '%s' to stem '%s'; result would be the same as the original"):
			format(typ, origstem))
	end
	return retval
end


-- Apply reverse u-mutation to `stem`, maybe excluding suffixal -ur. `typ` is the type of u-mutation:
-- * "unumut" (unmutate the last vowel if possible, with ö -> a);
-- * "unUmut" (unmutate the last vowel if possible, with u -> a);
-- * "unuumut" (unmutate the last two vowels if possible, with ö -> a in the second-to-last and ö -> a in the last);
-- * "unuUmut" (unmutate the last two vowels if possible, with ö -> a in the second-to-last and u -> a in the last);
-- * "unuUUmut" (unmutate the last three vowels if possible, with ö -> a in the third-to-last and u -> a in the last and
--			     second-to-last; needed, at least theoretically, in declining adjective-noun multiword terms where the
--			     adjective is an inflected-form superlative of a past-participle-derived adjective such as [[söltuðust]]
--			     "saltiest", nominative feminine singular of [[saltaðastur]]);
-- * "unu_mut" (unmutate the second-to-last vowel if possible, with ö -> a, leaving alone the last vowel).
-- If `exclude_final_ur`, act as if suffixal -ur isn't present and unmutate the previous vowel.
-- If `error_if_unmatchable`, throw an error if we are unable to unmutate the vowel.
function export.apply_reverse_u_mutation(stem, typ, exclude_final_ur, error_if_unmatchable)
	local origstem = stem
	stem = apply_au_ey_ur_sub(stem, true, false, exclude_final_ur)
	if typ == "unuUUmut" then
		local first, v1, mid1, v2, mid2, v3, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)(" .. V .. ")(" ..
			C .. "*)(" .. V .. ")(" .. C .. "*)$")
		if first then
			v1 = lesser_reverse_u_mutation[v1] or v1
		elseif stem:sub(1, 1) ~= "-" then
			if error_if_unmatchable then
				error(("Can't apply reverse u-mutation of type '%s' because stem '%s' doesn't have three syllables"):
					format(typ, origstem))
			end
			return undo_au_ey_ur_sub(stem)
		else
			first, v2, mid2, v3, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)(" ..  V .. ")(" .. C .. "*)$")
			if not first then
				if error_if_unmatchable then
					error(("Can't apply reverse u-mutation of type '%s' because suffix stem '%s' doesn't have two syllables"):
						format(typ, origstem))
				end
				return undo_au_ey_ur_sub(stem)
			end
			v1 = ""
			mid1 = ""
		end
		v2 = greater_reverse_u_mutation[v2] or v2
		v3 = greater_reverse_u_mutation[v3] or v3
		local retval = undo_au_ey_ur_sub(first .. v1 .. mid1 .. v2 .. mid2 .. v3 .. last)
		if retval == origstem and error_if_unmatchable then
			error(("Can't apply reverse u-mutation of type '%s' to stem '%s'; result would be the same as the original"):
				format(typ, origstem))
		end
		return retval
	end
	if typ == "unuumut" or typ == "unuUmut" or typ == "unu_mut" then
		local first, v1, middle, v2, last =
			rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)(" ..  V .. ")(" .. C .. "*)$")
		if not first then
			if error_if_unmatchable then
				error(("Can't apply reverse u-mutation of type '%s' because stem '%s' doesn't have two syllables"):
					format(typ, origstem))
			end
			return undo_au_ey_ur_sub(stem)
		end
		v1 = lesser_reverse_u_mutation[v1] or v1
		v2 = typ == "unu_mut" and v2 or (typ == "unuUmut" and greater_reverse_u_mutation or lesser_reverse_u_mutation)[v2] or v2
		local retval = undo_au_ey_ur_sub(first .. v1 .. middle .. v2 .. last)
		if retval == origstem and error_if_unmatchable then
			error(("Can't apply reverse u-mutation of type '%s' to stem '%s'; result would be the same as the original"):
				format(typ, origstem))
		end
		return retval
	end
	if typ ~= "unumut" and typ ~= "unUmut" then
		error(("Internal error: For stem '%s', saw unrecognized reverse u-mutation type '%s'"):format(origstem, typ))
	end
	local first, v, last = rmatch(stem, "^(.*)(" .. V .. ")(" .. C .. "*)$")
	if not first then
		if error_if_unmatchable then
			error(("Can't apply reverse u-mutation of type '%s' because stem '%s' doesn't have a vowel"):
				format(typ, origstem))
		end
		return undo_au_ey_ur_sub(stem)
	end
	v = (typ == "unUmut" and greater_reverse_u_mutation or lesser_reverse_u_mutation)[v] or v
	local retval = undo_au_ey_ur_sub(first .. v .. last)
	if retval == origstem and error_if_unmatchable then
		error(("Can't apply reverse u-mutation of type '%s' to stem '%s'; result would be the same as the original"):
			format(typ, origstem))
	end
	return retval
end


-- Apply contraction to `stem`. Throw an error if the stem can't be contracted.
function export.apply_contraction(stem)
	-- Contraction only applies when the last vowel is a/i/u and followed by a single consonant. There are restrictions
	-- on what the consonant can be but I'm not sure exactly what they are; r/l/n/ð are all possible (cf. [[hamar]],
	-- [[megin]], [[höfuð]], [[þumall]], where in the last case the final -l is the nominative singular ending).
	local butlast, last = rmatch(stem, "^(.*" .. C .. ")[aiu](" .. C .. ")$")
	if not butlast then
		error(("Contraction cannot be applied to stem '%s' because it doesn't end in a/i/u preceded by a consonant and followed by a single consonant"
			):format(stem))
	end
	return butlast .. last
end


-- Add a dental ending (d/t/ð) to `stem`.
function export.add_dental_ending(stem)
	if stem:match("[lmn]$") then
		-- [[talinn]] "counted" -> tald-; [[framinn]] "performed" -> framd-; [[hruninn]] "fallen down/in" -> hrund-
		return stem .. "d"
	elseif stem:match("ð$") then
		-- I dunno if this ever happens.
		return usub(stem, 1, -2) .. "dd"
	elseif stem:match("[pkt]$") then
		-- [[glapinn]] "confused" -> glapt-; [[lukinn]] "(en)closed" -> lukt-; no examples with -t-
		return stem .. "t"
	end
	-- [[vafinn]] "wrapped" -> vafð-; [[varinn]] "defended" -> varð-; [[tugginn]] "chewed" -> tuggð- (or tuggn-);
	-- [[spúinn]] "vomited" -> spúð-
	return stem .. "ð"
end


-- Parse off and return a final -ur or -r nominative ending. Return the portion before the ending as well as the ending
-- itself. If the lemma ends in -aur, only the -r is stripped off. This is used by ## and by the `@l` scraping
-- indicator (so that e.g. `@r` when applied to a compound of [[réttur]] "law; court; course (of a meal)" won't get
-- confused by the final -r).
function export.parse_off_final_nom_ending(lemma)
	local lemma_minus_r, final_nom_ending
	if lemma:match("[^Aa]ur$") then
		lemma_minus_r, final_nom_ending = lemma:match("^(.*)(ur)$")
	elseif lemma:sub(-1) == "r" then
		lemma_minus_r, final_nom_ending = lemma:match("^(.*)(r)$")
	else
		lemma_minus_r, final_nom_ending = lemma, ""
	end
	return lemma_minus_r, final_nom_ending
end


-- Replace # and ## with `val`, substituting `lemma` as necessary (possibly without final -r or -ur).
function export.replace_hashvals(val, lemma)
	if not val then
		return val
	elseif val:find("##", nil, true) then
		local lemma_minus_r = export.parse_off_final_nom_ending(lemma)
		val = val:gsub("##", replacement_escape(lemma_minus_r))
	end
	return (val:gsub("#", replacement_escape(lemma)))
end
	

--[==[
Find the inflection spec by scraping the contents of the Icelandic section of `data.lemma`, looking for `data.infltemp`
calls (where the template is e.g. {"is-ndecl"}, {"is-adecl"} or {"is-conj"}). If `inflid` is given, it must match the
value of the {{para|id}} param specified to the inflection template; otherwise, the inflection template must not have an
{{para|id}} param. If anything goes wrong in the process, a string is returned describing the error message; otherwise a
table of inflections is returned, each containing a field `infl` with the inflection spec. The inflection spec comes
from the {{para|deriv}}, {{para|deriv2}}, etc. params in the inflection template if specified and `data.is_deriv` is
given; otherwise from {{para|1}}, {{para|2}}, etc. If `data.allow_empty_infl` is given, a missing inflection spec in
{{para|1}} is allowed and converted to an empty string; otherwise, an error string is returned.
]==]
function export.scrape_inflection(data)
	local retval = require(inflection_utilities_module).scrape_inflection {
		langname = "Tiếng Iceland",
		lemma = data.lemma,
		infltemp = data.infltemp,
		inflid = data.inflid,
		idparam = "id",
	}
	if type(retval) == "string" then
		return retval
	end
	local args = retval.template:get_arguments()
	local infls = {}
	if data.is_deriv and args.deriv then
		local i = 1
		while true do
			local deriv_param = "deriv" .. (i == 1 and "" or tostring(i))
			if args[deriv_param] then
				table.insert(infls, {infl = args[deriv_param]})
				i = i + 1
			else
				break
			end
		end
	elseif not args[1] and not data.allow_empty_infl then
		return ("For Icelandic base lemma '[[%s]]', saw no inflection spec in 1="):format(data.lemma)
	else
		local i = 1
		while true do
			if args[i] or (i == 1 and data.allow_empty_infl) then
				table.insert(infls, {infl = args[i] or ""})
				i = i + 1
			else
				break
			end
		end
	end
	return {infls = infls, pos = args.pos}
end


--[==[
Find the appropriate inflection template for a given lemma by ''scraping'', i.e. fetching the template from a page
where it is already given. There are two types of inflection scraping: ''direct scraping'' and
''related-lemma scraping''. ''Direct scraping'' is the simpler case of directly scraping the inflection of a specified
lemma, and occurs in two circumstances: ''self-scraping'' (scraping the inflection from elsewhere on the same page of
the lemma in question) and ''multiword scraping'' (scraping the inflection of a single word, or sometimes a multiword
subportion, of a multiword lemma). Self-scraping typically occurs in the headword of a lemma, where it is desirable to
scrape the inflection from the corresponding ==Inflection==, ==Conjugation== or ==Declension== section, rather than
duplicating it. For example, most Icelandic nouns have their headword specified as {{tl|is-noun|@@}}, where specs
beginning with `@` are scraping specs and `@@` specifically indicates direct scraping (in this case, self-scraping, i.e.
looking for the inflection specified in an {{tl|is-ndecl}} call elsewhere on the same page). Multiword scraping
typically occurs in the inflection call of a multiword expression; for example, the Icelandic idiom
{{m|is|almenn skynsemi|lit=common sense}} has its declension defined as {{tl|is-ndecl|almenn<adj> skynsemi<@@>}},
which says to inflect {{m|is||almenn}} (the feminine of {{m|is|almennur||common, general, universal}}) as an adjective
and inflect {{m|is|skynsemi||sense, reason}} by scraping its declension from the page on which it is defined.

''Related-lemma scraping'' means using the inflection of a related word, typically a suffixal component of the lemma in
question, to specify the lemma's inflection. For example, the lemma {{m|is|ljósabekkur||sunbed, tanning bed}} is a
compound of {{m|is|ljós||light}} and {{m|is|bekkur||bench}}, and inflects the same as {{m|is|bekkur}}, so we can
scrape the inflection of {{m|is|bekkur}} and use it to specify the inflection of {{m|is|ljósabekkur}} and other
similar compounds rather than copying the inflection of the base word to each compound. (This is comparable to using
{{m|en|tooth}} to define the inflection of {{m|en|sawtooth}}, {{m|en|foretooth}}, {{m|en|dogtooth}}, etc., or using
{{m|en|draw}} to define the inflection of {{m|en|withdraw}}, {{m|en|overdraw}}, and the like.) In this case, the
inflection of {{m|is|ljósabekkur}} will typically be given as {{tl|is-ndecl|@b}}, meaning to scrape the inflection of
the portion of the lemma beginning with ''b''. Sometimes more than one letter needs to be given; for example, to
scrape the inflection of {{m|en|sawtooth}} from {{m|en|tooth}}, we'd have to use `@to`, as `@t` would wrongly try to
look up the inflection of {{m|en|th}}.

There is one param, an object with the following fields:
* `lemma`: The lemma whose inflection is to be determined by scraping.
* `scrape_spec`: The spec indicating how the ''base lemma'' (the actual lemma whose inflection is to be scraped) is
  constructed. This is normally the part of the spec following the initial `@` in the template call. A value of {"@"}
  indicates ''direct scraping''' (see above), i.e. the value of `lemma` is used as the base lemma. Any other value
  indicates ''related-lemma scraping'' (see above). The base lemma will be constructed by fetching the rightmost
  suffix beginning with the letter(s) of `scrape_spec`, forming the ''lemma suffix'', which is either used directly
  as the base lemma or modified slightly (if `scrape_is_suffix` or `scrape_is_uppercase` is given).
* `scrape_is_suffix`: If specified, the base lemma is constructed from the lemma suffix by prefixing with {"-"}, i.e.
  the lemma whose inflection is to be scraped is a suffix, such as {{m|is|-son}}.
* `scrape_is_uppercase`: If specified, the base lemma is constructed from the lemma suffix by uppercasing it. This is
  used e.g. to scrape the inflection of {{m|is|Björn}} for use in constructing the inflection of {{m|is|Aðalbjörn}}.
* `infltemp`: A string specifying the name of the template containing the inflections.
* `allow_empty_infl`: If specified, a missing inflection spec in {{para|1}} is allowed and converted to an empty string;
  otherwise, an error string is returned.
* `inflid`: The inflection ID that must match the value of the `idparam` parameter specified by the inflection template.
  If not specified, there must be exactly one matching inflection template present, and it must not have a value given
  for the `idparam` parameter. Otherwise, all matching inflection templates must have a value specified for the
  `idparam` parameter of the template, and there must be exactly one template whose `idparam` parameter value is the
  same as the value of `inflid`.

The return value is an object with the following fields:
* `prefix`: The found prefix, as specified above.
* `base_lemma`: The found base lemma, as specified above.
* `infl`: The inflections; same as is returned by `scrape_inflection`. {nil} if no matching template could be found (in
  which case `error` will be populated).
* `errmsg`: If no matching inflection template could be found, this will be a string describing what went wrong. For
  example, if inflection templates were found but with incorrect ID's, the ID's actually found will be listed.
]==]
function export.find_inflection_given_scrape_spec(data)
	local lemma, scrape_spec, scrape_is_suffix, scrape_is_uppercase, infltemp, allow_empty_infl, inflid =
		data.lemma, data.scrape_spec, data.scrape_is_suffix, data.scrape_is_uppercase, data.infltemp,
		data.allow_empty_infl, data.inflid
	local prefix, base_lemma
	if scrape_spec == "@" then -- @@ specified
		base_lemma = lemma
		prefix = ""
	else
		local lemma_minus_ending, final_ending = data.parse_off_ending(lemma)
		prefix, base_lemma = rmatch(lemma_minus_ending, "^(.*)(" .. pattern_escape(scrape_spec) .. ".-)$")
		if not prefix then
			error(("Can't determine base lemma to scrape given lemma '%s' and scraping spec '@%s'; scraping spec not " ..
				"found in lemma"):format(lemma, scrape_spec))
		end
		base_lemma = base_lemma .. final_ending
	end
	if scrape_is_uppercase then
		local base_first, base_rest = rmatch(base_lemma, "^(.)(.*)$")
		if not base_first then
			error(("Internal error: Something wrong, couldn't match a single character in %s"):format(dump(base_lemma)))
		end
		base_lemma = uupper(base_first) .. base_rest
	end
	if scrape_is_suffix then
		base_lemma = "-" .. base_lemma
	end
	local infl = export.scrape_inflection {
		lemma = base_lemma,
		infltemp = infltemp,
		allow_empty_infl = allow_empty_infl,
		is_deriv = true,
		inflid = inflid
	}
	local errmsg = nil
	if type(infl) == "table" then
		infl = infl.infls
		if infl[2] then
			errmsg = ("For Icelandic base lemma '[[%s]]', saw %s inflection specs; currently, can only handle one"):
				format(base_lemma, #infl)
		else
			infl = infl[1]
			local argspec = infl.infl
			if argspec:find("<", nil, true) then
				errmsg = ("For Icelandic base lemma '[[%s]]', saw explicit angle bracket spec in inflection, likely " ..
					"indicating a multiword inflection; can't handle yet: %s"):format(lemma, argspec)
			elseif argspec:find("((", nil, true) then
				errmsg = ("For Icelandic base lemma '[[%s]]', saw alternant specs; can't handle yet: %s"):
					format(lemma, argspec)
			end
		end
		if errmsg then
			infl = nil
		end
	else
		errmsg = infl
		infl = nil
	end
	return {
		prefix = prefix,
		base_lemma = base_lemma,
		infl = infl,
		errmsg = errmsg,
	}
end


return export