Module:IPAc2-mh: Difference between revisions

From Vigyanwiki
([w] off-glide tweak)
 
m (1 revision imported)
 
(No difference)

Latest revision as of 14:45, 30 November 2022

Documentation for this module may be created at Module:IPAc2-mh/doc

-- 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