Implements Template:pl-pronunciation; relies on Module:pl-IPA for IPA generation.


local export = {}

local langcode = "pl"
local lang = require("Module:languages").getByCode(langcode)

local m_IPA = require("Module:IPA")
local m_pl_IPA = require("Module:pl-IPA")

local vowels = "aeiouyąęó"
local vowel = "[" .. vowels .. "]"
local consonants = "bcćdfghjklłmnńpqrsśtuvwxyzźż"
local consonant = "[" .. consonants .. "]"
-- vowel digraphs, not necessarily actual phonetic diphthongs
local diphthong_i_v2 = "[aąoeęuói]"
local diphthongs = {
	["a"] = "u",
	["e"] = "u",
	["i"] = diphthong_i_v2
}
-- consonant digraphs (key = first letter, value = possible second letters)
local digraphs = {
	["c"] = "[hz]",
	["d"] = "[zźż]",
	["q"] = "u",
	["r"] = "z",
	["s"] = "z",
}

local past_tense_suffixes = {
	"liśmy", "liście", "łyśmy", "łyście",
}

local latin_borrowing_suffixes = {
	"ika", "yka",
	"iki", "yki",
	"ika", "yka",
	"ice", "yce",
	"ikom", "ykom",
	"ikę", "ykę",
	"iką", "yką",
	"ice", "yce",
	"ikach", "ykach",
	"iko", "yko",
}

-- if this is changed, the next two functions also need to be
local function is_respelling_close_enough(respelling, word)
	word = mw.ustring.gsub(word, "j(" .. diphthong_i_v2 .. ")", "i%1")
	respelling = mw.ustring.gsub(respelling, "['.]", "")
	respelling = mw.ustring.gsub(respelling, "j(" .. diphthong_i_v2 .. ")", "i%1")
	return word == respelling
end

local function partition(word, oword)
	local parts = {}
	local lenword = mw.ustring.len(word)
	local pos = 1
	local offset = 0
	word = mw.ustring.gsub(word, "['-]", ".")
	while pos <= lenword do
		if mw.ustring.find(mw.ustring.lower(word), "^" .. vowel, pos) then
			local initial = mw.ustring.sub(mw.ustring.lower(word), pos, pos)
			local seq = 1
			if diphthongs[initial] and mw.ustring.find(mw.ustring.lower(word), "^" .. initial .. diphthongs[initial], pos) then
				seq = 2
			end
			table.insert(parts, { "v", mw.ustring.sub(oword, pos - offset, pos - offset + seq - 1) })
			pos = pos + seq
		elseif mw.ustring.find(mw.ustring.lower(word), "^" .. consonant, pos) then
			local initial = mw.ustring.sub(mw.ustring.lower(word), pos, pos)
			local seq = 1
			if digraphs[initial] and mw.ustring.find(mw.ustring.lower(word), "^" .. initial .. digraphs[initial], pos) then
				seq = 2
			end
			table.insert(parts, { "c", mw.ustring.sub(oword, pos - offset, pos - offset + seq - 1) })
			pos = pos + seq
		elseif mw.ustring.find(word, "^% ", pos) then
			-- multiword, do not hyphenate
			return nil
		elseif mw.ustring.find(word, "^%.", pos) then
			-- syllable break
			if not mw.ustring.find(oword, "^['-]", pos - offset) then
				offset = offset + 1
			end
			table.insert(parts, { "b", nil })
			pos = pos + 1
		else
			-- unrecognized symbol
			return nil
		end
	end
	return parts
end

local function get_word_suffix(word)
	word = word:gsub("([ˈ'.,ˌ])", "")
	local word_suffix = 0
	for i,v in ipairs(past_tense_suffixes) do
		if word:sub(-string.len(v)) == v 
		then
			word_suffix = 1
		end
	end
	for i,v in ipairs(latin_borrowing_suffixes) do
		if word:sub(-string.len(v)) == v 
		then
			word_suffix = 2
		end
	end
	
	return word_suffix
end

function export.generate_hyphenation(word, otitle)
	local syllables = {}
	local cursyl = ""
	local nucleus = false
	local coda = nil
	local pos = 1
	parts = partition(word, otitle)
	if not parts then return nil end
	for pos, p in ipairs(parts) do
		local kind, part = unpack(p)
		if kind == "v" then
			if coda then
				cursyl = cursyl .. mw.ustring.sub(syllables[#syllables], -coda)
				syllables[#syllables] = mw.ustring.sub(syllables[#syllables], 1, -coda - 1)
				coda = nil
			end
			if nucleus then
				table.insert(syllables, cursyl)
				cursyl = ""
			end
			nucleus = true
			coda = nil
			cursyl = cursyl .. part
		elseif kind == "c" then
			cursyl = cursyl .. part
			if nucleus then
				table.insert(syllables, cursyl)
				cursyl = ""
				nucleus = false
				coda = mw.ustring.len(part)
			else
				coda = nil
			end
		elseif kind == "b" then
			-- implicit syllable break
			if #cursyl > 0 then
				if nucleus or #syllables < 1 then
					table.insert(syllables, cursyl)
				else
					syllables[#syllables] = syllables[#syllables] .. cursyl
				end
			end
			cursyl = ""
			nucleus = false
			coda = nil
		else
			-- unrecognized kind
			return nil
		end
	end
	if #cursyl > 0 then
		if nucleus or #syllables < 1 then
			table.insert(syllables, cursyl)
		else
			syllables[#syllables] = syllables[#syllables] .. cursyl
		end
	end
	return syllables
end

local ipavowel = "[aɛiɨɔu]"
function export.generate_rhyme(ipa)
	local vowels_at = { }
	local pos = 1
	while true do
		local posnext = mw.ustring.find(ipa, ipavowel, pos)
		if not posnext then break end
		table.insert(vowels_at, posnext)
		pos = posnext + 1
	end
	local vend
	if #vowels_at < 1 then return nil end
	if #vowels_at > 1 then
		vend = vowels_at[#vowels_at - 1]
	else
		vend = vowels_at[#vowels_at]
	end
	local snippet = mw.ustring.sub(ipa, vend)
	snippet = mw.ustring.gsub(snippet, "[ˈˌ.]", "")
	if mw.ustring.find(snippet, " ") then
		return nil -- copout, something must be wrong
	end
	return snippet
end

function export.show(frame)
	local args = require "Module:parameters".process(frame:getParent().args, {
		[1] = { list = true },
		
		["ipa"] = { list = true, default = nil, allow_holes = true },
		["qual"] = { list = true, allow_holes = true },
		["n"] = { list = true, allow_holes = true },
		["h"] = { list = true, allow_holes = true }, ["hyphen"] = {},
		["r"] = { list = true, allow_holes = true }, ["rhymes"] = {},
		["a"] = { list = true, default = nil }, ["audio"] = {},
		["ac"] = { list = true, default = nil }, ["caption"] = {},
		["hh"] = { default = "" }, ["homophones"] = {},
		
		["q"] = { list = true, default = nil, allow_holes = true },
		["hp"] = { list = true, default = nil, allow_holes = true },
		["rp"] = { list = true, default = nil, allow_holes = true },
		["hhp"] = { list = true, default = nil, allow_holes = true },
			
		["nohyphen"] = { type = "boolean", default = false },
		["norhymes"] = { type = "boolean", default = false },
		["fs"] = { type = "boolean" }, ["fixstress"] = {},
		
		["title"] = { default = nil }, -- for debugging or demonstration only
	})

	local words, transcriptions, transcriptions_raw
	local lines = {}
	local categories = {}
	local actual = args["title"] or mw.title.getCurrentTitle().text
	if next(args[1]) ~= nil then
		words = args[1]
	else
		words = { actual }
	end
	local multiword = mw.ustring.find(words[1], " ")
	local hyphenations = args["h"]
	local rhymes = args["r"]
	local ipa = args["ipa"]
	if #ipa < 1 then ipa = nil end
	local qualifiers = args["q"]
	if not qualifiers or qualifiers.maxindex < 1 then qualifiers = args["qual"] end
	local hyphlabels = args["hp"]
	local rhymlabels = args["rp"]
	local nohyphen = args["nohyphen"]
	local norhymes = args["norhymes"]
	local fixstress = args["fs"]
	if args["fixstress"] then fixstress = args["fixstress"] end
	local homophones = mw.text.split(args["hh"], ",")
	local homophonelabels = args["hhp"]
	if #homophones == 1 and homophones[1] == "" then homophones = {} end
	local audio = {}
	local audios = args["a"]
	local captions = args["ac"]
	
	word_suffix = 0
	if not ipa and #words == 1 then
		-- 0 - normal word
		-- 1 - past tense verb stressed antepenultimately
		-- 2 - Latin borrowing stressed antepenultimately
		word_suffix = get_word_suffix(words[1])
	end
	if not (fixstress or (fixstress == nil and word_suffix == 1)) then
		word_suffix = 0
	end
		
	if args["hyphen"] then hyphenation[1] = args["hyphen"] end
	if args["rhymes"] then rhymes[1] = args["rhymes"] end
	if args["homophones"] then homophones = mw.text.split(args["homophones"], ",") end
	if args["audio"] then audios[1] = args["audio"] end
	if args["captions"] then captions[1] = args["caption"] end
	local respelling_ok = true
	for i, w in ipairs(words) do
		if not is_respelling_close_enough(w, actual) then
			respelling_ok = false
			break
		end
	end

	for i, audiofile in ipairs(audios) do
		if audiofile then
			table.insert(audio, {file = audiofile, caption = captions[i] or "Audio"})
		end
	end
	if #hyphenations == 1 and hyphenations[1] == "-" then
		nohyphen = true
	end
	if #rhymes == 1 and rhymes[1] == "-" then
		norhymes = true
	end

	if word_suffix == 0 then
		if ipa then
			transcriptions = {}
			transcriptions_raw = {}
			for i = 1, #ipa do
				local qual = qualifiers[i]
				table.insert(transcriptions, {
					pron = ipa[i],
					qualifiers = qual and { qual } or nil,
					note = args.n[i]
				})
			end
		else
			transcriptions = {}
			transcriptions_raw = {}
			for i = 1, #words do
				local qual = qualifiers[i]
				local ipaconv = m_pl_IPA.convert_to_IPA(words[i])
				table.insert(transcriptions_raw, ipaconv)
				table.insert(transcriptions, {
					pron = "/" .. ipaconv .. "/",
					qualifiers = qual and { qual } or nil,
					note = args.n[i]
				})
			end
		end
		table.insert(lines, "* " .. m_IPA.format_IPA_full(lang, transcriptions))
	else
		transcriptions = {}
		transcriptions2 = {}
		transcriptions_raw = {}
		if word_suffix == 1 then
			qualifier1 = {"prescriptive standard; rarely used"}
			qualifier2 = {"colloquial; overall more common"}
		elseif word_suffix == 2 then
			qualifier1 = {"standard"}
			qualifier2 = {"colloquial; common in casual speech"}
		end
		local ipaconv = m_pl_IPA.convert_to_IPA(words[1])
		table.insert(transcriptions_raw, ipaconv)
		ipaconv_syllables = mw.text.split(ipaconv, "([ˈ.])")
		for j, syl in ipairs(ipaconv_syllables) do
			if j == (#ipaconv_syllables - 2) then
				ipaconv_syllables[j] = "ˈ" .. syl
			elseif j ~= 1 then
				ipaconv_syllables[j] = "." .. syl
			end
		end
		ipacov_fixed_stress = table.concat(ipaconv_syllables)
		table.insert(transcriptions, {
			pron = "/" .. ipacov_fixed_stress .. "/",
			qualifiers = qualifier1,
		})
		table.insert(lines, "* " .. m_IPA.format_IPA_full(lang, transcriptions))
		table.insert(transcriptions2, {
			pron = "/" .. ipaconv .. "/",
			qualifiers = qualifier2,
		})
		table.insert(lines, "* " .. m_IPA.format_IPA_full(lang, transcriptions2))
	end
	
	for i, a in ipairs(audio) do
		table.insert(lines, "* " .. frame:expandTemplate{title = "audio", args = {langcode, a["file"], a["caption"]}})
	end

	if not ipa and #hyphenations < 1 and respelling_ok and not multiword then
		local autohyph = export.generate_hyphenation(words[1], actual)
		if autohyph then
			table.insert(hyphenations, autohyph)
		end
	elseif #hyphenations >= 1 then
		local newhyphenations = {}
		for i, h in ipairs(hyphenations) do
			local t = {}
			for x in mw.text.gsplit(h, "[.]") do
				table.insert(t, x)	
			end
			newhyphenations[i] = t
		end
		hyphenations = newhyphenations
	end

	if not norhymes then
		if not ipa and #rhymes < 1 and #transcriptions_raw > 0 then
			local autorhyme = export.generate_rhyme(transcriptions_raw[1])
			if autorhyme then
				table.insert(rhymes, autorhyme)
			end
		end
	
		if #rhymes > 0 then
			-- merge rhymes if they have identical labels
			local last_label = false
			local new_rhymes = {}
			local new_labels = {}
			local current_list = {}
			
			for i, r in ipairs(rhymes) do
				local label = rhymlabels[i]
				if last_label == label then
					table.insert(current_list, r)
				else
					if #current_list > 0 then
						table.insert(new_rhymes, current_list)
					end
					if last_label ~= false then
						table.insert(new_labels, last_label)
					end
					current_list = { r }
					last_label = label
				end
			end
			
			table.insert(new_rhymes, current_list)
			table.insert(new_labels, last_label)
			rhymes = new_rhymes
			rhymlabels = new_labels
		end
		
		for i, r in ipairs(rhymes) do
			local label = ""
			if rhymlabels[i] then
				label = " " .. require("Module:qualifier").format_qualifier(rhymlabels[i])
			end
			if #r >= 1 then
				local sylkeys = {}
				local sylcounts = {}
				-- get all possible syllable counts from syllabifications
				for i, h in ipairs(hyphenations) do
					local hl = #h
					if hl > 0 and not sylkeys[hl] then
						table.insert(sylcounts, hl)
						sylkeys[hl] = true
					end
				end
				local rhymeobjs = {}
				for _, rhyme in ipairs(r) do
					table.insert(rhymeobjs, {rhyme = rhyme})
				end
				table.insert(lines, "* " .. require("မေႃႇၵျူး:rhymes").format_rhymes(
					{ lang = lang, rhymes = rhymeobjs, num_syl = sylcounts }) .. label)
			end
		end
	end

	if not nohyphen then
		if #transcriptions > 0 and #hyphenations > 0 then
			local syl_IPA = require("Module:syllables").getVowels(transcriptions[1].pron, lang)
			local syl_hyph = #hyphenations[1]
			if syl_IPA and syl_IPA ~= syl_hyph then
				table.insert(categories, "pl-pronunciation syllable count mismatch")
			end
		end
		
		if not actual:find("[ %.]") and #hyphenations < 1 then
			table.insert(categories, "pl-pronunciation without hyphenation")
		end
		
		for i, h in ipairs(hyphenations) do
			local label = ""
			if hyphlabels[i] then
				label = " " .. require("Module:qualifier").format_qualifier(hyphlabels[i])
			end
			table.insert(lines, "* Syllabification: " .. require("Module:links").full_link({lang = lang, alt = table.concat(h, "‧"), tr = "-"}) .. label)
		end
	end

	if #homophones > 0 then
		local homophone_param = {langcode}
		for i, h in ipairs(homophones) do
			table.insert(homophone_param, h)
			if homophonelabels[i] then
				homophone_param["q" .. i] = homophonelabels[i]
			end
		end
		table.insert(lines, "* " .. frame:expandTemplate{title = "homophones", args = homophone_param})
	end
	
	return table.concat(lines, "\n") .. require("Module:utilities/format_categories")(categories, lang)
end

return export