--[=[
Based on ]
FIXME. Only make_plural has been adapted.
]=]
local export = {}
local romut_module = "Module:romance utilities"
local u = require("Module:string/char")
local rsplit = mw.text.split
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rsubn = mw.ustring.gsub
local TEMPC1 = u(0xFFF1)
local TEMPC2 = u(0xFFF2)
local TEMPV1 = u(0xFFF3)
local DIV = u(0xFFF4)
local vowel = "aeiouáéíóú" .. TEMPV1
local V = ""
local AV = "" -- accented vowel
local W = "" -- glide
local C = ""
export.vowel = vowel
export.V = V
export.AV = AV
export.W = W
export.C = C
local remove_accent = {
= "a", = "e", = "i", = "o", = "u", = "y"
}
local add_accent = {
= "á", = "é", = "í", = "ó", = "ú", = "ý"
}
export.remove_accent = remove_accent
export.add_accent = add_accent
local prepositions = {
"al? ",
"del? ",
"como ",
"con ",
"en ",
"para ",
"por ",
}
-- 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
export.rsub = rsub
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
export.rsub_repeatedly = rsub_repeatedly
-- Apply vowel alternation to stem.
function export.apply_vowel_alternation(stem, alternation)
local ret, err
-- Treat final -gu, -qu as a consonant, so the previous vowel can alternate (e.g. conseguir -> consigo).
stem = rsub(stem, "()u$", "%1" .. TEMPC1)
local before_last_vowel, last_vowel, after_last_vowel = rmatch(stem, "^(.*)(" .. V .. ")(.-)$")
if alternation == "uo" or alternation == "uô" then
if last_vowel == "o" then
ret = before_last_vowel .. alternation .. after_last_vowel
else
err = "should have -o- as the last vowel"
end
elseif alternation == "ei" or alternation == "éi" then
if last_vowel == "e" then
ret = before_last_vowel .. alternation .. after_last_vowel
else
err = "should have -e- as the last vowel"
end
elseif alternation == "a-e" or alternation == "a-é" then
local alt_vowel = (alternation == "a-e") and "e" or "é"
if last_vowel == "a" then
ret = before_last_vowel .. alt_vowel .. after_last_vowel
else
err = "should have -a- as the last vowel"
end
elseif alternation == "o-e" or alternation == "o-é" then
local alt_vowel = (alternation == "o-e") and "e" or "é"
if last_vowel == "o" then
ret = before_last_vowel .. alt_vowel .. after_last_vowel
else
err = "should have -o- as the last vowel"
end
elseif alternation == "a-i" or alternation == "a-í" then
local alt_vowel = (alternation == "a-i") and "i" or "í"
if last_vowel == "a" then
ret = before_last_vowel .. alt_vowel .. after_last_vowel
else
err = "should have -a- as the last vowel"
end
elseif alternation == "e-i" or alternation == "e-í" then
local alt_vowel = (alternation == "e-i") and "i" or "í"
if last_vowel == "e" then
ret = before_last_vowel .. alt_vowel .. after_last_vowel
else
err = "should have -e- as the last vowel"
end
--fixme
elseif alternation == "i" or alternation == "í" then
if last_vowel == "e" then
ret = before_last_vowel .. alternation .. after_last_vowel
else
err = "should have -e- as the last vowel"
end
else
error("Unrecognized vowel alternation '" .. alternation .. "'")
end
ret = ret and ret:gsub(TEMPC1, "u") or nil
return {ret = ret, err = err}
end
-- Syllabify a word. This implements the full syllabification algorithm, based on the corresponding code
-- in ]. This is more than is needed for the purpose of this module, which doesn't
-- care so much about syllable boundaries, but won't hurt.
function export.syllabify(word)
word = DIV .. word .. DIV
-- gu/qu + front vowel; make sure we treat the u as a consonant; a following
-- i should not be treated as a consonant (] would become ''álguienes''
-- if pluralized)
word = rsub(word, "()u()", "%1" .. TEMPC2 .. "%2")
local vowel_to_glide = { = TEMPC1, = TEMPC2 }
-- i and u between vowels should behave like consonants (], ], ],
-- ], ], etc.)
word = rsub_repeatedly(word, "(" .. V .. ")()(" .. V .. ")",
function(v1, iu, v2) return v1 .. vowel_to_glide .. v2 end
)
-- y between consonants or after a consonant at the end of the word should behave like a vowel
-- (], ], ], ], etc.)
word = rsub_repeatedly(word, "(" .. C .. ")y(" .. C .. ")",
function(c1, c2) return c1 .. TEMPV1 .. c2 end
)
word = rsub_repeatedly(word, "(" .. V .. ")(" .. C .. W .. "?" .. V .. ")", "%1.%2")
word = rsub_repeatedly(word, "(" .. V .. C .. ")(" .. C .. V .. ")", "%1.%2")
word = rsub_repeatedly(word, "(" .. V .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
word = rsub(word, "()%.()", ".%1%2")
word = rsub_repeatedly(word, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeo, or stressed iu, should be syllabically divided from a following aeo or stressed iu.
word = rsub_repeatedly(word, "()()", "%1.%2")
word = rsub_repeatedly(word, "()()", "%1.%2")
word = rsub_repeatedly(word, "()()", "%1.%2")
word = rsub(word, "()", {
= "",
= "i",
= "u",
= "y",
})
return rsplit(word, "%.")
end
-- Return the index of the (last) stressed syllable.
function export.stressed_syllable(syllables)
-- If a syllable is stressed, return it.
for i = #syllables, 1, -1 do
if rfind(syllables, AV) then
return i
end
end
-- Monosyllabic words are stressed on that syllable.
if #syllables == 1 then
return 1
end
local i = #syllables
-- Unaccented words ending in a vowel or a vowel + s/n are stressed on the preceding syllable.
if rfind(syllables, V .. "?$") then
return i - 1
end
-- Remaining words are stressed on the last syllable.
return i
end
-- Add an accent to the appropriate vowel in a syllable, if not already accented.
function export.add_accent_to_syllable(syllable)
-- Don't do anything if syllable already stressed.
if rfind(syllable, AV) then
return syllable
end
-- Prefer to accent an a/e/o in case of a diphthong or triphthong (the first one if for some reason
-- there are multiple, which should not occur with the standard syllabification algorithm);
-- otherwise, do the last i or u in case of a diphthong ui or iu.
if rfind(syllable, "") then
return rsub(syllable, "^(.-)()", function(prev, v) return prev .. add_accent end)
end
return rsub(syllable, "^(.*)()", function(prev, v) return prev .. add_accent end)
end
-- Remove any accent from a syllable.
function export.remove_accent_from_syllable(syllable)
return rsub(syllable, AV, remove_accent)
end
function export.mark_alt(alt)
if alt == "uo" then return "uô"
elseif alt == "i" then return "í"
elseif alt == "a-e" then return "a-é"
elseif alt == "a-i" then return "a-í"
elseif alt == "o-e" then return "o-é"
elseif alt == "e-i" then return "e-í"
elseif alt == "ei" then return "éi"
else error("Unrecognized vowel alternant '" .. alt .. "'") end
end
function export.stress_last(word)
local stressed = { a = "á", e = "é", i = "í", o = "ó", u = "ú" }
-- Try to find a diphthong at the end of the word
if word:match("()*$") then
return word:gsub("()()(*)$", function(v1, v2, rest)
return stressed .. v2 .. rest
end)
end
-- Else, stress the last vowel
return word:gsub("()(*)$", function(vowel, rest)
local stressed = { a = "á", e = "é", i = "í", o = "ó", u = "ú" }
return stressed .. rest
end)
end
-- Return true if an accent is needed on syllable number `sylno` if that syllable were to receive the stress,
-- given the syllables of a word. The current accent may be on any syllable.
function export.accent_needed(syllables, sylno)
-- Diphthongs iu and ui are normally stressed on the second vowel, so if the accent is on the first vowel,
-- it's needed.
if rfind(syllables, "íu") or rfind(syllables, "úi") then
return true
end
-- If the default-stressed syllable is different from `sylno`, accent is needed.
local unaccented_syllables = {}
for _, syl in ipairs(syllables) do
table.insert(unaccented_syllables, export.remove_accent_from_syllable(syl))
end
local would_be_stressed_syl = export.stressed_syllable(unaccented_syllables)
if would_be_stressed_syl ~= sylno then
return true
end
-- At this point, we know that the stress would by default go on `sylno`, given the syllabification in
-- `syllables`. Now we have to check for situations where removing the accent mark would result in a
-- different syllabification. For example, países -> `pa.i.ses` but removing the accent mark would lead
-- to `pai.ses`. Similarly, río -> `ri.o` but removing the accent mark would lead to single-syllable `rio`.
-- We need to check whether (a) the stress falls on an i or u; (b) in the absence of an accent mark, the
-- i or u would form a diphthong with a preceding or following vowel and the stress would be on that vowel.
-- The conditions are slightly different when dealing with preceding or following vowels because ui and ui
-- diphthongs are by default stressed on the second vowel. We also have to ignore h between the vowels.
local accented_syllable = export.add_accent_to_syllable(unaccented_syllables)
if sylno > 1 and rfind(unaccented_syllables, "$") and rfind(accented_syllable, "^h?") then
return true
end
if sylno < #syllables then
if rfind(accented_syllable, "í$") and rfind(unaccented_syllables, "^h?") or
rfind(accented_syllable, "ú$") and rfind(unaccented_syllables, "^h?") then
return true
end
end
return false
end
-- generate the plurals of nominals
function export.make_plural(form, special)
local retval = require(romut_module).handle_multiword(form, special,
function(term) return export.make_plural(term) end, prepositions)
if retval then
return retval
end
-- ends in stressed/unstressed vowel (stressed endings in -i/u are not marked)
if rfind(form, "$") then return {form .. "s"} end
-- ends in és
if rfind(form, "és$") then return {rsub(form, "és$", "es") .. "es"} end
-- ends in an unstressed vowel + ç (lhápeç - > lhápeç)
if rfind(form, AV .. ".*" .. V .. "ç$") then return {form} end
-- ends in a stressed vowel + ç
if rfind(form, V .. "ç$") then
return {rsub(form, "ç$", "zes")}
end
if rfind(form, V .. "$") then return {form .. "es"} end
return nil
end
function export.make_feminine(form, special)
local retval = require(romut_module).handle_multiword(form, special, export.make_feminine, prepositions)
if retval then
if #retval ~= 1 then
error("Internal error: Should have one return value for make_feminine: " .. table.concat(retval, ","))
end
return retval
end
if form:find("o$") then
local retval = form:gsub("o$", "a") -- discard second retval
return retval
end
if rfind(form, "és$") then
return rsub(form, "és$", "esa")
end
if rfind(form, "$") then
return form .. "a"
end
return form
end
function export.make_masculine(form, special)
local retval = require(romut_module).handle_multiword(form, special, export.make_masculine, prepositions)
if retval then
if #retval ~= 1 then
error("Internal error: Should have one return value for make_masculine: " .. table.concat(retval, ","))
end
return retval
end
if form:find("a$") then
local retval = form:gsub("a$", "o")
return retval
end
return form
end
return export