-
Notifications
You must be signed in to change notification settings - Fork 3
/
rime-table.js
379 lines (354 loc) · 15.3 KB
/
rime-table.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
const 所有韻列表 = [...'東冬鍾江支脂之微魚虞模齊祭泰佳皆夬灰咍廢真臻文殷元魂痕寒刪山先仙蕭宵肴豪歌麻陽唐庚耕清青蒸登尤侯幽侵覃談鹽添咸銜嚴凡'];
const 陰聲韻列表 = [...'支脂之微魚虞模齊祭泰佳皆夬灰咍廢蕭宵肴豪歌麻尤侯幽'];
const 聲調列表 = [...'平上去入'];
const 等類列表 = [...'2BA4C1'];
const 等列表 = [...'二三三四三一'];
const 類列表 = [null, 'B', 'A', null, 'C', null];
const get組 = Object.fromEntries([
'幫滂並明',
'端透定泥', '精清從心邪', '來',
'知徹澄孃', '莊初崇生俟', '以', '章昌常書船', '日',
'見溪羣疑',
'影曉匣云',
].map(母列表 => [...母列表].map(母 => [母, 母列表[0]])).flat());
const is銳音聲母 = 母 => [...'端知精莊章來日以'].includes(get組[母]);
const 所有韻目字典 = {
'東': '東董送屋',
'冬': '冬腫宋沃',
'鍾': '鍾腫用燭',
'江': '江講絳覺',
'支': '支紙寘 ',
'脂': '脂旨至 ',
'之': '之止志 ',
'微': '微尾未 ',
'魚': '魚語御 ',
'虞': '虞麌遇 ',
'模': '模姥暮 ',
'齊': '齊薺霽 ',
'祭': ' 祭 ',
'泰': ' 泰 ',
'佳': '佳蟹卦 ',
'皆': '皆駭怪 ',
'夬': ' 夬 ',
'灰': '灰賄隊 ',
'咍': '咍海代 ',
'廢': ' 廢 ',
'真': '真軫震質',
'臻': '臻隱震櫛',
'文': '文吻問物',
'殷': '殷隱焮迄',
'元': '元阮願月',
'魂': '魂混慁沒',
'痕': '痕很恨沒',
'寒': '寒旱翰末',
'刪': '刪潸諫鎋',
'山': '山產襇黠',
'先': '先銑霰屑',
'仙': '仙獮線薛',
'蕭': '蕭篠嘯 ',
'宵': '宵小笑 ',
'肴': '肴巧效 ',
'豪': '豪晧号 ',
'歌': '歌哿箇 ',
'麻': '麻馬禡 ',
'陽': '陽養漾藥',
'唐': '唐蕩宕鐸',
'庚': '庚梗敬陌',
'耕': '耕耿諍麥',
'清': '清靜勁昔',
'青': '青迥徑錫',
'蒸': '蒸拯證職',
'登': '登等嶝德',
'尤': '尤有宥 ',
'侯': '侯厚候 ',
'幽': '幽黝幼 ',
'侵': '侵寑沁緝',
'覃': '覃感勘合',
'談': '談敢闞盍',
'鹽': '鹽琰豔葉',
'添': '添忝㮇怗',
'咸': '咸豏陷洽',
'銜': '銜檻鑑狎',
'嚴': '嚴 業',
'凡': '凡范梵乏',
};
function 全角空格toNull(str) {
return str === ' ' ? null : str;
}
function _韻圖屬性(韻列表, 呼, 攝, 國際音標) {
韻列表 = [...韻列表].map((韻, 等類idx) => ({
// 一列多韻的情況
幽: '幽尤',
庚: 等類idx === 等類列表.indexOf('B') && '庚清',
嚴: '嚴凡',
}[韻] || 韻)).map(全角空格toNull);
return {
韻串: 韻列表.join(''),
韻列表: 韻列表,
韻字典: Object.fromEntries(等類列表.map((等類, i) => [等類, 韻列表[i]])),
韻目列表: 聲調列表.map((_, 聲調idx) => 韻列表.map(韻 =>
韻 && [...韻].map(單個韻 => 全角空格toNull(所有韻目字典[單個韻][聲調idx])).join(''))),
呼: 全角空格toNull(呼),
攝: 攝.trim(),
國際音標: 國際音標.trim(),
};
}
const 韻圖屬性列表 = [
_韻圖屬性(' 幽幽 尤侯', ' ', '流 ', ' u, iw'), // B、A 列實爲幽尤
_韻圖屬性(' 魚 ', '開', '遇 ', ' ə'),
_韻圖屬性(' 虞模', ' ', '遇 ', ' o'),
_韻圖屬性(' 之 ', '開', '止 ', ' ɨ'),
_韻圖屬性(' 脂脂 微 ', '開', '止 ', ' i, ɨj'),
_韻圖屬性(' 脂脂 微 ', '合', '止 ', ' wi, uj'),
_韻圖屬性('佳支支 ', '開', '止蟹', ' ie; e'),
_韻圖屬性('佳支支 ', '合', '止蟹', 'wie; we'),
_韻圖屬性('麻麻麻 歌歌', '開', '果假', ' a, ɑ'),
_韻圖屬性('麻麻麻 歌歌', '合', '果假', ' wa, wɑ'),
_韻圖屬性(' 東東', ' ', '通 ', ' uŋ'),
_韻圖屬性('江 鍾冬', ' ', '通江', ' oŋ'),
_韻圖屬性(' 蒸 蒸登', '開', '曾 ', ' ɨŋ; əŋ'),
_韻圖屬性(' 蒸 登', '合', '曾 ', 'wɨŋ; wəŋ'),
_韻圖屬性('耕 青 ', '開', '梗 ', ' eŋ'),
_韻圖屬性('耕 青 ', '合', '梗 ', 'weŋ'),
_韻圖屬性('庚庚清 ', '開', '梗 ', ' aŋ'), // B 列實爲庚清
_韻圖屬性('庚庚清 ', '合', '梗 ', 'waŋ'), // B 列實爲庚清
_韻圖屬性(' 陽唐', '開', '宕 ', ' ɑŋ'),
_韻圖屬性(' 陽唐', '合', '宕 ', 'wɑŋ'),
_韻圖屬性('皆祭祭齊廢咍', '開', '蟹 ', ' ej, əj'),
_韻圖屬性('皆祭祭齊廢灰', '合', '蟹 ', 'wej, wəj'),
_韻圖屬性('夬 泰', '開', '蟹 ', ' aj, ɑj'),
_韻圖屬性('夬 泰', '合', '蟹 ', 'waj, wɑj'),
_韻圖屬性('臻真真 殷 ', '開', '臻 ', ' in, ɨn'),
_韻圖屬性(' 真真 文 ', '合', '臻 ', 'win, un'),
_韻圖屬性('山仙仙先元痕', '開', '臻山', ' en, ən'),
_韻圖屬性('山仙仙先元魂', '合', '臻山', 'wen, wən'),
_韻圖屬性('刪 寒', '開', '山 ', ' an, ɑn'),
_韻圖屬性('刪 寒', '合', '山 ', 'wan, wɑn'),
_韻圖屬性('肴宵宵蕭 豪', '開', '效 ', ' ew, əw; aw'),
_韻圖屬性(' 侵侵 ', '開', '深 ', ' im, ɨm'),
_韻圖屬性('咸鹽鹽添嚴覃', ' ', '咸 ', ' em, əm'), // C 列實爲嚴凡
_韻圖屬性('銜 談', '開', '咸 ', ' am, ɑm'),
];
const 開合中立圖的韻的呼 = {
'幽': '開',
'虞': '合',
'咸': '開', '鹽': '開', '添': '開', '嚴': '開', '覃': '開',
};
function 韻to單個韻(韻, 母) {
if (韻 === '幽尤') return 韻[is銳音聲母(母) ? 1 : 0];
if (韻 === '庚清') return 韻[is銳音聲母(母) && get組[母] !== '莊' ? 1 : 0];
if (韻 === '嚴凡') return 韻[get組[母] === '幫' ? 1 : 0];
return 韻;
}
const 各等類聲母列表 = [
'幫滂並明知徹澄孃來莊初崇生俟 見溪 疑影曉匣', // 2
'幫滂並明知徹澄孃來章昌常書船日 見溪羣疑影曉云', // B
'幫滂並明 精清從心邪 以見溪羣疑影曉 ', // A
'幫滂並明端透定泥來精清從心 見溪 疑影曉匣', // 4
'幫滂並明 見溪羣疑影曉云', // C
'幫滂並明端透定泥來精清從心 見溪 疑影曉匣', // 1
].map(聲母列表 => [...聲母列表].map(全角空格toNull));
function _坐標(韻圖idx, 等類idx, 聲母idx, 聲調idx) {
return {
韻圖idx: 韻圖idx,
等類idx: 等類idx,
聲母idx: 聲母idx,
聲調idx: 聲調idx,
toString() { return `${String(韻圖idx + 1).padStart(2, '0')}${等類idx + 1}${String(聲母idx + 1).padStart(2, '0')}${聲調idx + 1}`; },
to新韻圖編碼() { return this.toString(); },
to音韻地位(保留非法結果 = false, 轉換特殊格子 = false) {
let 韻圖屬性 = 韻圖屬性列表[this.韻圖idx];
let 母 = 各等類聲母列表[this.等類idx][this.聲母idx];
let 呼 = 韻圖屬性.呼;
let 等 = 等列表[this.等類idx];
let 類 = 類列表[this.等類idx];
let 韻 = 韻圖屬性.韻列表[this.等類idx];
let 聲 = 聲調列表[this.聲調idx];
let 組 = get組[母];
// 臻韻只是虛設
if (韻 === '臻') {
if (組 === '莊') {
等 = '三';
} else {
韻 = null;
}
}
// 處理銳音聲母三等
if (is銳音聲母(母)) {
// 無二等韻目的情況下,莊組二等格子里排的是三等韻
if (組 === '莊' && !韻) {
韻 = 韻to單個韻(韻圖屬性.韻字典['B'], 母);
等 = '三';
}
// 銳音聲母 C 類韻
if (等 === '三' && !韻) 韻 = 韻圖屬性.韻字典['C'];
// 莊組合口拼支韻不拼佳韻
if (組 === '莊' && 韻 === '佳' && 呼 === '合') {
韻 = '支';
等 = '三';
}
類 = null;
}
韻 = 韻to單個韻(韻, 母);
呼 = 開合中立圖的韻的呼[韻] || 呼;
if (聲 === '入' && 陰聲韻列表.includes(韻)) 聲 = null;
// 處理脣音聲母
if (組 === '幫') {
if (類 === 'C' || 等 === '一') {
if ([...'歌陽唐泰寒'].includes(韻)) {
// 脣音拼低元音歸開口
if (呼 === '合') 母 = null;
} else if ([...'蒸登'].includes(韻)) {
// 脣音蒸登韻暫依習慣歸開口
if (呼 === '合') 母 = null;
} else if (!韻圖屬性.國際音標.endsWith('w') && !韻圖屬性.國際音標.endsWith('m')) {
// 脣音拼非前非低元音歸合口。-w、-m 韻沒有合口圖,排除
if (呼 === '開') 母 = null;
}
} else {
// 脣音拼前元音歸開口
if (呼 === '合') 母 = null;
}
呼 = null;
}
// 特殊格子
if (轉換特殊格子 && !韻 && 等 === '四' && ['端', '來'].includes(組)) {
if (['脂', '麻'].includes(韻圖屬性.韻字典['A'])) { // 802 爹、2237 地
韻 = 韻圖屬性.韻字典['A'];
} else if (['佳', '庚'].includes(韻圖屬性.韻字典['2'])) { // 1423a 箉、1871 打、1872 冷
韻 = 韻圖屬性.韻字典['2'];
等 = '二';
}
}
if (母 && 韻 && 聲) {
let 邊緣地位種類指定 = [];
if (母 === '云' && 呼 === '開') 邊緣地位種類指定.push('云母開口');
if (母 === '匣' && 等 === '三') 邊緣地位種類指定.push('匣母三等');
if ('羣邪俟'.includes(母) && 等 !== '三') 邊緣地位種類指定.push('羣邪俟母非三等');
try {
return new TshetUinh.音韻地位(母, 呼, 等, 類, 韻, 聲, 邊緣地位種類指定);
} catch (error) {
console.log(error.message);
}
} else if (!保留非法結果) return null;
// 非法的情況下,返回簡易的音韻地位類
return {
母, 呼, 等, 類, 韻, 聲,
get 描述() {
const { 母, 呼, 等, 類, 韻, 聲 } = this;
return [母, 呼, 等, 類, 韻, 聲].join('');
},
};
},
};
}
function get坐標from新韻圖編碼(新韻圖編碼) {
return _坐標(
Number(新韻圖編碼.slice(0, 2)),
Number(新韻圖編碼[2]),
Number(新韻圖編碼.slice(3, 5)),
Number(新韻圖編碼[5]),
);
}
function get坐標from音韻地位(音韻地位) {
let { 母, 呼, 等, 類, 韻, 聲 } = 音韻地位;
let 組 = get組[母];
let 韻圖idxs = 韻圖屬性列表.flatMap((韻圖屬性, 韻圖idx) => 韻圖屬性.韻串.includes(韻) ? [韻圖idx] : []);
let 韻圖idx = 韻圖idxs[韻圖idxs.length > 1 && (
呼 === '合' ||
組 === '幫' && (類 === 'C' || 等 === '一') && ![...'歌陽唐泰寒', ...'蒸登'].includes(韻) // 脣音拼低元音歸開口,脣音蒸登韻暫依習慣歸開口
) ? 1 : 0];
let 等類idx = 等 !== '三' ? 等列表.indexOf(等) :
類 ? 類列表.indexOf(類) :
['知', '來'].includes(組) ? 等類列表.indexOf('B') : 各等類聲母列表.findIndex(聲母列表 => 聲母列表.includes(母));
let 聲母idx = 各等類聲母列表[等類idx].indexOf(母);
let 聲調idx = 聲調列表.indexOf(聲);
// 特殊格子
if (組 === '端' && 等 === '二' || 音韻地位.描述 === '來開二庚上') // 1423a 箉、1871 打、1872 冷
等類idx = 等類列表.indexOf('4');
if (is銳音聲母(母) && 等 === '三' && 韻 === '廢') // 1454 茝、1460 佁、1464 䑂
等類idx = 等類列表.indexOf('C');
if (母 === '明' && 韻 === '尤') // 1016 謀、3059 莓
等類idx = 等類列表.indexOf('1');
if (聲母idx === -1) 聲母idx = 各等類聲母列表[等類列表.indexOf('1')].indexOf(母);
return _坐標(韻圖idx, 等類idx, 聲母idx, 聲調idx);
}
// 格子的合法性指該格子代表的聲、韻、調組合是否合乎音系規則
//
// ○ 強合法
// ◌ 稀有合法
// △ 弱合法
// ◎ 弱非法
// ● 強非法
//
// 合法格子若無字,則爲偶然空缺(accidental gap)
// 非法格子若無字,則爲系統空缺(systematic gap)
// 非法格子若有字,則爲邊緣音節
// 強非法格子不應有字
// 無效格子表示沒有任何音韻地位能填入該格子
// 歷史原因造成的合法格子缺字直接算作稀有格子,不再額外考慮歷史因素
const 合法性規則列表 = [
['●', '「陰聲韻無入聲」'],
['●', '「聲母和等類不能搭配的情況」'],
['●', '「韻和等類不能搭配的情況」'],
['●', '幽蒸韻 無 (開口 B類 或 非 開口 非 B類)'], // 「幽蒸無重紐」
['●', '祭泰夬廢韻 僅 去聲'],
['●', '陽唐庚耕清青蒸登韻 銳音 合口 僅 以母 清韻'],
['●', '四等 合口 無 銳音'],
['●', '云母 開口 僅 宵侵鹽韻'],
['●', '麻韻 三等 僅 銳音 開口'],
['●', '侵鹽韻 A類 僅 影母'],
['●', '俟母 僅 之韻'],
['◎', '來母 無 二等'],
['◎', '歌韻 三等 僅 見影組 平聲'],
['◎', '痕韻 無 銳音'],
['◎', '幫組 無 蕭添韻'],
['◎', '祭韻 見影組 A類 僅 影母'],
['◎', '祭韻 幫組 無 B類'],
['△', '冬韻 鈍音 少 舒聲'],
['△', '船母 無 尤之東陽祭宵鹽韻'],
['△', '麻韻 三等 無 知組'],
['△', '蒸韻 合口 僅 入聲'],
['△', '東韻 三等 無 上聲'],
['△', '佳麻皆夬韻 合口 少 知組'],
['△', '山刪韻 合口 舒聲 少 知組'],
['◌', '銜韻 少 知組'],
['◌', '云母 無 鍾韻'],
['◌', '脂仙宵韻 見影組 開口 A類 僅 影母'],
['◌', '幫組 少 咸覃銜談韻'],
['◌', '脂韻 莊組 僅 生母'],
['◌', '真臻韻 莊組 合口 僅 生母 入聲'],
['◌', '蒸韻 少 上聲'],
['◌', '登韻 合口 無 上去聲'],
['◌', '冬韻 少 上聲'],
['◌', '皆韻 上聲 僅 見影組 開口'],
['◌', '邪母 無 虞東宵韻'],
['○', '「其餘所有」'],
].map(([合法性圖標, 規則描述], i) => ({
圖標: 合法性圖標,
規則描述: 規則描述,
規則編號: i + 1,
音韻表達式: 規則描述.startsWith('「') ? null :
規則描述.includes('僅') ? `(${規則描述.replace(' 僅 ', ') 非 (')})` : 規則描述.replace(/ [無少] /, ' '),
toString() { return `${this.規則編號}${this.圖標} ${this.規則描述}`; },
}));
function get合法性from音韻地位(音韻地位) {
if (!音韻地位.聲) return 合法性規則列表[0];
if (!音韻地位.母) return 合法性規則列表[1];
if (!音韻地位.韻) return 合法性規則列表[2];
return 合法性規則列表.slice(3, -1).find(合法性規則 => 音韻地位.屬於(合法性規則.音韻表達式)) || 合法性規則列表.at(-1);
}
function create空韻圖() {
return 韻圖屬性列表.map((_, 韻圖idx) =>
等類列表.map((_, 等類idx) =>
各等類聲母列表[0].map((_, 聲母idx) =>
聲調列表.map((_, 聲調idx) => {
let 坐標 = _坐標(韻圖idx, 等類idx, 聲母idx, 聲調idx);
return {
坐標: 坐標,
合法性: get合法性from音韻地位(坐標.to音韻地位(true)),
小韻列表: [],
toString() { return `${this.坐標} ${this.小韻列表}`; },
};
}))));
}