Module:IPAc2-mh explained

-- This module is primarily maintained at:-- https://en.wiktionary.org/wiki/Module:mh-pronunc-- Please direct all technical queries and contributions there.-- The version of this script on Wikipedia is only a mirror.

local export =

local MERGED_VOWELS = falselocal PARENTHETICAL_EPENTHESIS = truelocal PHONETIC_DETAILS = falselocal W_OFF_GLIDES = true

local ASYLL = "̯"local BREVE = "̆"local CEDILLA = "̧"local MACRON = "̄"local TIE = "͡"local TIE2 = "͜"

local C1_ = "pbtdSZszkgmnNrlyYhH_"local C1 = "["..C1_.."]"local C2_ = "jGw"local C = ".["..C2_.."]"local V_ = "aEeiAV7MQOou"local V = "["..V_.."]"local VI_ = V_.."I"local VI = "["..VI_.."]"local S = "[%s%-]+"

local UTF8_CHAR = "[%z\1-\127\194-\244][\128-\191]*"

local EMPTY =

-- Adds elements to a sequence as if it's a set (retains unique elements only).local function addUnique(seq, value) for _, value2 in pairs(seq) do if value

value2 then return end end seq[#seq + 1] = valueend

-- Intended to work the same as JavaScript's Object.assign function.local function assign(target, ...) local args = for _, source in pairs(args) do if type(source)

"table" then for key, value in pairs(source) do target[key] = value end end end return targetend

local function fastTrim(text) return string.match(text, "^%s*(.-)%s*$")end

local function parseBoolean(text) if type(text)

"string" then text = string.gsub(text, "[^0-9A-Za-z]", "") if text ~= "" and text ~= "0" and string.lower(text) ~= "false" then return true end end return falseend

local function splitChars(text, pattern, chars, shorten) chars = chars or local index = 1 for ch in string.gmatch(text, pattern or UTF8_CHAR) do chars[index] = ch index = index + 1 end if index <= #chars then if shorten then table.remove(chars, index) else repeat chars[index] = nil index = index + 1 until index > #chars end end return charsend

local function string_gsub2(text, pattern, subst) return string.gsub(string.gsub(text, pattern, subst), pattern, subst)end

local function tableGet(value, key1, key2, key3) if type(value) ~= "table" or key1

nil then return value end value = value[key1] if key2

nil then return value end if type(value) ~= "table" then return nil end value = value[key2] if key3

nil then return value end if type(value) ~= "table" then return nil end return value[key3]end

local function ZTBL(text, sep) local tbl = for key in mw.text.gsplit(text, sep or " ") do tbl[key] = true end return tblend

local PARSE_PSEUDO_GLIDE =

local PARSE_C_CH_CW =

local PARSE_REMAINING =

local function parse(code) local outSeq = code = mw.ustring.gsub(code, "%s+", " ") code = string.lower(code) for text in mw.text.gsplit(code, " *,[,]*") do text = fastTrim(text) if text ~= "" then local temp = string.gsub(text, "[abdeghijklmnprtwy_&'%- ]", "") if temp ~= "" then error("'"..code.."' contains unsupported characters: "..temp) end -- Recognize "y_", "h_", "w_", "_y", "_h", "_w" as pseudo-glides. text = string.gsub(text, "_*([hwy])_+", PARSE_PSEUDO_GLIDE) text = string.gsub(text, "_+([hwy])", PARSE_PSEUDO_GLIDE) if string.find(text, "_") then error("contains misplaced underscores: "..code) end -- a plain protected from dialect-specific reflexes text = string.gsub(text, "'i", "I") -- "yi'y" and "'yiy" sequences text = string.gsub(text, "('?)yi('*)y", function(aposA, aposB) if aposA ~= "" then -- "dwelling upon" i return "Z" elseif aposB ~= "" then -- "passing over lightly" i return "z" end end) -- Convert multigraphs to pseudo-X-SAMPA format. text = string.gsub(text, "[klmnr0]g?[hw]?", PARSE_C_CH_CW) if string.find(text, "g") then error("contains g that is not part of ng: "..code) end -- Convert remaining sequences to pseudo-X-SAMPA format. text = string.gsub(text, ".", PARSE_REMAINING) -- Enforce CVC, CVCVC, CVCCVC, etc. phonotactics, -- but allow VC, CV at affix boundaries -- where a vowel may link to another morpheme's consonant. temp = string.gsub(text, "[%s%-]+", "") if string.find(temp, "_..[jGw]") or string.find(temp, ".[jGw]_.") then error("pseudo-glides may not neighbor a consonant") end if string.find(temp, VI.."_."..VI) then error("pseudo-glides may only be at the beginning or end"..code) end if string.find(temp, VI..VI) then error("vowels must be separated by a consonant: "..code) end if string.find(temp, ".[jGw].[jGw].[jGw]") then error("each consonant cluster is limited to two: "..code) end if string.find(temp, ".[jGw].[jGw]$") then error("may not end with a consonant cluster: "..code) end string.gsub(temp, "^(.[jGw])(.[jGw])", function(consonX, consonY) if consonX ~= consonY then error("may only begin with single or geminated consonant: " ..code) end end) if text ~= "" then addUnique(outSeq, text) end end end return outSeq end

local BENDER_1968 = local BENDER_MED = assign(BENDER_1968,)local BENDER_MOD = assign(BENDER_MED,)local BENDER_DEFAULT = assign(BENDER_MOD,)local BENDER_MAPS =

local function toBender(inSeq, args) -- "1968" is from "Marshallese Phonology" (1968 by Byron W. Bender). -- "med" is from the Marshallese-English Dictionary (1976). -- "mod" is from the Marshallese-English Online Dictionary. -- "default" is the same as "mod" but with cedillas. local version = args and args.version local map = BENDER_MAPS[type(version) == "string" and string.lower(version) or "" ] or BENDER_DEFAULT local outSeq = for _, text in pairs(inSeq) do text = string.gsub(text, ".[jGw]?", map) addUnique(outSeq, text) end return outSeqend

local TO_MOD =

local function toMOD(text) text = mw.ustring.gsub(text, ".["..CEDILLA..MACRON.."]?", TO_MOD) return textend

local PHONEMIC_MAP = if false then assign(PHONEMIC_MAP,)endassign(PHONEMIC_MAP,)

local function toPhonemic(inSeq) local outSeq = for _, text in pairs(inSeq) do text = string.gsub(text, ".[jGw]?", PHONEMIC_MAP) addUnique(outSeq, text) end return outSeqend

local VOWEL =

local F1 = local F2_FRONT = 1local F2_BACK = 2local F2_ROUND = 3local F2 =

local FRONT_VOWEL = local BACK_VOWEL = local ROUND_VOWEL =

for f1, row in pairs(VOWEL) do local front = row[F2_FRONT] local back = row[F2_BACK] local round = row[F2_ROUND] for f2, vowel in pairs(row) do F1[vowel] = f1 F2[vowel] = f2 FRONT_VOWEL[vowel] = front BACK_VOWEL[vowel] = back ROUND_VOWEL[vowel] = round endend

local function maxF1(a, b, c) if c then return VOWEL[math.max(F1[a], F1[b], F1[c])][F2_FRONT] elseif b then return VOWEL[math.max(F1[a], F1[b])][F2_FRONT] else return FRONT_VOWEL[a] endend

local function toPhoneticDialect(text, config, isRalik) -- Morphemes can begin with geminated consonants, but spoken words cannot. text = string.gsub(text, "^(.[jGw])(*)%1(*)("..VI..")", function(conson, _, __, vowel) if conson

"hG" then if isRalik then return "hG"..vowel.._.."hG"..__..vowel else return "hG".._..__..vowel end else if isRalik then return "hj"..maxF1(vowel, "E")..conson.._..conson..__..vowel else return conson..maxF1(vowel, "E").._..conson..__..vowel end end end ) -- Initial sequences have special behavior. -- To block this in the template argument, use "'i" instead of "i". text = " "..text text = string.gsub(text, "([jGw])(*)(h[jw])(*)i(*)(h[jw])(*)("..VI..")", function(nonVowel, _, consonX, __, ___, consonY, ____, vowel) if consonY

"hw" then -- sequences if isRalik then -- Rālik becomes . consonX = "hj" end -- becomes in both dialects. return nonVowel.._..consonX..__.. "I"..___..consonY..____..consonY..vowel elseif consonX

"hj" then -- sequences if isRalik then -- "dwelling upon" i return nonVowel.._..__.."Yj"..___..____..vowel else -- "passing over lightly" i return nonVowel.._..__.."yj"..___..____..vowel end end end ) text = string.sub(text, 2) -- Restore protected, we won't be checking for it anymore. text = string.gsub(text, "I", "i") return text end

local IS_VOWEL = FRONT_VOWEL

local VOWEL_REFLEXif true then -- [f1] local aEei = local AEei = local AV7i = local AV7M = local AV7u = local AOou = local QOou = -- [F2[secondaryR]][f1] local _jv_X = local njv_X = local hjvtX = local hjvkX = local _Gv_X = local rGv_X = -- not currently used local hGv_X = local _wv_X = local rwv_X = local hwv_X = local hwvtX = -- [F2[secondaryL]][F2[secondaryR]][f1] local _Xv__ = local nXv__ = local rXv__ = local hXv__ = local hXvt_ = local hXvk_ = local hXvr_ = -- [primaryR][F2[secondaryL]][F2[secondaryR]][f1] local __vX_ = local n_vX_ = local r_vX_ = local h_vX_ = -- [primaryL][primaryR][F2[secondaryL]][F2[secondaryR]][f1] VOWEL_REFLEX = end

local CONSON_REFLEXif true then local map = for primary in mw.text.gsplit("ptkmnNrl", "") do local map2 = map[primary] if not map2 then map2 = map[primary] = map2 end map2["j"] = map2["j"] or primary map2["G"] = map2["G"] or primary map2["w"] = map2["w"] or primary end map["T"] = map["t"] map["J"] = map["n"] map["R"] = map["r"] map["L"] = map["l"] CONSON_REFLEX = mapend

local VOICED_PRIMARY = local VOICELESS_PRIMARY =

local PHONETIC_IPAif true then local map = if PHONETIC_DETAILS then assign(map,) end map["T"] = map["T"] or map["t"] map["D"] = map["D"] or map["d"] map["S"] = map["S"] or (map["T"]..map["s"]) map["Z"] = map["Z"] or (map["D"]..map["z"]) map["kG"] = map["kG"] or map["k"] map["gG"] = map["gG"] or map["g"] map["J"] = map["J"] or map["n"] map["NG"] = map["NG"] or map["N"] map["R"] = map["R"] or map["r"] map["L"] = map["L"] or map["l"] map["Hj"] = map["Hj"] or map["i"]..map["^"] local key for primary in mw.text.gsplit("pbBtdTDSZszkgmnJNrRlL_", "") do for secondary in mw.text.gsplit("jGw", "") do key = primary..secondary map[key] = map[key] or (map[primary]..map[secondary]) end end for vowel in mw.text.gsplit(V_, "") do key = vowel.."@" map[key] = map[key] or (map[vowel]..map["@"]) key = vowel.."^" map[key] = map[key] or (map[vowel]..map["^"]) end PHONETIC_IPA = mapend

local function toPhoneticRemainder(code, config, leftFlag, rightFlag) local text = code local chars, subst local diphthongs = config.diphthongs -- If the phrase begins or ends with a bare vowel -- and no pseudo-glide, display phrase up to five times -- with each of the different pseudo-glides and possible vowel reflexes. if IS_VOWEL[string.sub(text, 1, 1)] then text = "_j"..code toPhoneticRemainder(text, config, false, rightFlag) if not diphthongs then toPhoneticRemainder(text, config, true, rightFlag) end text = "_G"..code toPhoneticRemainder(text, config, false, rightFlag) if not diphthongs then toPhoneticRemainder(text, config, true, rightFlag) end text = "_w"..code toPhoneticRemainder(text, config, false, rightFlag) if not diphthongs then toPhoneticRemainder(text, config, true, rightFlag) end return end if IS_VOWEL[string.sub(text, -1)] then text = code.."_j" toPhoneticRemainder(text, config, leftFlag, false) if not diphthongs then toPhoneticRemainder(text, config, leftFlag, true) end text = code.."_G" toPhoneticRemainder(text, config, leftFlag, false) if not diphthongs then toPhoneticRemainder(text, config, leftFlag, true) end text = code.."_w" toPhoneticRemainder(text, config, leftFlag, false) if not diphthongs then toPhoneticRemainder(text, config, leftFlag, true) end return end local initialJ = config.initialJ local medialJ = config.medialJ local finalJ = config.finalJ local noHints = config.noHints local outSeq = config.outSeq local voice = config.voice if initialJ

"x" or medialJ

"x" or finalJ

"x" then local subSeq = config.outSeq = subSeq if initialJ

"x" then config.initialJ = "t" end if medialJ

"x" then config.medialJ = "t" end if finalJ

"x" then config.finalJ = "t" end toPhoneticRemainder(code, config) if initialJ

"x" then config.initialJ = "s" end if medialJ

"x" then config.medialJ = "s" end if finalJ

"x" then config.finalJ = "s" end toPhoneticRemainder(code, config) addUnique(outSeq, table.concat(subSeq, " ~ ")) config.outSeq = outSeq config.initialJ = initialJ config.medialJ = medialJ config.finalJ = finalJ return end -- Glides always trigger epenthesis, even neighboring other glides. text = string_gsub2(text, "([aEei])(*h)(.)(*)(h)%3(*)([aEei])", function(vowelL, _, secondary, __, primaryR, ___, vowelR) if secondary

"w" then primaryR = "H" end return (vowelL.._..secondary.. maxF1(vowelL, vowelR).."@".. __..primaryR..secondary..___..vowelR ) end ) text = string.gsub(text, "([aEei])(*)hG(*.[jGw])", "%1%2hG%1@%3") text = string.gsub(text, "(.[jGw])(*)hG(*)([aEei])", "%1%4@%2hG%3%4") text = string.gsub(text, "([aEei])(*)h(.)(*.[jGw])", "%1%2h%3%1@%4") text = string.gsub(text, "(.[jGw])(*)h(. *)([aEei])", "%1%4@%2h%3%4") text = string.gsub(text, "(.[jGw])(*[yY].)", "%1i@%2") -- Preserve these exceptionally stable clusters. text = string.gsub(text, "l([jG] *)tG", "l%1|tG") -- Unstable consonant clusters trigger epenthesis. -- Liquids before coronal obstruents. text = string.gsub(text, "([rl].)(*)t", "%1v%2t") -- Nasals and liquids after coronal obstruents. text = string.gsub(text, "t(.)(*[nrl])", "t%1v%2") -- Heterorganic clusters. -- Labial consonants neighboring coronal or dorsal consonants. text = string.gsub(text, "([pm].)(*[tnrlkN])", "%1v%2") -- Coronal consonants neighboring labial or dorsal consonants. text = string.gsub(text, "([tnrl].)(*[pmkN])", "%1v%2") -- Dorsal consonants neighboring labial or coronal consonants. text = string.gsub(text, "([kN].)(*[pmtnrl])", "%1v%2") -- Organic speech involves certain consonant cluster assimilations. -- Forward assimilation of rounded consonants. -- There is no rounded coronal obstruent. text = string.gsub(text, "(w *[^t])[jG]", "%1w") -- Backward assimilation of remaining secondary articulations. text = string.gsub(text, "[jGw](*.)([jGw])", "%2%1%2") -- Backward nasal assimilation of primary articulations. text = string.gsub(text, "[pkrl](. *)([mnN])", "%2%1%2") -- No longer need to protect exceptionally stable consonant clusters. text = string.gsub(text, "|", "") -- Give a vowel height to all epenthetic vowels that still lack one. text = string_gsub2(text, "(.)(*..)v(*.. *)(.)", function(vowelL, consonL, consonR, vowelR) return vowelL..consonL.. maxF1(vowelL, vowelR, "E").."@".. consonR..vowelR end ) -- Tag all vowels for next set of operations. text = string.gsub(text, "([aEei])", "/%1") -- There is no variation in the surface realizations of vowels -- between two identical secondary articulations. text = string_gsub2(text, "([jGw])(*)/([aEei])(@? *.)%1", function(secondary, _, vowel, infix) return (secondary.._..VOWEL[F1[vowel]][F2[secondary]].. infix..secondary ) end ) if diphthongs then text = string_gsub2(text, "(.)([jGw])(*)/([aEei])(@?)(*)(.)([jGw])", function(primaryL, secondaryL, _, vowel, epenth, __, primaryR, secondaryR ) local f1 = F1[vowel] return (primaryL..secondaryL.._.. VOWEL[f1][F2[secondaryL]]..epenth.."=".. VOWEL[f1][F2[secondaryR]]..epenth..__.. primaryR..secondaryR ) end ) else -- Vowels neighboring pseudo-glides. subst = function(primaryL, secondaryL, _, vowel, epenth, __, primaryR, secondaryR, flag ) local f2L = F2[secondaryL] local f2R = F2[secondaryR] local f2 if flag then f2 = math.max(f2L, f2R) else f2 = math.min(f2L, f2R) end return (primaryL..secondaryL.._.. VOWEL[F1[vowel]][f2]..epenth..__.. primaryR..secondaryR ) end text = string.gsub(text, "(_)([jGw])(*)/("..V..")(@?)(*)(.)([jGw])", function(a, b, c, d, e, f, g, h) return subst(a, b, c, d, e, f, g, h, leftFlag) end ) text = string.gsub(text, "(.)([jGw])(*)/("..V..")(@?)(*)(_)([jGw])", function(a, b, c, d, e, f, g, h) return subst(a, b, c, d, e, f, g, h, rightFlag) end ) -- Vowels between two non-glides have the most predictable reflexes. text = string_gsub2(text, "([ptkmnNrl])(.)(*)/([aEei])(@? *)([ptkmnNrl])(.)", function(primaryL, secondaryL, _, vowel, infix, primaryR, secondaryR ) return primaryL..secondaryL.._.. VOWEL_REFLEX[primaryL][primaryR] [F2[secondaryL]][F2[secondaryR]][F1[vowel]].. infix..primaryR..secondaryR end ) -- Exceptionally for the single word "rej". text = string.gsub(text, "^(rG *)([V7])(*tj)$", function(prefix, vowel, suffix) return prefix..FRONT_VOWEL[vowel]..suffix end ) -- Vowels always claim the secondary articulation -- of a neighboring back unrounded glide. text = string.gsub(text, "(hG *)/([aEei])", function(prefix, vowel) return prefix..BACK_VOWEL[vowel] end) text = string.gsub(text, "/([aEei])(@? *hG)", function(vowel, suffix) return BACK_VOWEL[vowel]..suffix end) -- Unless already claimed, epenthetic vowels after a glide -- always claim the secondary articulation to the left. text = string.gsub(text, "([hH])(.)(*)/([aEei])@", function(primaryL, secondaryL, _, vowel) return (primaryL..secondaryL.._.. VOWEL[F1[vowel]][F2[secondaryL]].."@" ) end ) -- Unless already claimed, vowels before a glide -- always claim the secondary articulation to the right. text = string.gsub(text, "/([aEei])(@?)(*[hHyY])(.)", function(vowel, epenth, primaryR, secondaryR) return (VOWEL[F1[vowel]][F2[secondaryR]]..epenth.. primaryR..secondaryR ) end ) -- For now, unless already claimed, vowels before a rounded consonant -- claim the secondary articulation to the right. text = string.gsub(text, "/([aEei])(@? *.w)", function(vowel, suffix) return ROUND_VOWEL[vowel]..suffix end) -- For now, unless already claimed, remaining vowels -- claim the secondary articulation to the left. text = string.gsub(text, "([jGw])(*)/([aEei])", function(secondaryL, _, vowel) return secondaryL.._..VOWEL[F1[vowel]][F2[secondaryL]] end ) -- Change certain vowels in a special environment from round to front. text = string_gsub2(text, "(hj *)([Oou])(*.w *"..V.." *h[jh])", function(prefix, vowel, suffix) return prefix..FRONT_VOWEL[vowel]..suffix end ) text = string.gsub(text, "(hj *)([Oou])(*)(.w)(*)("..V..")", function(prefix, vowelL, _, conson, __, vowelR) if conson ~= "hw" or F1[vowelL] ~= F1[vowelR] then return prefix..FRONT_VOWEL[vowelL].._..conson..__..vowelR end end ) text = string.gsub(text, "(hj *)([Oou])(*.w *.w)", function(prefix, vowel, suffix) return prefix..FRONT_VOWEL[vowel]..suffix end ) text = string.gsub(text, "(a@? *hj *)Q(*.w *"..V..")", "%1a%2") text = string.gsub(text, "(a@? *hj *)Q(*.w *.w)", "%1a%2") -- Tag certain glide-vowel-non-glide sequences for special reflexes. text = string.gsub(text, "([HyY][jw] *)("..V.." *[ptkmnNrl])", "%1/%2") text = string.gsub(text, "^ *(h[jw] *)("..V.." *[ptkmnNrl])", "%1/%2") text = string.gsub(text, "(@ *h[jw] *)("..V.." *[ptkmnNrl])", "%1/%2") text = string.gsub(text, "([EeiAV7MOou] *h[jw] *)([aAQ] *[ptkmnNrl])", "%1/%2") text = string.gsub(text, "([iMu] *hj *)([EeV7] *[kN]G)", "%1/%2") text = string.gsub(text, "(hj *[aEei]@? *hw *)("..V.." *[ptkmnNrl])", "%1/%2") -- Untag certain sequences, exempting them from special reflexes. text = string.gsub(text, "(hj *)/([aEei] *[knNrl]w)", "%1%2") -- Special reflexes. text = string.gsub(text, "([jw])(*)/("..V..")(*)(.)([jGw])", function(secondaryL, _, vowel, __, primaryR, secondaryR) return (secondaryL.._.. VOWEL_REFLEX["h"][primaryR] [F2[secondaryL]][F2[secondaryR]][F1[vowel]].. __..primaryR..secondaryR ) end ) -- Exceptional phrase-initial reflex. text = string.gsub(text, "^ *([Hh]j *)([V7])(*[kN]G)", function(prefix, vowel, suffix) return prefix..FRONT_VOWEL[vowel]..suffix end ) text = string.gsub(text, "^ *([Hh]w *)M(*tG)", "%1u%2") end -- Temporarily cancel epenthetic neighboring . text = string.gsub(text, "i@(*yj)", "%1") -- neighboring may now be demoted to . text = string.gsub(text, "([iMu]@? *)yj", "%1hj") text = string.gsub(text, "yj(*[iMu])", "hj%1") -- may now be demoted everywhere. text = string.gsub(text, "(i@ *)Yj", "%1hjihj") text = string.gsub(text, "Yj", "hjihji@hj") -- For the purposes of this template, -- surface all glides pronounced in isolation. text = string.gsub(text, "^ *h(.) *$", "H%1") if not diphthongs then -- Opportunistically front these vowels. text = string.gsub(text, "(hj *)([A7M])(*[kN]G *[kN]?G? *"..V..")", function(prefix, vowel, suffix) return prefix..FRONT_VOWEL[vowel]..suffix end ) -- Surface certain glides. text = string.gsub(text, "^ *h(w *[Oou])", "H%1") text = string.gsub(text, "h(w *[aEeiAV7M])", "H%1") text = string.gsub(text, "^ *h(j *[AV7MQOou])", "H%1") text = string.gsub(text, "([ptkmnNrl]..@ *)h(w *[Oou])", "%1H%2") text = string.gsub(text, "([ptkmnNrl]..@ *)h(j *"..V..")", "%1H%2") text = string.gsub(text, "([AV7MQOou]@? *)h(j *[AV7MQOou])", "%1H%2") text = string.gsub(text, "([aEeiAV7M])(@? *)hw(*)([QOou])", function(vowelL, infix, _, vowelR) if F1[vowelL] > F1[vowelR] then return vowelL..infix.."Hw".._..vowelR end end ) text = string.gsub(text, "([AV7MQOou])(@? *)hj(*)([aEei])", function(vowelL, infix, _, vowelR) if F1[vowelL] > F1[vowelR] then return vowelL..infix.."Hj".._..vowelR end end ) text = string.gsub(text, "([aEei])(@? *)hj(*)([AV7MQOou])", function(vowelL, infix, _, vowelR) if F1[vowelL] < F1[vowelR] then return vowelL..infix.."Hj".._..vowelR end end ) text = string.gsub(text, "("..V..")(*)h([jw]) *$", function(vowel, _, secondary) if F2[vowel] ~= F2[secondary] then return vowel.._.."H"..secondary end end ) -- Protect word-final epenthetic vowels after non-glides -- from the next operation. text = string.gsub(text, "([ptkmnNrl]."..V..")(@)", "%1/%2") -- De-epenthesize vowels if they still neighbor unsurfaced glides. text = string.gsub(text, "("..V..")@(*h.)", "%1%2") text = string.gsub(text, "(h. *"..V..")@", "%1") -- Adjust F1 of currently remaining epenthetic vowels. text = string_gsub2(text, "("..V..")(*.[jGw])(.)@(*.[jGw] *)("..V..")", function(vowelL, infixL, vowel, infixR, vowelR) return (vowelL..infixL.. VOWEL[F1[maxF1(vowelL, vowelR, "E")]][F2[vowel]].."/@".. infixR..vowelR ) end ) text = string.gsub(text, "/", "") end -- Delete all remaining unsurfaced glides. text = string.gsub(text, "h.", "") -- Surface realization for . text = string.gsub(text, "yj", "i^") if not diphthongs then -- Realization for surfaced . text = string_gsub2(text, "("..V.."?)(@?)(*)Hj(*)("..V.."?)", function(vowelL, epenthL, _, __, vowelR) if vowelL ~= "" then if vowelR ~= "" then if vowelL

vowelR and F2[vowelL]

F2_FRONT then return vowelL.._..__..vowelR else return (vowelL..epenthL.._.. maxF1(vowelL, vowelR, "E").."^"..__..vowelR ) end else return vowelL.._..epenthL..maxF1(vowelL, "E").."^"..__ end else if vowelR ~= "" then return _..maxF1(vowelR, "E").."^"..__..vowelR else return _.."i^"..__ end end end ) -- Flatten this epenthetic vowel and surfaced glide. text = string_gsub2(text, "([aAQ] *"..C..")E@(*)E%^(*)a", "%1a%2%3a") -- Collapse this epenthetic vowel and surfaced glide into a semi-vowel. text = string.gsub(text, "([aEei])@(*)%1%^", "%2%1^") end if MERGED_VOWELS then text = string.gsub(text, "[EO]", function(vowel) return VOWEL[F1[vowel] + 1][F2[vowel]] end) end chars = splitChars(text, ".") if not diphthongs then -- Geminate long vowels. local index = #chars repeat local ch = chars[index] local index2 = index - 1 if IS_VOWEL[ch] then local ch2 = chars[index + 1] if ch2 ~= "@" and ch2 ~= "^" and chars[index2]

ch then chars[index] = ":" end end index = index2 until index

1 text = table.concat(chars, "") end -- Tweak remaining consonants, using offsets as a guide. text = string.gsub(text, "(.)([jGw])(*)([ptkmnNrl]?)([jGw]?)", function(offsetL, primaryL, secondaryL, _, primaryR, secondaryR, offsetR ) local isInitial = offsetL

1 local isFinal = offsetR

#chars + 1 if primaryL

"H" or primaryL

"y" then return primaryL..secondaryL.._ end if primaryL

"_" then if noHints then -- Delete pseudo-glide. return _ end if isInitial then -- Show secondary articulation to the left, not the right. return secondaryL..primaryL.._ end return primaryL..secondaryL.._ end local geminated = primaryL

primaryR if primaryL ~= "t" and primaryR

"t" then -- /tʲ/ is palatalized postalveolar. -- /tˠ/ is velarized dental. -- /nʲ, rʲ, lʲ/ are palatalized dental. -- /nˠ, rˠ, lˠ/ are velarized postalveolar. -- Regressively assimilate primary dental or postalveolar. -- None of this will be visible unless PHONETIC_DETAILS

true. primaryL = CONSON_REFLEX[primaryL] [secondaryL == "j" and "G" or "j"] primaryR = CONSON_REFLEX[primaryR][secondaryR] else primaryL = CONSON_REFLEX[primaryL][secondaryL] if primaryR ~= "" then primaryR = CONSON_REFLEX[primaryR][secondaryR] end end if primaryR

"T" then if primaryL

"T" then primaryL = finalJ primaryR = initialJ if primaryL

"S" and primaryR ~= "s" then primaryL = "T" elseif primaryL

"T" and primaryR

"s" and medialJ

"S" then primaryL = "S" end else primaryR = medialJ end elseif primaryL

"T" then if isInitial then primaryL = initialJ elseif isFinal then primaryL = finalJ else primaryL = medialJ end end if primaryR ~= "" then -- Consonant cluster. -- For some reason, the in and is voiceless. if not geminated and primaryL ~= "l" and primaryL ~= "L" then primaryL = VOICED_PRIMARY[primaryL] or primaryL primaryR = VOICED_PRIMARY[primaryR] or primaryR end -- Display secondary articulation only once for the cluster. secondaryL = "" elseif not isInitial and not isFinal then -- Medial single consonant. primaryL = VOICED_PRIMARY[primaryL] or primaryL end if voice

false then primaryL = VOICELESS_PRIMARY[primaryL] or primaryL primaryR = VOICELESS_PRIMARY[primaryR] or primaryR elseif voice

true then primaryL = VOICED_PRIMARY[primaryL] or primaryL primaryR = VOICED_PRIMARY[primaryR] or primaryR end return primaryL..secondaryL.._..primaryR..secondaryR end ) if not diphthongs then -- Elegantly connect long and epenthetic vowels across word gaps. text = string.gsub(text, "(["..V_..":]): +", "%1 : ") text = string.gsub(text, "("..V..") +%1([^%^])", "%1 :%2") text = string.gsub(text, "("..V..") +%1$", "%1 :") text = string.gsub(text, "("..V..")@ +%1", " %1 :") text = string.gsub(text, "("..V.."@) +", " %1 ") if W_OFF_GLIDES then -- Add [w] off-glides after certain consonants. subst = function(primary, _, epenth) if epenth

"" then return primary.."Hw".._ end end if false and PHONETIC_DETAILS then text = string.gsub(text, "([pbm])(G *[aEei])(@?)", function(primary, _, epenth) if epenth

"" then return primary.."B".._ end end ) else text = string.gsub(text, "([pbm])G(*[aEei])(@?)", subst) end text = string.gsub(text, "([kgnNrl])w(*[aEeiAV7M])(@?)", subst) -- Remove [w] off-glides after certain consonants -- when they occur after rounded vowels. text = string.gsub(text, "([QOou] *[nrl]? *[nrl])Hw", "%1w") text = string.gsub(text, "([QOou] *[kgN]? *N)Hw(*M)", "%1w%2") end end if PARENTHETICAL_EPENTHESIS then if not diphthongs then text = string.gsub(text, "(.)@("..V..")", "%1^%2") end text = string.gsub(text, "(.)@", "(%1)") text = string.gsub(text, "%)(=?)%(", "%1") if not diphthongs and W_OFF_GLIDES then if false and PHONETIC_DETAILS then text = string.gsub(text, "([pbm]G%([aEei])", "%1BG%2") else text = string.gsub(text, "([pbm]G%([aEei])", "%1Hw%2") end text = string.gsub(text, "([kgnNrl]w%([aEeiAV7M])", "%1Hw%2") text = string.gsub(text, "([QOou] *[nrl]w%Hw", "%1") text = string.gsub(text, "([QOou] *Nw%HwM", "%1M") end end -- Convert remaining word gaps to liaison. text = fastTrim(text) text = string.gsub(text, " +", false and "_" or "") text = string.gsub(text, ".[jGw@%^]?", PHONETIC_IPA) addUnique(outSeq, text) end

local PHONETIC_ARG_J =

local function toPhonetic(inSeq, args) -- Recognize "ralik" for Rālik Chain (western dialect). -- Recognize "ratak" for Ratak Chain (eastern dialect). -- For other values, list both possible dialect reflexes where applicable. local dialect = args and args.dialect and mw.ustring.lower(mw.text.trim(args.dialect)) or "" if dialect

"rālik" then dialect = "ralik" end -- If enabled, display full diphthong allophones for short vowels. local diphthongs = not not (args and parseBoolean(args.diphthongs)) -- Argument "J" has format like "tst". -- Recognized letters are "t" = plosive, "c" = affricate, "s" = fricative. -- Letters for initial, medial and final respectively. -- Real-world pronunciation said to vary by sociological factors, -- but all realizations may occur in free variation. local modeJ = splitChars(args and args.J and string.lower(args.J) or "tst") local initialJ = PHONETIC_ARG_J[modeJ[1] or ""] or "t" local medialJ = PHONETIC_ARG_J[modeJ[2] or ""] or "s" local finalJ = PHONETIC_ARG_J[modeJ[3] or ""] or initialJ -- If enabled, do not display pseudo-glide hints at all. local noHints = not not (args and parseBoolean(args.nohints)) -- "false" will display all obstruent allophones as voiceless. -- "true" will display all obstruent allophones as voiced. -- Empty string or absent by default will display -- only medial obstruent allophones as semi-voiced. local voice = args and args.voice or "" if voice ~= "" then voice = parseBoolean(voice) end local outSeq = local config = for _, str in pairs(inSeq) do str = string.gsub(str, S, " ") str = string.gsub(str, "^ *", "") str = string.gsub(str, " *$", "") local isRalik = dialect

"ralik" if isRalik or dialect

"ratak" then str = toPhoneticDialect(str, config, isRalik) toPhoneticRemainder(str, config) else local ralik = toPhoneticDialect(str, config, true) local ratak = toPhoneticDialect(str, config, false) -- If both dialect reflexes are the same, display only one of them. toPhoneticRemainder(ralik, config) if ralik ~= ratak then toPhoneticRemainder(ratak, config) end end end return outSeq end

export._parse = parseexport._toBender = toBenderexport._toMOD = toMODexport._toPhonemic = toPhonemicexport._toPhonetic = toPhonetic

function export.bender(frame) return table.concat(toBender(parse(frame.args[1], frame.args)), ", ")end

function export.MOD(frame) return toMOD(frame.args[1])end

function export.parse(frame) return table.concat(parse(frame.args[1]), ", ")end

function export.phonemic(frame) return table.concat(toPhonemic(parse(frame.args[1])), ", ")end

function export.phonetic(frame) return table.concat(toPhonetic(parse(frame.args[1]), frame.args), ", ")end

function export.phoneticMED(frame) return "DEPRECATED"end

function export.phoneticChoi(frame) return "DEPRECATED"end

function export.phoneticWillson(frame) return "DEPRECATED"end

return export