Open main menu
Home
Random
Donate
Recent changes
Special pages
Community portal
Preferences
About Stockhub
Disclaimers
Search
User menu
Talk
Contributions
Create account
Log in
Editing
Module:IPAc2-mh
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
-- 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 = false local PARENTHETICAL_EPENTHESIS = true local PHONETIC_DETAILS = false local 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] = value end -- 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 target end 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 false end 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 chars end 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 tbl end local PARSE_PSEUDO_GLIDE = { ["y"] = "0", ["h"] = "0h", ["w"] = "0w" } local PARSE_C_CH_CW = { ["k"] = "kG", ["kh"] = "kGh", -- N\A ["kw"] = "kW", ["l"] = "lJ", ["lh"] = "lG", ["lw"] = "lW", ["m"] = "mJ", ["mh"] = "mG", ["mw"] = "mJw", -- N\A ["n"] = "nJ", ["nh"] = "nG", ["nw"] = "nW", ["ng"] = "NG", ["ngh"] = "NGh", -- N\A ["ngw"] = "NW", ["r"] = "rG", ["rh"] = "rGh", -- N\A ["rw"] = "rW", ["0"] = "_J", ["0h"] = "_G", ["0w"] = "_W" } local PARSE_REMAINING = { ["b"] = "pG", ["d"] = "rj", ["e"] = "E", ["&"] = "e", ["h"] = "hG", ["j"] = "tj", ["J"] = "j", ["p"] = "pj", ["t"] = "tG", ["w"] = "hw", ["W"] = "w", ["y"] = "hj", ["z"] = "yj", ["Z"] = "Yj", ["'"] = "" } 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 {i} 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 = { ["pj"] = "p", ["pG"] = "b", ["tj"] = "j", ["tG"] = "t", ["kG"] = "k", ["kw"] = "q", ["mj"] = "m", ["mG"] = "ṁ", ["nj"] = "n", ["nG"] = "ṅ", ["nw"] = "n̈", ["NG"] = "g", ["Nw"] = "g̈", ["rj"] = "d", ["rG"] = "r", ["rw"] = "r̈", ["lj"] = "l", ["lG"] = "ł", ["lw"] = "l̈", ["yj"] = "yi'y", ["Yj"] = "'yiy", ["hj"] = "y", ["hG"] = "h", ["hw"] = "w", ["_j"] = "", ["_G"] = "", ["_w"] = "", ["a"] = "a", ["E"] = "e", ["e"] = "&", ["i"] = "i", ["I"] = "i" } local BENDER_MED = assign({}, BENDER_1968, { ["mG"] = "m̧", ["nG"] = "ņ", ["nw"] = "ņ°", ["Nw"] = "g°", ["rw"] = "r°", ["lG"] = "ļ", ["lw"] = "ļ°", ["e"] = "ȩ" }) local BENDER_MOD = assign({}, BENDER_MED, { ["kw"] = "kʷ", ["mG"] = "ṃ", ["nG"] = "ṇ", ["nw"] = "ṇʷ", ["Nw"] = "gʷ", ["rw"] = "rʷ", ["lG"] = "ḷ", ["lw"] = "ḷʷ", ["e"] = "ẹ" }) local BENDER_DEFAULT = assign({}, BENDER_MOD, { ["mG"] = "m̧", ["nG"] = "ņ", ["nw"] = "ņʷ", ["lG"] = "ļ", ["lw"] = "ļʷ", ["e"] = "ȩ" }) local BENDER_MAPS = { ["1968"] = BENDER_1968, ["med"] = BENDER_MED, ["mod"] = BENDER_MOD } 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 outSeq end local TO_MOD = { ["Ȩ"] = "Ẹ", ["ȩ"] = "ẹ", ["Ļ"] = "Ḷ", ["ļ"] = "ḷ", ["M̧"] = "Ṃ", ["m̧"] = "ṃ", ["Ņ"] = "Ṇ", ["ņ"] = "ṇ", ["N̄"] = "Ñ", ["n̄"] = "ñ", ["O̧"] = "Ọ", ["o̧"] = "ọ" } local function toMOD(text) text = mw.ustring.gsub(text, ".["..CEDILLA..MACRON.."]?", TO_MOD) return text end local PHONEMIC_MAP = { ["pj"] = "pʲ", ["pG"] = "pˠ", ["tj"] = "tʲ", ["tG"] = "tˠ", ["kG"] = "k", ["kw"] = "kʷ", ["mj"] = "mʲ", ["mG"] = "mˠ", ["nj"] = "nʲ", ["nG"] = "nˠ", ["nw"] = "nʷ", ["NG"] = "ŋ", ["Nw"] = "ŋʷ", ["rj"] = "rʲ", ["rG"] = "rˠ", ["rw"] = "rʷ", ["lj"] = "lʲ", ["lG"] = "lˠ", ["lw"] = "lʷ", ["hj"] = "j", ["hG"] = "ɰ", ["hw"] = "w", ["_j"] = "", ["_G"] = "", ["_w"] = "", ["a"] = "æ", ["E"] = "ɛ", ["e"] = "e", ["i"] = "i", ["I"] = "i" } if false then assign(PHONEMIC_MAP, { ["a"] = "ɐ", ["E"] = "ə", ["e"] = "ɘ", ["i"] = "ɨ", ["I"] = "ɨ" }) end assign(PHONEMIC_MAP, { ["yj"] = PHONEMIC_MAP.hj..PHONEMIC_MAP.i..ASYLL..PHONEMIC_MAP.hj, ["Yj"] = PHONEMIC_MAP.hj..PHONEMIC_MAP.i..PHONEMIC_MAP.hj..PHONEMIC_MAP.hj }) local function toPhonemic(inSeq) local outSeq = {} for _, text in pairs(inSeq) do text = string.gsub(text, ".[jGw]?", PHONEMIC_MAP) addUnique(outSeq, text) end return outSeq end local VOWEL = { -- VOWELS[f1][f2] { "a", "A", "Q" }, { "E", "V", "O" }, { "e", "7", "o" }, { "i", "M", "u" } } local F1 = {} local F2_FRONT = 1 local F2_BACK = 2 local F2_ROUND = 3 local F2 = { ["j"] = F2_FRONT, ["G"] = F2_BACK, ["w"] = F2_ROUND } 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 end end 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] end end 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 {yiyV-, yiwV-, wiwV-} 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 -- {yiwV-, wiwV-} sequences if isRalik then -- Rālik {wiwV-} becomes {yiwV-}. consonX = "hj" end -- {[yw]iwV-} becomes {[yw]iwwV-} in both dialects. return nonVowel.._..consonX..__.. "I"..___..consonY..____..consonY..vowel elseif consonX == "hj" then -- {yiyV-} 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 {i}, we won't be checking for it anymore. text = string.gsub(text, "I", "i") return text end local IS_VOWEL = FRONT_VOWEL local VOWEL_REFLEX if true then -- [f1] local aEei = { "a", "E", "e", "i" } local AEei = { "A", "E", "e", "i" } local AV7i = { "A", "V", "7", "i" } local AV7M = { "A", "V", "7", "M" } local AV7u = { "A", "V", "7", "u" } local AOou = { "A", "O", "o", "u" } local QOou = { "Q", "O", "o", "u" } -- [F2[secondaryR]][f1] local _jv_X = { aEei, AEei, QOou } local njv_X = { aEei, AV7i, QOou } local hjvtX = { aEei, aEei, QOou } local hjvkX = { AV7i, AV7i, QOou } local _Gv_X = { AV7i, AV7M, QOou } local rGv_X = { AEei, AV7M, QOou } -- not currently used local hGv_X = { AV7M, AV7M, AV7M } local _wv_X = { AV7u, AOou, QOou } local rwv_X = { AOou, AOou, QOou } local hwv_X = { AV7M, AOou, QOou } local hwvtX = { AV7M, AV7M, QOou } -- [F2[secondaryL]][F2[secondaryR]][f1] local _Xv__ = { _jv_X, _Gv_X, _wv_X } local nXv__ = { njv_X, _Gv_X, hwv_X } local rXv__ = { _jv_X, _Gv_X, rwv_X } local hXv__ = { _jv_X, hGv_X, hwv_X } local hXvt_ = { hjvtX, hGv_X, hwvtX } local hXvk_ = { hjvkX, hGv_X, _wv_X } local hXvr_ = { hjvtX, hGv_X, hwv_X } -- [primaryR][F2[secondaryL]][F2[secondaryR]][f1] local __vX_ = { ["p"] = _Xv__, ["t"] = _Xv__, ["k"] = _Xv__, ["m"] = _Xv__, ["n"] = _Xv__, ["N"] = _Xv__, ["r"] = _Xv__, ["l"] = _Xv__ } local n_vX_ = { ["p"] = nXv__, ["t"] = nXv__, ["k"] = nXv__, ["m"] = nXv__, ["n"] = nXv__, ["N"] = nXv__, ["r"] = nXv__, ["l"] = nXv__ } local r_vX_ = { ["p"] = rXv__, ["t"] = rXv__, ["k"] = rXv__, ["m"] = rXv__, ["n"] = rXv__, ["N"] = rXv__, ["r"] = rXv__, ["l"] = _Xv__ } local h_vX_ = { ["p"] = hXv__, ["t"] = hXvt_, ["k"] = hXvk_, ["m"] = hXv__, ["n"] = hXv__, ["N"] = hXvk_, ["r"] = hXvr_, ["l"] = hXv__ } -- [primaryL][primaryR][F2[secondaryL]][F2[secondaryR]][f1] VOWEL_REFLEX = { ["p"] = __vX_, ["t"] = __vX_, ["k"] = __vX_, ["m"] = __vX_, ["n"] = n_vX_, ["N"] = n_vX_, ["r"] = r_vX_, ["l"] = n_vX_, ["h"] = h_vX_ } end local CONSON_REFLEX if true then local map = { ["t"] = { ["j"] = "T" }, ["n"] = { ["j"] = "J" }, ["r"] = { ["j"] = "R" }, ["l"] = { ["j"] = "L" } } 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 = map end local VOICED_PRIMARY = { ["p"]="b", ["t"]="d", ["T"]="D", ["S"]="Z", ["s"]="z", ["k"]="g" } local VOICELESS_PRIMARY = { ["b"]="p", ["d"]="t", ["D"]="T", ["Z"]="S", ["z"]="s", ["g"]="k" } local PHONETIC_IPA if true then local map = { ["p"] = "p", ["b"] = "b", ["B"] = "β̞", ["t"] = "t", ["d"] = "d", ["s"] = "s", ["z"] = "z", ["k"] = "k", ["g"] = "ɡ", ["m"] = "m", ["n"] = "n", ["N"] = "ŋ", ["r"] = "r", ["l"] = "l", ["Hj"] = "j", ["HG"] = "ʔ", ["Hw"] = "w", ["_"] = "‿", ["j"] = "ʲ", ["G"] = "ˠ", ["w"] = "ʷ", ["a"] = "æ", ["E"] = "ɛ", ["e"] = "e", ["i"] = "i", ["A"] = "ɑ", ["V"] = "ʌ", ["7"] = "ɤ", ["M"] = "ɯ", ["Q"] = "ɒ", ["O"] = "ɔ", ["o"] = "o", ["u"] = "u", ["^"] = ASYLL, ["@"] = ASYLL, ["("] = "(", [")"] = ")", [":"] = "ː", ["="] = TIE2 } if PHONETIC_DETAILS then assign(map, { ["t"] = "t̪", ["T"] = "t̠", ["d"] = "d̪", ["D"] = "d̠", ["s"] = "s̠", ["z"] = "z̠", ["k"] = "k̠", ["g"] = "ɡ̠", ["n"] = "n̠", ["J"] = "n̪", ["N"] = "ŋ̠", ["r"] = "r̠", ["R"] = "r̪", ["l"] = "l̠", ["L"] = "l̪", ["a"] = "æ̝", ["E"] = "ɛ̝", ["E@"] = "e"..map["@"], ["E^"] = "e"..map["^"], ["Q"] = "ɒ̝", ["O"] = "ɔ̝", ["O@"] = "o"..map["@"], ["O^"] = "o"..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 = map end 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 {i} neighboring {yi'y}. text = string.gsub(text, "i@( *yj)", "%1") -- {yi'y} neighboring {i} may now be demoted to {y}. text = string.gsub(text, "([iMu]@? *)yj", "%1hj") text = string.gsub(text, "yj( *[iMu])", "hj%1") -- {'yiy} 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 {yi'y}. text = string.gsub(text, "yj", "i^") if not diphthongs then -- Realization for surfaced {y}. 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 {t} in {lt} and {ļt} 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 = { ["t"] = "T", ["c"] = "S", ["s"] = "s", ["x"] = "x" } 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 = { ["outSeq"] = outSeq, ["diphthongs"] = diphthongs, ["initialJ"] = initialJ, ["medialJ"] = medialJ, ["finalJ"] = finalJ, ["noHints"] = noHints, ["voice"] = voice } 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 = parse export._toBender = toBender export._toMOD = toMOD export._toPhonemic = toPhonemic export._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
Summary:
Please note that all contributions to Stockhub may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
Stockhub:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Templates used on this page:
Template:Tl
(
edit
)
Module:IPAc2-mh/doc
(
edit
)