The map is the syllable grammar: a key sequence stays pending only while it is a prefix of some entry, so a rime the table lacked split the syllable and a trailing tone key landed on the wrong letter (vowis gave vơí, rooif rôì, thaays thâý), ua+tone was a valueless prefix that committed the raw keys (cuar gave cuar, muaf muaf), uyê and ươ typed as uwow took no coda (nguyễn, được), and ưa toned the a (cửa gave cưả). Add âu ây êu ôi ơi eo ia ưi ưu uê ươu uây oeo uya uyu, the uyê, uwow, uê and oe codas, and tone ưa on ư.
181 lines
4.8 KiB
Python
Executable File
181 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
|
|
# tone marks: s=rising f=falling r=hook x=tilde j=dot
|
|
tone = {
|
|
's': str.maketrans("aăâeêioôơuưy", "áắấéếíóốớúứý"),
|
|
'f': str.maketrans("aăâeêioôơuưy", "àằầèềìòồờùừỳ"),
|
|
'r': str.maketrans("aăâeêioôơuưy", "ảẳẩẻểỉỏổởủửỷ"),
|
|
'x': str.maketrans("aăâeêioôơuưy", "ãẵẫẽễĩõỗỡũữỹ"),
|
|
'j': str.maketrans("aăâeêioôơuưy", "ạặậẹệịọộợụựỵ"),
|
|
}
|
|
|
|
# modified vowels: input -> output
|
|
modvowel = [
|
|
("aw", "ă"),
|
|
("aa", "â"),
|
|
("ee", "ê"),
|
|
("oo", "ô"),
|
|
("ow", "ơ"),
|
|
("uw", "ư"),
|
|
]
|
|
|
|
# modified cons: input -> output
|
|
modcons = [
|
|
("dd", "đ"),
|
|
]
|
|
|
|
def addtone(v, t):
|
|
return v.translate(tone[t])
|
|
|
|
entries = []
|
|
|
|
def initialupper(s):
|
|
return s[0].upper() + s[1:]
|
|
|
|
def emit(src, dst):
|
|
entries.append((src, dst))
|
|
print(f"{src}\t{dst}")
|
|
usrc = initialupper(src)
|
|
udst = initialupper(dst)
|
|
if (usrc, udst) != (src, dst):
|
|
print(f"{usrc}\t{udst}")
|
|
|
|
def vowel1():
|
|
for v in "aeiouy":
|
|
emit(v, v)
|
|
for t in tone:
|
|
emit(v+t, addtone(v, t))
|
|
|
|
def vowel2():
|
|
# input, output, vowel
|
|
tab = [
|
|
("oa", "oa", "a"), ("oe", "oe", "e"), ("ai", "ai", "a"),
|
|
("ao", "ao", "a"), ("au", "au", "a"), ("ay", "ay", "a"),
|
|
("eo", "eo", "e"), ("eu", "eu", "e"), ("ia", "ia", "i"),
|
|
("iu", "iu", "i"), ("oi", "oi", "o"), ("ua", "ua", "u"),
|
|
("ui", "ui", "u"), ("uy", "uy", "y"),
|
|
("aau", "âu", "â"), ("aay", "ây", "â"), ("eeu", "êu", "ê"),
|
|
("ooi", "ôi", "ô"), ("owi", "ơi", "ơ"), ("uwi", "ưi", "ư"),
|
|
("uwu", "ưu", "ư"), ("uwa", "ưa", "ư"), ("uee", "uê", "ê"),
|
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
|
("uow", "ươ", "ơ"), ("uaa", "uâ", "â"), ("oaw", "oă", "ă"),
|
|
("uwow", "ươ", "ơ"),
|
|
]
|
|
for i, o, v in tab:
|
|
emit(i, o)
|
|
for t in tone:
|
|
emit(i+t, o.replace(v, addtone(v, t), 1))
|
|
emit("ie", "ie")
|
|
emit("ye", "ye")
|
|
emit("uo", "uo")
|
|
|
|
def vowel3():
|
|
# input, output, vowel
|
|
tab = [
|
|
("ieeu", "iêu", "ê"), ("yeeu", "yêu", "ê"),
|
|
("uooi", "uôi", "ô"), ("uowi", "ươi", "ơ"),
|
|
("uowu", "ươu", "ơ"), ("uwowi", "ươi", "ơ"),
|
|
("uwowu", "ươu", "ơ"), ("uaay", "uây", "â"),
|
|
("oai", "oai", "a"), ("oay", "oay", "a"), ("oeo", "oeo", "e"),
|
|
("uya", "uya", "y"), ("uyu", "uyu", "y"), ("uyee", "uyê", "ê"),
|
|
]
|
|
for i, o, v in tab:
|
|
emit(i, o)
|
|
for t in tone:
|
|
emit(i+t, o.replace(v, addtone(v, t), 1))
|
|
|
|
def modvowels():
|
|
for i, o in modvowel:
|
|
emit(i, o)
|
|
|
|
def modconss():
|
|
for i, o in modcons:
|
|
emit(i, o)
|
|
|
|
def mod1tone():
|
|
# aw+s -> ắ
|
|
for i, o in modvowel:
|
|
for t in tone:
|
|
emit(i+t, addtone(o, t))
|
|
|
|
def tone1mod():
|
|
# a+s+w -> ắ
|
|
for i, o in modvowel:
|
|
for t in tone:
|
|
emit(i[0]+t+i[1], addtone(o, t))
|
|
|
|
def tone2mod():
|
|
# ie+s+e -> iế
|
|
# input, output, vowel, suffix
|
|
tab = [
|
|
("ie", "iê", "ê", "e"), ("ye", "yê", "ê", "e"),
|
|
("uo", "uô", "ô", "o"), ("uo", "ươ", "ơ", "w"),
|
|
("ua", "uâ", "â", "a"), ("oa", "oă", "ă", "w"),
|
|
("uwo", "ươ", "ơ", "w"),
|
|
]
|
|
for i, o, v, s in tab:
|
|
for t in tone:
|
|
emit(i+t+s, o.replace(v, addtone(v, t), 1))
|
|
|
|
def escape():
|
|
# aww -> aw
|
|
for e in ["aw", "aa", "ee", "oo", "ow", "uw", "dd"]:
|
|
emit(e+e[-1], e)
|
|
|
|
def final():
|
|
# codas
|
|
coda = ["c", "m", "n", "p", "t", "ch", "ng", "nh"]
|
|
# input, output, vowel
|
|
tab = [
|
|
("a", "a", "a"), ("e", "e", "e"), ("i", "i", "i"),
|
|
("o", "o", "o"), ("u", "u", "u"), ("y", "y", "y"),
|
|
("aw", "ă", "ă"), ("aa", "â", "â"), ("ee", "ê", "ê"),
|
|
("oo", "ô", "ô"), ("ow", "ơ", "ơ"), ("uw", "ư", "ư"),
|
|
("iee", "iê", "ê"), ("yee", "yê", "ê"), ("uoo", "uô", "ô"),
|
|
("uow", "ươ", "ơ"), ("uwow", "ươ", "ơ"), ("uyee", "uyê", "ê"),
|
|
("uaa", "uâ", "â"), ("uee", "uê", "ê"), ("oaw", "oă", "ă"),
|
|
("ai", "ai", "a"), ("ao", "ao", "a"), ("au", "au", "a"),
|
|
("ay", "ay", "a"), ("oa", "oa", "a"), ("oe", "oe", "e"),
|
|
("oi", "oi", "o"), ("ui", "ui", "u"), ("uy", "uy", "y"),
|
|
]
|
|
for i, o, v in tab:
|
|
for c in coda:
|
|
emit(i+c, o+c)
|
|
for t in tone:
|
|
emit(i+c+t, o.replace(v, addtone(v, t), 1)+c)
|
|
|
|
def onsets():
|
|
# Keep the qu-/gi- onset in the preedit so the tone lands on the rime
|
|
# nucleus, not on the onset glide (qua->quá not qúa, gia->giá not gía).
|
|
# transvi (onsetglide) knows to skip the glide; here we only need the
|
|
# composed clusters to exist so the preedit accumulates them.
|
|
vowels = set("aeiouy")
|
|
tones = set("sfrxj")
|
|
base = [(i, o) for (i, o) in list(entries)
|
|
if i and i[0] in vowels and not (set(i) & tones)]
|
|
for i, o in base:
|
|
if i[0] != 'u': # no qu+u syllable; u is the glide
|
|
emit("qu" + i, "qu" + o)
|
|
emit("gi" + i, "gi" + o)
|
|
# gi- with the i as nucleus (no following vowel): gì, gìn, gìm, ...
|
|
for c in ["", "c", "m", "n", "p", "t", "ch", "ng", "nh"]:
|
|
if c:
|
|
emit("gi" + c, "gi" + c)
|
|
|
|
vowel1()
|
|
vowel2()
|
|
vowel3()
|
|
modvowels()
|
|
modconss()
|
|
mod1tone()
|
|
tone1mod()
|
|
tone2mod()
|
|
escape()
|
|
final()
|
|
emit("\\", "\\")
|
|
onsets()
|