This module calculates the expected Mandarin and Cantonese reflexes of Middle Chinese pronunciations.
local export = {}
local fully_voiced_initial = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local partially_voiced_initial = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local fricativisation_inducing = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local m_lenition_inducing = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local devoicing_outcomes = {
= "p",
= "t",
= "c",
= "k",
= "ch",
= "ch",
}
local function has(str, index, ...)
if index ~= 0 then
str = mw.ustring.sub(str, index, index)
end
for i, v in ipairs{...} do
if str == v then
return true
end
end
return false
end
local initial_outcomes_cmn = {
= "b", = "p", = "b", = "m",
= "d", = "t", = "d", = "n",
= "zh", = "ch", = "zh", = "n",
= "z", = "c", = "z", = "s", = "s",
= "zh", = "ch", = "zh", = "sh", = "sh",
= "zh", = "ch", = "sh", = "sh", = "sh",
= "g", = "k", = "g", = "h", = "h",
= "", = "", = "", = "",
= "l", = "r",
}
local devoicing_initial_cmn = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local palatalisation_outcomes_cmn = {
= "j",
= "q",
= "x",
= "j",
= "q",
= "x",
}
local final_simplification_outcomes_cmn = {
= "iu",
= "ui",
= "un",
= "ong",
}
local rhyme_outcomes_cmn = {
= "ueng",
= { "ueng", 3, "iong" },
= "u",
= { "u", 3, "ü" },
= "ueng",
= "u",
= { "ueng", 3, "iong" },
= { "u", 3, "ü" },
= { "uang", 3, "iang" },
= { "uo", 3, "üe" },
= { "er", 3, "i" },
= { "uei", 2, "uai" },
= { "er", 3, "i" },
= { "uei", 2, "uai" },
= { "er", 3, "i" },
= { "uei", 2, "uai" },
= { "er", 3, "i" },
= { "uei", 2, "uai" },
= { "er", 3, "i" },
= { "er", 3, "i" },
= { "uei", 2, "uai" },
= "ü",
= "u",
= "ü",
= "ai",
= "uei",
= "i",
= "uei",
= { "ai", 3, "ie" },
= "uai",
= { "ai", 3, "ia" },
= { "uai", 3, "ua" },
= { "ai", 3, "ie" },
= "uai",
= "i",
= { "uei", 2, "uai" },
= "i",
= { "uei", 2, "uai" },
= "i",
= "uei",
= "ai",
= "uei",
= "in",
= "in",
= "ün",
= "en",
= "ün",
= "i",
= "i",
= { "ü", 2, "uai" },
= "e",
= { "ü", 2, "uai" },
= { "uen", 3, "en" },
= "e",
= "uen",
= { "u", 1, "o" },
= "in",
= "i",
= { "uen", 3, "ün" },
= "ü",
= "an",
= "uan",
= { "a", 3, "e" },
= "uo",
= "ian",
= "üan",
= "ie",
= { "a", 3, "üe" },
= { "an", 3, "ian" },
= "uan",
= { "a", 3, "ia" },
= "ua",
= { "an", 3, "ian" },
= "uan",
= { "a", 3, "ia" },
= "ua",
= "ian",
= "üan",
= "ian",
= "üan",
= "ie",
= "üe",
= "ie",
= "üe",
= "ian",
= "üan",
= "ie",
= "üe",
= "ao",
= { "ao", 3, "iao" },
= "iao",
= "iao",
= "iao",
= { "uo", 3, "e" },
= "uo",
= "ie",
= "üe",
= { "a", 3, "ia" },
= "ua",
= "ie",
= "ang",
= "uang",
= { "uo", 3, "e" },
= "uo",
= { "iang", 2, "uang" },
= "uang",
= { "üe", 1, "o" },
= { "üe", 1, "o" },
= "eng",
= "ueng",
= "ing",
= "iong",
= "e",
= "uo",
= "i",
= "ü",
= "eng",
= "ueng",
= "e",
= "uo",
= "ing",
= "iong",
= "i",
= "ü",
= "ing",
= "iong",
= "i",
= "ü",
= "eng",
= "ueng",
= "e",
= "uo",
= "ing",
= { "i", 2, "e" },
= "ü",
= { "iou", 1, "ou" },
= "ou",
= { "iou", 1, "iao" },
= "in",
= "in",
= { "i", 2, "e" },
= { "i", 2, "e" },
= "an",
= { "a", 3, "e" },
= { "an", 3, "ian" },
= "uan",
= "ie",
= { "a", 3, "ia" },
= { "an", 3, "ian" },
= { "a", 3, "ia" },
= { "an", 3, "ian" },
= { "a", 3, "ia" },
= "ian",
= "ian",
= "ie",
= "ie",
= "ian",
= "ie",
= "an",
= { "a", 3, "e" },
}
local labial_openness_normalisation_cmn = {
= 11,
= 13,
= 15,
= 17,
= 20,
= 26,
= 27,
= 35,
= 37,
= 39,
= 64,
= 68,
= 106,
= 111,
= 114,
= 120,
= 121,
= 125,
= 132,
}
function export.predict_cmn(initial, final, tone)
local initial_result = initial_outcomes_cmn
if fricativisation_inducing and initial <= 3 then
initial_result = "f"
elseif m_lenition_inducing and initial == 4 then
initial_result = "w"
elseif tone == 1 and devoicing_initial_cmn then
initial_result = devoicing_outcomes
end
if initial <= 4 then
final = labial_openness_normalisation_cmn or final
end
local final_result = rhyme_outcomes_cmn
if type(final_result) == "table" then
if final_result == 1 and initial <= 4
or final_result == 2 and initial >= 18 and initial <= 22
or final_result == 3 and initial >= 28 and initial <= 36 then
final_result = final_result
else
final_result = final_result
end
end
if has(final_result, 1, "i", "ü") then
initial_result = palatalisation_outcomes_cmn or initial_result
end
if final_result == "er" then
if initial_result == "r" then
initial_result = ""
else
final_result = "i"
end
end
if has(initial_result, 0, "zh", "ch", "sh", "r") then
final_result = mw.ustring.gsub(final_result, "^in", "en")
final_result = mw.ustring.gsub(final_result, "^i(.)", "%1")
final_result = mw.ustring.gsub(final_result, "^üe$", "uo")
end
if initial <= 4 then
final_result = mw.ustring.gsub(final_result, "^u(.)", "%1")
if has(initial_result, 0, "f", "w") then
final_result = mw.ustring.gsub(final_result, "^(.)", "%1")
final_result = mw.ustring.gsub(final_result, "^i$", "ei")
else
final_result = mw.ustring.gsub(final_result, "^ü", "i")
end
end
if has(initial_result, 0, "n", "l") and has(final_result, 0, "ua", "uai", "uang", "uei") then
final_result = mw.ustring.gsub(final_result, "^u", "")
end
if has(final_result, 1, "ü") and
not (has(initial_result, 0, "n", "l") and has(final_result, 0, "ü", "üe")) then
if initial_result == "" then
initial_result = "y"
end
final_result = mw.ustring.gsub(final_result, "^ü", "u")
end
if initial_result == "" then
if has(final_result, 1, "i") then
initial_result = "y"
end
if has(final_result, 1, "u") then
initial_result = "w"
end
if initial_result ~= "" then
final_result = mw.ustring.gsub(final_result, "^.()", "%1")
end
end
final_result = final_simplification_outcomes_cmn or final_result
local tone_result
if tone == 1 then
if fully_voiced_initial or partially_voiced_initial then
tone_result = 2
else
tone_result = 1
end
elseif tone == 2 then
if fully_voiced_initial then
tone_result = 4
else
tone_result = 3
end
elseif tone == 3 then
tone_result = 4
else
if fully_voiced_initial then
tone_result = 2
elseif partially_voiced_initial then
tone_result = 4
else
tone_result = ""
end
end
return initial_result .. final_result .. tone_result
end
local initial_outcomes_yue = {
= "b", = "p", = "b", = "m",
= "d", = "t", = "d", = "n",
= "z", = "c", = "z", = "n",
= "z", = "c", = "z", = "s", = "z",
= "z", = "c", = "z", = "s", = "z",
= "z", = "c", = "s", = "s", = "s",
= "g", = "k", = "g", = "ng", = "h",
= "h", = "", = "", = "",
= "l", = "j",
}
local devoicing_initial_yue = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local initial_class_yue = {
= 1,
= 1,
= 1,
= 1,
= 2,
= 2,
= 2,
= 2,
= 3,
= 3,
= 3,
= 2,
= 4,
= 4,
= 4,
= 4,
= 4,
= 5,
= 5,
= 5,
= 5,
= 5,
= 6,
= 6,
= 6,
= 6,
= 6,
= 7,
= 7,
= 7,
= 8,
= 7,
= 7,
= 8,
= 8,
= 8,
= 2,
= 6,
}
local final_class_yue = {
= 20,
= 20,
= 20,
= 20,
= 20,
= 20,
= 20,
= 20,
= 34,
= 34,
= 35,
= 21,
= 35,
= 21,
= 35,
= 21,
= 35,
= 21,
= 35,
= 35,
= 21,
= 36,
= 24,
= 36,
= 39,
= 28,
= 6,
= 6,
= 2,
= 33,
= 2,
= 33,
= 2,
= 33,
= 6,
= 21,
= 6,
= 21,
= 6,
= 6,
= 17,
= 25,
= 8,
= 8,
= 22,
= 8,
= 22,
= 8,
= 8,
= 22,
= 8,
= 22,
= 8,
= 8,
= 38,
= 38,
= 8,
= 8,
= 8,
= 8,
= 31,
= 32,
= 31,
= 32,
= 26,
= 27,
= 26,
= 27,
= 4,
= 4,
= 4,
= 4,
= 4,
= 4,
= 4,
= 4,
= 13,
= 30,
= 13,
= 30,
= 13,
= 30,
= 13,
= 30,
= 13,
= 30,
= 13,
= 30,
= 19,
= 5,
= 15,
= 15,
= 15,
= 16,
= 16,
= 11,
= 29,
= 1,
= 1,
= 11,
= 18,
= 18,
= 18,
= 18,
= 37,
= 18,
= 37,
= 18,
= 9,
= 9,
= 40,
= 40,
= 9,
= 9,
= 14,
= 14,
= 9,
= 9,
= 9,
= 9,
= 14,
= 14,
= 14,
= 14,
= 14,
= 14,
= 14,
= 14,
= 9,
= 9,
= 9,
= 9,
= 14,
= 14,
= 14,
= 10,
= 10,
= 10,
= 7,
= 7,
= 7,
= 7,
= 23,
= 23,
= 12,
= 3,
= 12,
= 3,
= 3,
= 3,
= 3,
= 3,
= 12,
= 12,
= 12,
= 12,
= 12,
= 12,
= 23,
= 23,
}
local rhyme_outcomes_yue = {
= "A",
= "Ai",
= "Am",
= "An",
= "Au",
= "ai",
= "am",
= "an",
= "aN",
= "au",
= "e",
= "im",
= "in",
= "IN",
= "iu",
= "o",
= "oi",
= "oN",
= "ou",
= "UN",
= { "ei", "Ei", "ai" },
= { "En", "En", "an" },
= { "Am", "Am", "om" },
= { "ou", "ou", "u" },
= { "ui", "Ei", "ui" },
= { "An", "in", "in" },
= { "An", "Yn", "Yn" },
= { "ai", "Ei", "ui" },
= { "e", "O", "O" },
= { "in", "Yn", "Yn" },
= { "un", "An", "on" },
= { "un", "Yn", "un" },
= { "Ai", "Ei", "Ai" },
= { "oN", "oN", "oN" },
= { "ei", "ei", "i", "i", "i", "i", "ei", "i" },
= { "u", "Ei", "Y", "Ei", "o", "Y", "Ei", "Y" },
= { "oN", "ON", "ON", "ON", "oN", "ON", "ON", "ON" },
= { "un", "En", "En", "Yn", "Yn", "Yn", "an", "an" },
= { "ui", "Ai", "Ai", "oi", "Ai", "Ai", "oi", "oi" },
= { "IN", "IN", "IN", "IN", "aN", "IN", "IN", "IN" },
}
local palatalisation_inducing_yue = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local labialisation_inducing_yue = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local glide_inducing_yue = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
local debuccalisation_inducing_yue = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
function export.predict_yue(initial, final, tone)
local initial_result = initial_outcomes_yue
if fricativisation_inducing and initial <= 3 then
initial_result = "f"
elseif tone == 1 and devoicing_initial_yue then
initial_result = devoicing_outcomes
elseif initial == 17 and labialisation_inducing_yue then
initial_result = "s"
end
if initial == 29 and debuccalisation_inducing_yue then
initial_result = "h"
elseif initial == 29 and (final == 136 or final >= 139 and final <= 142)
or initial == 32 and (final == 136 or final == 57)
or initial == 33 and (labialisation_inducing_yue or glide_inducing_yue)
or initial_result == "" and palatalisation_inducing_yue then
initial_result = "j"
end
local final_result
if (final == 36 or final == 38) and initial == 36 then
final_result = "Ei"
elseif (final == 26 or final == 42) and initial == 31 then
final_result = "oi"
elseif final == 23 and initial == 31 then
final_result = ""
elseif (final == 22 or final == 24) and initial == 4 then
final_result = "ou"
else
local final_class = final_class_yue
final_result = rhyme_outcomes_yue
if final_class >= 35 then
final_result = final_result]
elseif final_class >= 21 then
final_result = final_result + 8) / 5)]
end
end
if initial == 31 and mw.ustring.find(final_result, "^") ~= nil then
initial_result = "j"
end
if labialisation_inducing_yue and mw.ustring.find(final_result, "^") == nil then
if initial_result == "h" and mw.ustring.find(final_result, "^") == nil then
initial_result = "f"
elseif initial_result == "j" or initial_result == "" then
initial_result = "w"
elseif initial_result == "g" and mw.ustring.find(final_result, "^") == nil then
initial_result = "gw"
elseif initial_result == "k" and mw.ustring.find(final_result, "^") == nil then
initial_result = "kw"
end
end
local tone_result
if tone == 4 then
if fully_voiced_initial or partially_voiced_initial then
tone_result = 6
elseif mw.ustring.find(final_result, "^") ~= nil then
tone_result = 3
else
tone_result = 1
end
elseif tone == 2 and fully_voiced_initial then
tone_result = 6
elseif fully_voiced_initial or partially_voiced_initial then
tone_result = 3 + tone
else
tone_result = tone
end
final_result = mw.ustring.gsub(final_result, "om", "am")
if initial <= 4 then
final_result = mw.ustring.gsub(final_result, "m$", "n")
end
if tone == 4 then
final_result = mw.ustring.gsub(final_result, "m$", "p")
final_result = mw.ustring.gsub(final_result, "n$", "t")
final_result = mw.ustring.gsub(final_result, "N$", "k")
end
final_result = mw.ustring.gsub(final_result, "A", "aa")
final_result = mw.ustring.gsub(final_result, "I", "i")
final_result = mw.ustring.gsub(final_result, "U", "u")
final_result = mw.ustring.gsub(final_result, "O", "oe")
final_result = mw.ustring.gsub(final_result, "E", "eo")
final_result = mw.ustring.gsub(final_result, "Y", "yu")
final_result = mw.ustring.gsub(final_result, "N", "ng")
return initial_result .. final_result .. "<sup>" .. tone_result .. "</sup>"
end
return export