Skip to content

Implement 音韻地位.合法性 #5

New issue

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

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

Already on GitHub? Sign in to your account

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