Module:User:Erutuon/mh-pronunc

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Lua error: attempt to index field '?' (a nil value)


-- This is still a work in progress.

local export = {}

local concat = table.concat
local find = mw.ustring.find
local gsplit = mw.text.gsplit
local gsub = mw.ustring.gsub
local insert = table.insert
local lower = mw.ustring.lower
local split = mw.text.split
local trim = mw.text.trim

local LP = "%("
local RP = "%)"

local LQ = LP.."?"
local RQ = RP.."?"

local ASYLL = "̯"
local ASYLLTIE = "᷼"
local DENT = "̪"
local DEVO = "̥"
local DEVO2 = "̊"
local DOWN = "̞"
local RETR = "̠"
local TIE = "͡"
local TIE2 = "͜"
local UNREL = "̚"
local UP = "̝"

local C2 = "[ʲˠʷ]"
local NG1 = "[^ɦ_]"
local NG = NG1..C2
local C = "."..C2
local V_ = "æɛeiɑʌɤɯɒɔouï"
local V = "["..V_.."]"
local NV = "[^"..V_.."]"
local S = "[%s%-]*"

local function gsub2(text, patt, subst)
	text = gsub(text, patt, subst)
	text = gsub(text, patt, subst)
	return text
end

local function gsubx(text, patt, subst)
	local oldText
	repeat
		oldText = text
		text = gsub(text, patt, subst)
	until text == oldText
	return text
end

local function insertUnique(seq, value)
	for _, value2 in pairs(seq) do
		if value == value2 then
			return
		end
	end
	insert(seq, value)
end

local function parseBoolean(text)
	local z = false
	if text then
		text = trim(text)
		if text ~= "" and text ~= "0" and lower(text) ~= "false" then
			z = true
		end
	end
	return z
end

local function parse(code)
	
	local seq, subst, temp
	
	code = trim(code)
	seq = {}
	
	for text in gsplit(code, "%s*,[%s,]*") do
		
		text = trim(text)
		
		if text ~= "" then
		 
			text = " "..lower(text).." "
			
			temp = gsub(text, "[abdeghijklmnprtwy_&'%-%s]", "")
			if temp ~= "" then
				error("'"..code.."' contains unsupported characters: "..temp)
			end
			
			-- recognize "y_", "h_", "w_", "_y", "_h", "_w" as pseudo-glides
			subst = { ["h"] = "0ˠ", ["w"] = "0ʷ", ["y"] = "0ʲ" }
			text = gsub(text, "_*([hwy])_+", subst)
			text = gsub(text, "_+([hwy])", subst)
			if find(text, "_") then
				error("contains misplaced underscores: "..code)
			end
			text = gsub(text, "0", "_")
			
			-- recognize "ng", but not plain "g"
			-- "ngw" is a special sequence
			text = gsub(text, "ngw?", {
				["ng"] = "ŋˠ",
				["ngw"] = "ŋʷ"
			})
			if find(text, "g") then
				error("contains g that is not part of ng: "..code)
			end
			
			-- "kw", "lh", "lw", "mh", "nh", "nw", "rw" are special sequences
			-- recognize both these and plain "k", "l", "m", "n", "r"
			-- but "kh", "mw", "rh" are not special sequences
			text = gsub(text, "[klmnr][hw]?", {
				["k"] = "kˠ",
				["kh"] = "kˠh", -- N\A
				["kw"] = "kʷ",
				["l"] = "lʲ",
				["lh"] = "lˠ",
				["lw"] = "lʷ",
				["m"] = "mʲ",
				["mh"] = "mˠ",
				["mw"] = "mʲw", -- N\A
				["n"] = "nʲ",
				["nh"] = "nˠ",
				["nw"] = "nʷ",
				["r"] = "rˠ",
				["rh"] = "rˠh", -- N\A
				["rw"] = "rʷ"
			})
			
			-- "passing over lightly"
			text = gsub(text, "yi'+y", "ĭʲ")
			-- "dwelling upon"
			text = gsub(text, "'+yiy", "īʲ")
			-- a plain /i/ protected from dialect-specific reflexes
			text = gsub(text, "'+i", "ï")
			
			-- convert remaining sequences to internal format
			text = gsub(text, "[abdehijptwy&']", {
				["a"] = "æ",
				["b"] = "pˠ",
				["d"] = "rʲ",
				["e"] = "ɛ",
				["&"] = "e",
				["h"] = "ɦˠ",
				["i"] = "i",
				["j"] = "tʲ",
				["p"] = "pʲ",
				["t"] = "tˠ",
				["w"] = "ɦʷ",
				["y"] = "ɦʲ",
				["'"] = ""
			})
			
			-- treat initial /ɦˠɦˠ/ as a special consonant
			text = gsub(text, "("..NV..")ɦˠɦˠ("..V..")", "%1ɣˠ%2")
			
			-- enforce /CVC/, /CVCVC/, /CVCCVC/ phonotactics
			-- but allow /(_)VC/, /CV(_)/ at affix boundaries
			-- where a vowel may link to another morpheme's consonant
			temp = gsub(text, S, "")
			if find(temp, "_."..C) or find(temp, C.."_") then
				error("pseudo-glides may not neighbor a consonant")
			end
			if find(temp, V.."_."..V) then
				error("pseudo-glides may only be at the beginning or end"..code)
			end
			if find(temp, V..V) then
				error("vowels must be separated by a consonant: "..code)
			end
			if find(temp, C..C..C) then
				error("consonant clusters are limited to two: "..code)
			end
			if find(temp, C..C.."$") then
				error("may not end with a consonant cluster: "..code)
			end
			gsub(temp, "^("..C..")("..C..")", function(a, b)
				if a ~= b then
					error(
						"may only begin with single or geminated consonant: "
						..code
					)
				end
				return ""
			end)
			
			text = gsub(text, "%s+", " ")
			text = trim(text)
			if text ~= "" then
				insertUnique(seq, text)
			end
			
		end
		
	end
	
	return seq
	
end

local function toBender(items, args)
	-- "1969" is from "Spoken Marshallese" (1969 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 beneath consonants
	local version = args and args.version ~= "" and lower(args.version)
		or "default"
	local consSubst = {
		["pʲ"] = "p",
		["pˠ"] = "b",
		["tʲ"] = "j",
		["tˠ"] = "t",
		["kˠ"] = "k",
		["kʷ"] = ({ ["1969"] = "q", ["med"] = "q" })[version] or "kʷ",
		["mʲ"] = "m",
		["mˠ"] = ({ ["1969"] = "ṁ", ["mod"] = "ṃ" })[version] or "m̧",
		["nʲ"] = "n",
		["nˠ"] = ({ ["1969"] = "ṅ", ["mod"] = "ṇ" })[version] or "ņ",
		["nʷ"] = ({
			["1969"] = "n̈", ["med"] = "ņ°", ["mod"] = "ṇʷ"
		})[version] or "ņʷ",
		["ŋˠ"] = "g",
		["ŋʷ"] = ({ ["1969"] = "g̈", ["med"] = "g°" })[version] or "gʷ",
		["rʲ"] = "d",
		["rˠ"] = "r",
		["rʷ"] = ({ ["1969"] = "r̈", ["med"] = "r°" })[version] or "rʷ",
		["lʲ"] = "l",
		["lˠ"] = ({ ["1969"] = "ƚ", ["mod"] = "ḷ" })[version] or "ļ",
		["lʷ"] = ({
			["1969"] = "l̈", ["med"] = "ļ°", ["mod"] = "ḷʷ"
		})[version] or "ļʷ",
		["ĭʲ"] = "yi'y",
		["īʲ"] = "'yiy",
		["ɣˠ"] = "hh",
		["ɦʲ"] = "y",
		["ɦˠ"] = "h",
		["ɦʷ"] = "w",
		["_ʲ"] = "",
		["_ˠ"] = "",
		["_ʷ"] = ""
	}
	local vowelSubst = {
		["æ"] = "a",
		["ɛ"] = "e",
		["e"] = ({ ["1969"] = "&", ["mod"] = "ẹ" })[version] or "ȩ",
		["i"] = "i",
		["ï"] = "i"
	}
	local seq = {}
	for _, text in pairs(items) do
		text = gsub(text, C, consSubst)
		text = gsub(text, V, vowelSubst)
		insertUnique(seq, text)
	end
	return seq
end

local function toPhonemic(items)
	local seq = {}
	for _, text in pairs(items) do
		text = gsub(text, C, {
			["kˠ"] = "k",
			["ŋˠ"] = "ŋ",
			["ĭʲ"] = "ji̯j",
			["īʲ"] = "jijj",
			["ɣˠ"] = "ɰɰ",
			["ɦʲ"] = "j",
			["ɦˠ"] = "ɰ",
			["ɦʷ"] = "w",
			["_ʲ"] = "",
			["_ˠ"] = "",
			["_ʷ"] = ""
		})
		text = gsub(text, "ï", "i")
		insertUnique(seq, text)
	end
	return seq
end

local function toPhonetic(items, args)
	
	-- if enabled, display any palatalized coronal sibilant allophones
	-- as alveolopalatal sibilants
	local alvPal = args and parseBoolean(args.alvpal)
	
	-- recognize "ralik" for Rālik Chain (western dialect)
	-- recognize "ratak" for Ratak Chain (eastern dialect)
	-- for "any", list both possible dialect reflexes where applicable
	local dialect = args and args.dialect and lower(args.dialect) or "any"
	if dialect == "rālik" then
		dialect = "ralik"
	end
	
	-- argument "J" has format like "tstt"
	-- recognized letters are "t" = plosive, "c" = affricate, "s" = fricative
	-- letters for initial, medial, final and geminate respectively
	-- real-world pronunciation said to vary by sociological factors
	-- but all realizations may occur in free variation
	local modeJ = split(args.J and lower(args.J) or "tstt", "")
	local voicelessJ = { ["t"] = "t", ["c"] = "ʦ", ["s"] = "s" }
	local voicedJ    = { ["t"] = "d", ["c"] = "ʣ", ["s"] = "z" }
	local initialJ = voicelessJ[modeJ[1] or ""] or "t"
	local medialJ = voicedJ[modeJ[2] or ""] or "t"
	local finalJ = voicelessJ[modeJ[3] or ""] or initialJ
	local geminateJ = voicelessJ[modeJ[4] or ""] or initialJ
	
	-- if enabled, do not display pseudo-glide hints at all
	local noHints = args and parseBoolean(args.nohints)
	
	-- if enabled, vowels between two non-glide consonants
	-- will be radically simplified resembling newer Marshallese orthography
	local radSimp = args and parseBoolean(args.radsimp)
	
	-- 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 ""
	
	local seq = {}
	
	local function forItem(text)
		
		local map, map2, patt, patt2, subst
		
		text = gsub(text, S, "")
		
		function forDialect(text, dialect)
			
			-- morphemes can begin with geminated consonants,
			-- but spoken words cannot
			text = gsub(text, "^("..C..")%1("..V..")", function(a, b)
				-- the prosthetic vowel is never more open than /ɛ/
				local c = b
				if c == "æ" then
					c = "ɛ"
				end
				if dialect == "ralik" then
					-- Rālik /CCV-/ becomes /jVCCV-/
					return "ɦʲ"..c..a..a..b
				else
					-- Ratak /CCV-/ becomes /CVCV-/
					return a..c..a..b
				end
			end)
			
			-- initial /jijV-, jiwV-, wiwV-/ sequences have special behavior
			-- to block this in the template argument, use "'i" instead of "i"
			text = " "..text
			text = gsub(
				text, "([ʲˠʷ ])(ɦ[ʲʷ])i(ɦ[ʲʷ])("..V..")",
				function(a, b, c, d)
					local result
					if c == "ɦʷ" then
						-- /jiwV-, wiwV-/ sequences
						if dialect == "ralik" then
							-- Rālik /wiwV-/ becomes /jiwV-/
							b = "ɦʲ"
						end
						-- /[jw]iwV-/ becomes /[jw]iwwV-/ in both dialects
						result = b.."ïɦʷɦʷ"
					elseif b == "ɦʲ" then
						-- /jijV-/ sequences
						if dialect == "ralik" then
							-- "dwelling upon"
							result = "īʲ"
						else
							-- "passing over lightly"
							result = "ĭʲ"
						end
					else
						-- no change for /wijV-/ sequences
						result = b.."ï"..c
					end
					return a..result..d
				end
			)
			text = trim(text)
			
			-- Rālik /ɰɰV-/ becomes /ɰVɰV-/
			if dialect == "ralik" then
				-- Rālik /ɰɰV-/ becomes /ɰVɰ-/
				text = gsub(text, "ɣˠ("..V..")", "ɦˠ%1ɦˠ")
			else
				-- Ratak /ɰɰV-/ becomes /ɰV-/
				text = gsub(text, "ɣ", "ɦ")
			end
			
			return text
			
		end
		
		if dialect == "ralik" or dialect == "ratak" then
			text = forDialect(text, dialect)
		else
			local ralik = forDialect(text, "ralik")
			local ratak = forDialect(text, "ratak")
			-- if both dialect reflexes are the same, display only one of them
			if ralik == ratak then
				text = ralik
			else
				forItem(ralik)
				forItem(ratak)
				return
			end
		end
		
		-- if the phrase begins or ends with a bare vowel and no pseudo-glide,
		-- display phrase three times with each of the different pseudo-glides
		if find(text, "^"..V) then
			forItem("_ʲ"..text)
			forItem("_ˠ"..text)
			forItem("_ʷ"..text)
			return
		end	
		if find(text, V.."$") then
			forItem(text.."_ʲ")
			forItem(text.."_ˠ")
			forItem(text.."_ʷ")
			return
		end
		
		-- restore protected /i/, we won't be hiding it anymore
		text = gsub(text, "ï", "i")
		
		-- expand "dwelling upon" i, we won't be checking for it anymore
		text = gsub(text, "īʲ", "ɦʲiɦʲɦʲ")
		
		-- forward assimilation of rounded consonant clusters
		subst = "%1ʷ%2%ʷ"
		text = gsub(text, "([kŋ])ʷ([kŋ]).", subst)
		text = gsub(text, "([nrl])ʷ([nrl]).", subst)
		
		-- experimental
		if true then
			-- the references are vague on
			-- how this actually assimilates
			-- there is no /tʷ/ in Marshallese
			-- but /tˠ/ is at least still heavy
			text = gsub(text, "nʷtʲ", "nʷtˠ")
		end
		
		-- backward assimilation of remaining secondary articulations
		subst = "%1%3%2%3"
		text = gsub(text, "([pm]).([pm])(.)", subst)
		text = gsub(text, "([tn]).(t)(.)", subst)
		text = gsub(text, "([kŋ]).([kŋ])(.)", subst)
		text = gsub(text, "([nrl]).([nrl])(.)", subst)
		
		-- backward nasal assimilation of consonant clusters
		subst = "%2%1%2"
		text = gsub(text, "p(.)(m)", subst)
		text = gsub(text, "[rl](.)(n)", subst)
		text = gsub(text, "k(.)(ŋ)", subst)
		
		-- insert epenthetic vowels within unstable consonant clusters
		subst = "%1V%2"
		-- rhotics before coronal obstruents
		text = gsub(text, "(r.)(t)", subst)
		-- laterals before velarized coronal obstruents are stable
		-- but laterals before palatalized coronal obstruents are not
		text = gsub(text, "(l.)(tʲ)", subst)
		-- obstruents or non-coronal nasals before coronal nasals or liquids
		text = gsub(text, "([ptkmŋ].)([nrl])", subst)
		-- labials before coronals or dorsals
		text = gsub(text, "([pm].)([tkŋ])", subst)
		-- coronals before dorsals or labials
		text = gsub(text, "([tnrl].)([pkmŋ])", subst)
		-- dorsals before labials or coronals
		text = gsub(text, "([kŋ].)([ptm])", subst)
		
		-- give those epenthetic vowels a transitional height
		map = {
			["æ"] = { ["æ"] = "æ", ["ɛ"] = "æ", ["e"] = "ɛ", ["i"] = "ɛ" },
			["ɛ"] = { ["æ"] = "ɛ", ["ɛ"] = "ɛ", ["e"] = "ɛ", ["i"] = "e" },
			["e"] = { ["æ"] = "ɛ", ["ɛ"] = "e", ["e"] = "e", ["i"] = "e" },
			["i"] = { ["æ"] = "e", ["ɛ"] = "e", ["e"] = "i", ["i"] = "i" }
		}
		text = gsub2(text, "(.)(..)V(..)(.)", function(a, b, c, d)
			if not (map[a] and map[a][d]) then
				error("no data for "..d.." after "..a)
			end
			return a..b.."("..map[a][d]..")"..c..d
		end)
		
		-- clusters with glides have epenthetic vowels, too
		-- but their height is that of the glide's other vowel
		-- forming epenthetic long vowels
		text = gsub(text, "("..V..")ĭʲ("..NG..")", "%1ɦʲi%2")
		text = gsub(text, "("..NG..")ĭʲ("..V..")", "%1iɦʲ%2")
		text = gsub(text, "("..V..")(ɦ.)("..C..")", "%1%2%1%3")
		text = gsub(text, "("..C..")(ɦ.)("..V..")", "%1%3%2%3")
		
		local allophones = {
			["ʲ"] = { ["æ"] = "æ", ["ɛ"] = "ɛ", ["e"] = "e", ["i"] = "i" },
			["ˠ"] = { ["æ"] = "ɑ", ["ɛ"] = "ʌ", ["e"] = "ɤ", ["i"] = "ɯ" },
			["ʷ"] = { ["æ"] = "ɒ", ["ɛ"] = "ɔ", ["e"] = "o", ["i"] = "u" }
		}
		
		local front, back, rounded = "ʲ", "ˠ", "ʷ"
		local glide = "ɦ"
		local low, mid_low, mid_high, high = "æ", "ɛ", "e", "i"
		-- match consonant, articulation, vowel,
		-- and grab next consonant and articulation,
		-- excluding them from pattern so that matches don't overlap
		text = gsub(text, "()(.)("..C2..")("..LQ..")("..V..")("..RQ..")()",
			function(start, C1, A1, LQ, V, RQ, finish)
				local C2, A2 = mw.ustring.match(text, "(.)(.)", finish)
				local final_articulation
				if A1 == A2 then
					final_articulation = A1
				elseif (C1 == glide) ~= (C2 == glide) then
					final_articulation = C1 == glide and A1 or A2
				else
					-- incorporate some orthographic biases
					-- from [[w:Marshallese language#Orthography]]
					local front_vs_back = A1 == front and A2 == back or A2 == front and A1 == back
					-- a over ā
					if front_vs_back and V == low then
						final_articulation = back
					-- i over ū
					elseif front_vs_back and V == high then
						final_articulation = front
					-- ō over e
					elseif front_vs_back and (V == mid_low or V == mid_high) then
						final_articulation = back
					elseif A1 == front and (A2 == back or A2 == rounded) then
						final_articulation = A2
					elseif A2 == front and (A1 == back or A1 == rounded) then
						final_articulation = A1
					elseif A1 == back and A2 == rounded then
						final_articulation = A2
					elseif A2 == back and A1 == rounded then
						final_articulation = A1
					end
				end
				local final_V = final_articulation and allophones[final_articulation][V]
					or allophones[A1][V]..allophones[A2][V]
				mw.log(text, start, C1, A1, LQ, V, RQ, C2, A2, final_V)
				return C1..A1..LQ..final_V..RQ
			end)
		
		text = trim(text)
		
		-- tie short diphthongs
		text = gsub(text, "("..V..")("..V..")", "%1"..TIE.."%2")
		
		-- delete glide phonemes, now that we're done coloring vowels
		text = gsub(text, "ɦ.", "")
		
		-- experimental
		if true then
			-- convert double consonants to geminates
			text = gsub(text, "("..C..")%1", "%1ː")
		end
		
		-- convert certain medial obstruent consonants to voiced allophones
		map = {
			["pʲ"] = "bʲ",
			["pˠ"] = "bˠ",
			["tʲ"] = medialJ.."ʲ",
			["tˠ"] = "dˠ",
			["kˠ"] = "ɡˠ",
			["kʷ"] = "ɡʷ"
		}
		subst = function(a, b, c)
			return a..map[b]..c
		end
		text = gsub2(text, "("..V..RQ..")([ptk].)("..LQ..V..")", subst)
		
		-- experimental
		if true then
			-- after nasals, too
			text = gsub(text, "([mnŋ].)([ptk].)("..V..")", subst)
		end
		
		-- convert /tʲ/ to preferred allophones per J argument
		text = gsub(text, "tʲː?", {
			["tʲ"] = initialJ.."ʲ",
			["tʲː"] = geminateJ.."ʲː"
		})
		patt = "[tʦs]ʲ"
		text = gsub(text, patt..patt, finalJ.."ʲ"..initialJ.."ʲ")
		text = gsub(text, patt.."$", finalJ.."ʲ")
		
		-- display full voicing or full devoicing per voice argument
		if voice ~= "" then
			if parseBoolean(voice) then
				-- display all consonants as voiced in this mode
				text = gsub(text, "[ptʦsk]", {
					["p"] = "b",
					["t"] = "d",
					["ʦ"] = "ʣ",
					["s"] = "z",
					["k"] = "ɡ"
				})
			else
				-- display all consonants as voiceless in this mode
				text = gsub(text, "[bdʣzɡ]", {
					["b"] = "p",
					["d"] = "t",
					["ʣ"] = "ʦ",
					["z"] = "s",
					["ɡ"] = "k"
				})
			end
		end
		
		-- experimental
		if true then
			-- convert hard glides /j/ to semivowels
			-- æɛeiɑʌɤɯɒɔou
			text = gsub(text, "jʲ([ɯu])", "i̯%1")
			text = gsub(text, "([ɯu])jʲ", "%1i̯")
			text = gsub(text, "jʲ([ɤo])", "e̯%1")
			text = gsub(text, "([ɤo])jʲ", "%1e̯")
			text = gsub(text, "jʲ([ɑʌɒɔ])", "ɛ̯%1")
			text = gsub(text, "([ɑʌɒɔ])jʲ", "%1ɛ̯")
		end
		
		-- experimental
		if true then
			-- convert repeated vowels to geminates
			text = gsubx(text, "("..V..")(ː*)%1", "%1%2ː")
		end
		
		-- display affricates, if any, as tied consonants
		text = gsub(text, "[ʦʣ]", {
			["ʦ"] = "t"..TIE.."s",
			["ʣ"] = "d"..TIE.."z"
		})
		
		if alvPal then
			text = gsub(text, "[sz]ʲ", { ["sʲ"] = "ɕ", ["zʲ"] = "ʑ" })
		end
		
		-- display final stops as unreleased
		text = gsub(text, "([ptk]"..C2..")$", "%1"..UNREL)
		
		-- (mostly) final consonant presentation forms
		text = gsub(text, C..UNREL.."?", {
			-- dorsal obstruents and nasals are postvelar
			["kˠ"] = "k"..RETR,
			["kʷ"] = "k"..RETR.."ʷ",
			["ɡˠ"] = "ɡ"..RETR,
			["ɡʷ"] = "ɡ"..RETR.."ʷ",
			["ŋˠ"] = "ŋ"..RETR,
			["ŋʷ"] = "ŋ"..RETR.."ʷ",
			-- these are "dark-R-colored," e.g. retroflex
			["nˠ"] = "ɳˠ",
			["nʷ"] = "ɳʷ",
			-- these are dental according to Bender (1969)
			["tˠ"] = "t"..DENT.."ˠ",
			["dˠ"] = "d"..DENT.."ˠ",
			["nʲ"] = "n"..DENT.."ʲ",
			["rʲ"] = "r"..DENT.."ʲ",
			["lʲ"] = "l"..DENT.."ʲ",
			-- dark L
			--["lˠ"] = "ɫ",
			--["lʷ"] = "ɫʷ",
			-- "passing over lightly"
			["ĭʲ"] = "i"..ASYLL,
			-- on-glides
			["jʲ"] = "j",
			["wʷ"] = "w",
			-- unreleased stops
			["pʲ"..UNREL] = "p"..UNREL.."ʲ",
			["pˠ"..UNREL] = "p"..UNREL.."ˠ",
			["tʲ"..UNREL] = "t"..UNREL.."ʲ",
			["tˠ"..UNREL] = "t"..DENT..UNREL.."ˠ",
			["kˠ"..UNREL] = "k"..RETR..UNREL,
			["kʷ"..UNREL] = "k"..RETR..UNREL.."ʷ"
		})
		
		-- experimental
		if true then
			if voice == "" then
				-- voiced allophones are actually semi-voiced
				text = gsub(text, "[bdzʑɡ]", {
					["b"] = "b"..DEVO,
					["d"] = "d"..DEVO,
					["z"] = "z"..DEVO,
					["ʑ"] = "ʑ"..DEVO2,
					["ɡ"] = "ɡ"..DEVO2
				})
				-- these display better
				text = gsub(text, DEVO..DENT, DENT..DEVO)
			end
		end
		
		-- experimental
		if false then
			-- deparenthesize epenthetic vowels, but make them asyllabic
			text = gsub(text, LP.."("..V..")"..RP, "%1"..ASYLL.."")
			text = gsub(
				text, LP.."("..V..")"..TIE.."("..V..")"..RP,
				"%1"..ASYLL..TIE.."%2"..ASYLL..""
			)
		end
		
		-- experimental
		if false then
			-- raised allophones?
			text = gsub(text, "[æɛeɒɔo]", "%1"..UP)
		end
		
		-- experimental
		if false then
			-- alternate between above and below ties to improve presentation
			text = gsub(
				text, TIE.."([^"..TIE.."]*)"..TIE.."([^"..TIE.."]*)",
				TIE.."%1"..TIE2.."%2"
			)
		end
		
		-- experimental
		if false then
			-- tying vowels from below is more elegant when there are ascenders
			text = gsub(text, "("..V..")"..TIE.."("..V..")", "%1"..TIE2.."%2")
		end
		
		-- experimental
		if true then
			-- Wiktionary tends to prefer more diacritics
			-- Wikipedia tends to prefer fewer diacritics
			-- strip some diacritics that don't affect meaning
			text = gsub(
				text, "["..DENT..DEVO..DEVO2..DOWN..RETR..UNREL..UP.."]", ""
			)
		end
		
		if noHints then
			text = gsub(text, "_.", "")
		else
			-- convert pseudo-glides to hints for attached affixes
			text = gsub(text, "^_(.)", "%1‿")
			text = gsub(text, "_(.)$", "‿%1")
		end
		
		-- workaround:  where is the trailing space being introduced?
		--text = trim(text)
		
		insertUnique(seq, text)
		
	end
	
	for _, item in pairs(items) do
		forItem(item)
	end
	
	return seq
	
end

export._parse = parse
export._toBender = toBender
export._toPhonemic = toPhonemic
export._toPhonetic = toPhonetic

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

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

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

function export.phonetic(frame)
	return 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