Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Implement 音韻地位.合法性 #5

Open
wants to merge 12 commits into
base: feat-0.5.x
Choose a base branch
from
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ["3.7", "3.8", "3.9", "3.10.0-beta.3"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
'Natural Language :: Chinese (Traditional)',
'License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9'
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
],
keywords='middle-chinese historical-linguistics qieyun',
packages=find_packages('src'),
package_dir={'': 'src'},
python_requires='>=3.6, <4',
python_requires='>=3.7, <4',
entry_points={},
project_urls={
'Bug Reports': 'https://github.com/nk2028/qieyun-encoder-python/issues',
Expand Down
119 changes: 119 additions & 0 deletions src/QieyunEncoder/_音位配列規則表.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-


from enum import IntEnum


class 合法性等級(IntEnum):
無效, 強非法, 弱非法, 弱合法, 稀有合法, 強合法 = range(6)

@property
def 字符串(self) -> str:
return self.name


# 以下規則只適用於更優音韻地位
# 每條規則是三元組 (合法性等級, 禁止的情況, 排除的情況)
# 在規則下進行簡要解釋,並舉出違反規則的例外小韻(根據數據中對一些爭議小韻的處理不同,例外小韻可能有差異)
音位配列規則表 = (
(合法性等級.無效, '邪章昌船書常日以羣母 一二四等', None),
# 這些聲母只能拼三等,也不存在對應的能拼非三等的聲母

(合法性等級.強非法, '次入韻', '去聲'),
# 因爲次入四韻都是由上古去聲韻尾變來。例外小韻:茝栘臡佁䑂倄

(合法性等級.強非法, '幫組 之韻', None),
# 因爲之韻在音系上相當於開合分韻的開口韻,幫組不能拼

(合法性等級.強非法, '陽唐庚耕清青蒸登韻 銳音 合口', '以母 清韻'),
# 分開合的 -ng 尾韻銳音無合口(以母清韻除外)。例外小韻:𢷾𦳮騂硦㘀

(合法性等級.強非法, '四等 合口 銳音', None),
# 因爲上古無唇化銳音聲母

(合法性等級.強非法, '云母 開口', '宵侵鹽韻'),
# 分開合的韻云母無開口,因爲云母上古就是 *w。例外小韻:矣漹

(合法性等級.強非法, '麻韻 三等', '銳音 開口'),
# 麻歌屬於銳鈍分韻,且合口沒有上古來源。例外小韻:乜𦣛

(合法性等級.強非法, '侵鹽韻 重紐A類', '影母'),
# 脂祭真仙侵鹽六韻見系(除影母外)開口缺少重紐。特別地,侵鹽韻僅影母有可靠重紐
# 這是因爲上古見系聲母拼這些韻的 A 類開口韻的字到了後世,聲母常常會被腭化到章組

(合法性等級.強非法, '俟母', '之韻'),
# 俟母之韻可能是成音節捲舌近音(ɻ̍)特殊演變的結果,類似現代漢語的 er 不能拼其他聲母

(合法性等級.弱非法, '來母 二等', None),
# 因爲來母上古就是 *r。例外小韻:藞瀧犖礐䐯斕𡰠顟臉

(合法性等級.弱非法, '歌韻 三等', '見影組 平聲'),
# 見系歌三本無上古来源,但因爲見組聲母三等非三等音值有別,所以引入見組歌三專用於音譯,且不用仄聲

(合法性等級.弱非法, '痕韻 銳音', None),
# 沒有上古来源,因为上古非三等 *ən 在銳音后会前化,入先韵。例外小韻:吞

(合法性等級.弱非法, '幫組 蕭添韻', None),
# m 與前元音相拼時更接近 n,因此唇音 + em 先異化成了 en。例外小韻:𡕢

(合法性等級.弱非法, '祭韻 見影組 重紐A類', '影母'),
# 脂祭真仙侵鹽六韻見系(除影母外)開口缺少重紐。例外小韻:藝

(合法性等級.弱非法, '祭韻 幫組 重紐B類', None),
# 特別地,祭韻幫組也無重紐

(合法性等級.弱合法, '冬韻 鈍音 舒聲', None),
# 在上古就缺乏,原因不明。例外小韻:𪁪雺攻䃔䃔

(合法性等級.弱合法, '船母 尤之東陽祭宵鹽韻', None),
# 除魚虞鍾外,船母不拼三 C 韻;此外船母也不拼祭宵鹽韻。原因不明

(合法性等級.弱合法, '麻韻 三等 知組', None),
# 原因不明。例外小韻:爹

(合法性等級.弱合法, '蒸韻 合口 舒聲', None),
# 因爲蒸韻合口字大多數併入了東韻

(合法性等級.弱合法, '東韻 三等 上聲', None),
# -ng 尾上聲字少,具體原因不明

(合法性等級.弱合法, '佳麻皆夬韻 合口 知組', None),
# 原因不明。例外小韻:檛䊬顡尵

(合法性等級.弱合法, '山刪韻 合口 舒聲 知組', None),
# 原因不明。例外小韻:窀奻奻

(合法性等級.稀有合法, '銜韻 知組', None),
# 原因不明。例外小韻:𠗨

(合法性等級.稀有合法, '云母 鍾韻', None),
# 沒有上古來源,因爲上古云母 *w 排斥元音圓唇的鍾韻

(合法性等級.稀有合法, '脂仙宵韻 見影組 開口 重紐A類', '影母'),
# 脂祭真仙侵鹽六韻見系(除影母外)開口缺少重紐。例外小韻:棄鬐咦甄孑遣譴蹻翹翹
# 特別地,真韻的例外小韻太多,因此不視爲非法。另外宵韻的見系開口重紐也很少,列入規則

(合法性等級.稀有合法, '幫組 咸覃銜談韻', None),
# 因爲唇音聲母會掩蔽 -m 尾的音色,將 -m 尾的部位異化。例外小韻:𨂝埿姏㛧

(合法性等級.稀有合法, '脂韻 莊組', '生母'),
# 原因不明。例外小韻:㿷

(合法性等級.稀有合法, '真韻 莊組 合口', '生母 入聲'),
# 原因不明。例外小韻:𠭴

(合法性等級.稀有合法, '蒸韻 上聲', None),
# -ng 尾上聲字少,具體原因不明。例外小韻:庱拯㱡殑

(合法性等級.稀有合法, '登韻 合口 上去聲', None),
# -ng 尾上聲字少,具體原因不明,且登合僅見系字

(合法性等級.稀有合法, '冬韻 上聲', None),
# -ng 尾上聲字少,具體原因不明。《切韻》冬韻無對應的上聲韻目。例外小韻:湩

(合法性等級.稀有合法, '皆韻 上聲', '見影組 開口'),
# 原因不明

(合法性等級.稀有合法, '邪母 虞東宵韻', None),
# 原因不明
)
1 change: 1 addition & 0 deletions src/QieyunEncoder/常量.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ class 常量:

輕脣韻: str = '東鍾微虞廢文元陽尤凡'

陰聲韻: str = '支脂之微魚虞模齊祭泰佳皆夬灰咍廢蕭宵肴豪歌麻尤侯幽'
次入韻: str = '祭泰夬廢'
149 changes: 139 additions & 10 deletions src/QieyunEncoder/音韻地位.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
.. hint::

不要與中古後期三十六字母混淆。

中古後期三十六字母:

<table class="big-table">
Expand Down Expand Up @@ -153,11 +153,12 @@
其中,仄表示上去入聲,舒表示平上去聲。
'''

from __future__ import annotations # PEP 563, for Python < 3.10
import re
from typing import Optional
from typing import Optional, Tuple

from .常量 import 常量
from ._母對應的標準等 import 母對應的標準等
from ._音位配列規則表 import 合法性等級, 音位配列規則表
from .工具.母到清濁 import 母到清濁
from .工具.母到組 import 母到組
from .工具.母到音 import 母到音
Expand Down Expand Up @@ -475,7 +476,7 @@ def equal聲(聲: str) -> bool:
@staticmethod
def 驗證(母: str, 呼: Optional[str], 等: str, 重紐: Optional[str], 韻: str, 聲: str):
'''
驗證給定的音韻地位六要素是否合法
初步驗證給定的音韻地位六要素是否合法
'''
assert len(母) == 1 and 母 in 常量.所有母, 'Unexpected 母: ' + repr(母)
assert len(等) == 1 and 等 in 常量.所有等, 'Unexpected 等: ' + repr(等)
Expand Down Expand Up @@ -511,8 +512,11 @@ def 驗證(母: str, 呼: Optional[str], 等: str, 重紐: Optional[str], 韻: s
elif 韻 in 常量.二三等韻:
assert 等 in '二三', 'Unexpected 等: ' + repr(等)

if 韻 in 常量.陰聲韻:
assert 聲 != '入', 'Unexpected 聲: ' + repr(聲)

@staticmethod
def from編碼(編碼: str):
def from編碼(編碼: str) -> 音韻地位:
'''
將音韻編碼轉換爲音韻地位。
'''
Expand Down Expand Up @@ -579,7 +583,7 @@ def from編碼(編碼: str):
return 音韻地位(母, 呼, 等, 重紐, 韻, 聲)

@staticmethod
def from描述(描述: str):
def from描述(描述: str) -> 音韻地位:
'''
將音韻描述或最簡音韻描述轉換爲音韻地位。
'''
Expand Down Expand Up @@ -612,13 +616,138 @@ def from描述(描述: str):

return 音韻地位(母, 呼, 等, 重紐, 韻, 聲)

def is_normal(self):
def 合法性(self) -> Tuple[str, Optional[音韻地位]]:
'''
是 normal 的音韻地位。
聲、韻、調組合的合法性。
如果有等價但更優的音韻地位能作爲替代,那麼也返回更優音韻地位,合法性取原地位和更優地位中的較差者。

合法性分爲 6 類,分別是:

例如,端母二等不是 normal 的音韻地位。
- 無效:聲母和韻母的固有性質互相衝突,無法拼合的情況。如:徹母齊韻、昌母山韻
- 強非法:範圍廣(> 50 個音節)且例外少(< 4%)的音系規則所禁止的情況。如:廢韻上聲、銳音四等合口
- 弱非法:範圍小(≤ 50 個音節)或例外多(4% ~ 15%)的音系規則所禁止的情況。如:來母二等、銳音痕韻
- 弱合法:語音學上沒有明確的約束,但範圍內有字率低于 15% 且都是僻字的情況。如:蒸合舒聲、東三上聲
- 稀有合法:語音學上沒有明確的約束,但範圍內有字率低于 15% 而不都是僻字的情況。如:云母鍾韻、知組銜韻
- 強合法:其餘情況。強合法音節的平均有字率約爲 75%

非法音節的空缺是系统空缺(systematic gap),非法音節範圍內的字屬於边缘音节。合法音節的空缺是偶然空缺(accidental gap)。
'''
return self.等 in 母對應的標準等[self.母]
母 = self.母
呼 = self.呼
等 = self.等
重紐 = self.重紐
韻 = self.韻
聲 = self.聲
音韻地位.驗證(母, 呼, 等, 重紐, 韻, 聲)

合法性 = 合法性等級.強合法

類隔 = {
'四': dict(zip('知徹澄孃莊初崇生俟云', '端透定泥精清從心邪匣')),
'一': dict(zip('知徹澄孃莊初崇生俟云', '端透定泥精清從心邪匣')),
'二': dict(zip('端透定泥精清從心邪云', '知徹澄孃莊初崇生俟匣')),
'三': dict(zip('端透定泥匣', '知徹澄孃云')),
}
開合分韻 = dict(zip(
'魚咍痕殷嚴',
'虞灰魂文凡'
))
銳音分韻 = dict(zip(
# 銳音不能拼第一行韻,而第二行相應的韻是其更優音韻地位
# 注意第一行韻不都是 C₁ 類三等韻,例外是恰恰相反的幽/尤之分
'微廢文殷元歌幽嚴凡',
'脂祭真真仙麻尤鹽鹽'
))
莊三化二韻 = {
# 有對應二等韻的三等韻拼莊組時以二等韻作爲更優音韻地位,支祭仙韻合口除外
None: {'鍾': '江'},
'開': dict(zip(
'支祭仙宵庚麻清鹽',
'佳皆山肴庚麻庚咸'
)),
'合': dict(zip(
'庚麻清',
'庚麻庚'
)),
}

# TODO: 銳音移入常量後移除
銳音 = '端透定泥來知徹澄孃精清從心邪莊初崇生俟章昌常書船日以'

# 首先生成等價但更優的音韻地位
if 母 in 類隔[等]:
母 = 類隔[等][母]
合法性 = min(合法性, 合法性等級.無效)

if 母到組(母) == '幫' and 韻 in 開合分韻:
韻 = 開合分韻[韻]
合法性 = min(合法性, 合法性等級.強非法)
elif 母到組(母) != '幫' and 韻 == '凡':
韻 = '嚴'
呼 = '開'
合法性 = min(合法性, 合法性等級.強非法)

if 母 in 銳音 and 等 == '三' and 韻 in 銳音分韻:
韻 = 銳音分韻[韻]
合法性 = min(合法性, 合法性等級.強非法)

if 母到組(母) == '莊':
if 韻 in 莊三化二韻[呼] and 等 == '三':
if 韻 in '鍾宵麻清':
# 此四韻無字,視爲非法
合法性 = min(合法性, 合法性等級.強非法)
elif 韻 == '鹽':
# 鹽韻字少
合法性 = min(合法性, 合法性等級.弱合法)
韻 = 莊三化二韻[呼][韻]
等 = '二'
elif 韻 == '佳' and 呼 == '合':
韻 = '支'
等 = '三'
合法性 = min(合法性, 合法性等級.強非法)
# 真臻之分也在此處理
elif 韻 == '真' and 呼 == '開':
韻 = '臻'
合法性 = min(合法性, 合法性等級.弱非法)
# 臻韻合口已被 驗證() 攔截,無需處理
else:
if 韻 == '臻':
# 莊組以外聲母不能拼臻韻,屬於無效組合。這裡以真B韻作爲更優音韻地位
韻 = '真'
if 母 in 常量.重紐母:
重紐 = 'B'
合法性 = min(合法性, 合法性等級.無效)

# 庚三清之分
if 韻 == '清' and (重紐 == 'B' or 母 == '云'):
韻 = '庚'
重紐 = None
合法性 = min(合法性, 合法性等級.無效)
elif 韻 == '庚' and 等 == '三' and 母 in 銳音 and 母到組(母) != '莊':
韻 = '清'
合法性 = min(合法性, 合法性等級.無效)

更優音韻地位 = 音韻地位(母, 呼, 等, 重紐, 韻, 聲)

# 接下來對更優音韻地位適用音位配列規則,檢驗其合法性
for 音位配列規則 in 音位配列規則表:
# TODO: 音韻表達式增加陰聲韻、銳鈍音之後移除以下部分
音位配列規則 = list(音位配列規則)
for i in (1, 2):
if not 音位配列規則[i]:
continue
音位配列規則[i] = 音位配列規則[i].replace(
'次入韻', '祭泰夬廢韻').replace(
'鈍音', '幫滂並明見溪羣疑影曉匣云母').replace(
'銳音', '端透定泥來知徹澄孃精清從心邪莊初崇生俟章昌常書船日以母')
# TODO: 音韻表達式增加陰聲韻、銳鈍音之後移除以上部分

if 更優音韻地位.屬於(音位配列規則[1]) and not (音位配列規則[2] and 更優音韻地位.屬於(音位配列規則[2])):
合法性 = min(合法性, 音位配列規則[0])
break
if 更優音韻地位 == self:
更優音韻地位 = None
return 合法性.字符串, 更優音韻地位

def __repr__(self) -> str:
return '<音韻地位 ' + self.描述 + '>'
Expand Down