diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7494b24..dea01fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 }} diff --git a/setup.py b/setup.py index 6ad0599..b6754ed 100644 --- a/setup.py +++ b/setup.py @@ -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', diff --git "a/src/QieyunEncoder/_\351\237\263\344\275\215\351\205\215\345\210\227\350\246\217\345\211\207\350\241\250.py" "b/src/QieyunEncoder/_\351\237\263\344\275\215\351\205\215\345\210\227\350\246\217\345\211\207\350\241\250.py" new file mode 100644 index 0000000..00726ed --- /dev/null +++ "b/src/QieyunEncoder/_\351\237\263\344\275\215\351\205\215\345\210\227\350\246\217\345\211\207\350\241\250.py" @@ -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), + # 原因不明 +) diff --git "a/src/QieyunEncoder/\345\270\270\351\207\217.py" "b/src/QieyunEncoder/\345\270\270\351\207\217.py" index e91d4b2..f2da613 100644 --- "a/src/QieyunEncoder/\345\270\270\351\207\217.py" +++ "b/src/QieyunEncoder/\345\270\270\351\207\217.py" @@ -41,4 +41,5 @@ class 常量: 輕脣韻: str = '東鍾微虞廢文元陽尤凡' + 陰聲韻: str = '支脂之微魚虞模齊祭泰佳皆夬灰咍廢蕭宵肴豪歌麻尤侯幽' 次入韻: str = '祭泰夬廢' diff --git "a/src/QieyunEncoder/\351\237\263\351\237\273\345\234\260\344\275\215.py" "b/src/QieyunEncoder/\351\237\263\351\237\273\345\234\260\344\275\215.py" index 572b3d3..1ff6587 100644 --- "a/src/QieyunEncoder/\351\237\263\351\237\273\345\234\260\344\275\215.py" +++ "b/src/QieyunEncoder/\351\237\263\351\237\273\345\234\260\344\275\215.py" @@ -40,7 +40,7 @@ .. hint:: 不要與中古後期三十六字母混淆。 - + 中古後期三十六字母: @@ -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 母到音 @@ -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(等) @@ -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) -> 音韻地位: ''' 將音韻編碼轉換爲音韻地位。 ''' @@ -579,7 +583,7 @@ def from編碼(編碼: str): return 音韻地位(母, 呼, 等, 重紐, 韻, 聲) @staticmethod - def from描述(描述: str): + def from描述(描述: str) -> 音韻地位: ''' 將音韻描述或最簡音韻描述轉換爲音韻地位。 ''' @@ -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.描述 + '>'