From 3cdb4b75ea208dfd8f30276dc1d204983b0b469a Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Thu, 5 Aug 2021 04:51:53 +0800 Subject: [PATCH 1/2] =?UTF-8?q?Add=20bracket=20support=20and=20=E5=88=A4?= =?UTF-8?q?=E6=96=B7=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\345\270\270\351\207\217.py" | 6 +- ...63\351\237\273\345\234\260\344\275\215.py" | 314 +++++++++++------- test/main.py | 93 +++++- 3 files changed, 292 insertions(+), 121 deletions(-) diff --git "a/src/QieyunEncoder/\345\270\270\351\207\217.py" "b/src/QieyunEncoder/\345\270\270\351\207\217.py" index 874be4d..0dba244 100644 --- "a/src/QieyunEncoder/\345\270\270\351\207\217.py" +++ "b/src/QieyunEncoder/\345\270\270\351\207\217.py" @@ -24,6 +24,10 @@ class 常量: 所有韻: str = '東冬鍾江支脂之微魚虞模齊祭泰佳皆夬灰咍廢眞臻文欣元魂痕寒刪山仙先蕭宵肴豪歌麻陽唐庚耕清青蒸登尤侯幽侵覃談鹽添咸銜嚴凡' 所有聲: str = '平上去入' + 所有音: str = '脣舌齒牙喉' + 所有攝: str = '通江止遇蟹臻山效果假宕梗曾流深咸' + 所有組: str = '幫端知精莊章見影' + 重紐母: str = '幫滂並明見溪羣疑影曉' 重紐韻: str = '支脂祭眞仙宵清侵鹽' @@ -40,5 +44,5 @@ class 常量: 二三等韻: str = '麻庚' 輕脣韻: 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 f6169b1..e17301e 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" @@ -156,24 +156,31 @@ ''' import re -from typing import Optional +from typing import TypeVar, NewType, Union, List, Tuple, Optional from .常量 import 常量 from ._拓展音韻屬性 import 母到清濁, 母到音, 母到組, 韻到攝 -編碼表 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' +編碼表 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$_' 韻順序表 = '東_冬鍾江支脂之微魚虞模齊祭泰佳皆夬灰咍廢眞臻文欣元魂痕寒刪山仙先蕭宵肴豪歌_麻_陽唐庚_耕清青蒸登尤侯幽侵覃談鹽添咸銜嚴凡' 解析音韻描述 = re.compile('([%s])([%s]?)([%s]?)([%s]?)([%s])([%s])' % ( 常量.所有母, 常量.所有呼, 常量.所有等, 常量.所有重紐, 常量.所有韻, 常量.所有聲)) +特別編碼 = {0: ('東', '一'), 1: ('東', '三'), 37: ('歌', '一'), 38: ('歌', '三'), + 39: ('麻', '二'), 40: ('麻', '三'), 43: ('庚', '二'), 44: ('庚', '三')} + +T = TypeVar('T') +RecursiveArray = NewType( + 'RecursiveArray', List[Tuple[Optional[Union[str, bool]], Union[T, 'RecursiveArray']]]) + class 音韻地位: ''' 切韻音系音韻地位。 ''' - def __init__(self, 母, 呼, 等, 重紐, 韻, 聲): + def __init__(self, 母: str, 呼: Optional[str], 等: str, 重紐: Optional[str], 韻: str, 聲: str): 音韻地位.驗證(母, 呼, 等, 重紐, 韻, 聲) self.母 = 母 @@ -239,6 +246,20 @@ def 攝(self) -> str: ''' return 韻到攝[self.韻] + @property + def 韻別(self) -> str: + ''' + 韻別(陰聲韻、陽聲韻、入聲韻)。 + + ```python + >>> Qieyun.音韻地位.from描述('幫三凡入').韻別 + '入' + >>> Qieyun.音韻地位.from描述('羣開三A支平').韻別 + '陰' + ``` + ''' + return '陰' if self.韻 in 常量.陰聲韻 else '入' if self.聲 == '入' else '陽' + @property def 描述(self) -> str: ''' @@ -377,6 +398,34 @@ def 屬於(self, 表達式: str) -> bool: ''' 判斷音韻地位是否符合給定的音韻表達式。 + Args: + 表達式 (str): 描述音韻地位的字串。 + + 字串中音韻地位的描述格式: + + * 音韻地位六要素:`……母`, `……等`, `……韻`, `……聲`, `開口`, `合口`, `開合中立`, `重紐A類`, `重紐B類`, `不分重紐` + * 拓展音韻地位:`……組`, `……音`, `……攝`, `全清`, `次清`, `全濁`, `次濁`, `清音`, `濁音` + * 其他表達式:`陰聲韻`, `陽聲韻`, `入聲韻`, `輕脣韻`, `次入韻`, `仄聲`, `舒聲` + + 支援的運算子: + + * AND 運算子:`且`, `and`, `&`, `&&` + *   OR 運算子:`或`, `or`, `|`, `||` + *  NOT 運算子:`非`, `not`, `~`, `!` + * 括號:`(……)`, `(……)` + + 各表達式及運算子之間以空格隔開。 + + AND 運算子可省略。 + + 例子:`(端精組 且 入聲) 或 (以母 且 四等 且 去聲)` 與 `端精組 入聲 或 以母 四等 去聲` 同義。 + + Returns: + bool: 若描述音韻地位的字串符合該音韻地位,回傳 `True`;否則回傳 `False`。 + + Raises: + AssertionError: `無效的表達式`, `表達式為空`, `非預期的運算子`, `非預期的閉括號`, `括號未匹配` + ```python >>> Qieyun.音韻地位.from描述('幫三凡入').屬於('章母') False @@ -386,87 +435,141 @@ def 屬於(self, 表達式: str) -> bool: True ``` ''' - def inner(q: str): - if q.endswith('母'): - 母們 = q[:-1] - assert len(母們) > 0, '未指定母' - for 母 in 母們: - assert 母 in 常量.所有母, 母 + '母不存在' - return self.母 in 母們 - - if q.endswith('等'): - 等們 = q[:-1] - assert len(等們) > 0, '未指定等' - for 等 in 等們: - assert 等 in '一二三四', 等 + '等不存在' - return self.等 in 等們 - - if q.endswith('韻'): - 韻們 = q[:-1] - assert len(韻們) > 0, '未指定韻' - for 韻 in 韻們: - assert 韻 in 常量.所有韻, 韻 + '韻不存在' - return self.韻 in 韻們 - - if q.endswith('聲'): - 聲們 = q[:-1] - assert len(聲們) > 0, '未指定聲' - - def equal聲(聲: str) -> bool: - if 聲 in '平上去入': - return self.聲 == 聲 - if 聲 == '仄': - return self.聲 != '平' - if 聲 == '舒': - return self.聲 != '入' - raise AssertionError(聲 + '聲不存在') - return any(equal聲(聲) for 聲 in 聲們) - - if q.endswith('組'): - 組們 = q[:-1] - assert len(組們) > 0, '未指定組' - # TODO: 所有組 - # for 組 in 組們: - # assert 組 in 所有組, 組 + '組不存在' - return self.組 is not None and self.組 in 組們 - - if q.endswith('音'): - 音們 = q[:-1] - assert len(音們) > 0, '未指定音' - for 音 in 音們: - assert 音 in '脣舌牙齒喉', 音 + '音不存在' - return self.音 in 音們 - - if q.endswith('攝'): - 攝們 = q[:-1] - assert len(攝們) > 0, '未指定攝' - # TODO: 所有攝 - # for 攝 in 攝們: - # assert 攝 in 所有攝, 攝 + '攝不存在' - return self.攝 in 攝們 - - if q == '開口': - return self.呼 == '開' - if q == '合口': - return self.呼 == '合' - if q == '開合中立': - return self.呼 is None - if q == '重紐A類': - return self.重紐 == 'A' - if q == '重紐B類': - return self.重紐 == 'B' - if q == '全清': - return self.清濁 == '全清' - if q == '次清': - return self.清濁 == '次清' - if q == '全濁': - return self.清濁 == '全濁' - if q == '次濁': - return self.清濁 == '次濁' - - raise AssertionError('無此運算符:' + q) - - return any(all(inner(q) for q in p.split(' ')) for p in 表達式.split(' 或 ')) + tokens = [i for i in re.split( + r'(&+|\|+|[!~()()])|(? T: + ''' + 判斷某個小韻是否屬於給定的音韻地位,傳回自訂值。 + + Args: + 規則 (RecursiveArray): `(表達式, 結果)` 形式的陣列。 + + 表達式為描述音韻地位的字串,用於屬於函數。 + + 使用空字串或 `None` 作表達式以指定後備結果。 + + 結果為任意傳回值或遞迴規則。 + + error (Union[str, bool]): 若為 `True` 或非空字串,在未涵蓋所有條件時會拋出錯誤。 + + fallback (bool): 若為 `True`,在遞迴子陣列未涵蓋所有條件時會繼續嘗試母陣列的條件。 + + Returns: + T: 自訂值,在未涵蓋所有條件且不使用 `error` 時會回傳 `None`。 + + Raises: + AssertionError: `未涵蓋所有條件`, `無效的表達式`, `表達式為空`, `非預期的運算子`, `非預期的閉括號`, `括號未匹配` + + ```python + >>> Qieyun.音韻地位.from描述('幫三凡入').判斷([ + ... ('遇果假攝 或 支脂之佳韻', ''), + ... ('蟹攝 或 微韻', 'i'), + ... ('效流攝', 'u'), + ... ('深咸攝', [ + ... ('舒聲', 'm'), + ... ('入聲', 'p') + ... ]), + ... ('臻山攝', [ + ... ('舒聲', 'n'), + ... ('入聲', 't') + ... ]), + ... ('通江宕梗曾攝', [ + ... ('舒聲', 'ng'), + ... ('入聲', 'k') + ... ]) + ... ], '無韻尾規則') + 'p' + ``` + ''' + def loop(規則: RecursiveArray) -> T: + for 條件, 結果 in 規則: + if self.屬於(條件) if type(條件) == str and 條件 else 條件 != False: + if type(結果) != list: + return 結果 + if not fallback: + return loop(結果) + try: + return loop(結果) + except: + continue + raise error if type(error) == str else '未涵蓋所有條件' + if error: + return loop(規則) + try: + return loop(規則) + except: + return None def __eq__(self, that): if not isinstance(that, 音韻地位): @@ -532,30 +635,8 @@ def from編碼(編碼: str): 重紐 = 常量.所有重紐[重紐編碼] 聲 = 常量.所有聲[聲編碼] - if 韻編碼 == 0: - 韻 = '東' - 等 = '一' - elif 韻編碼 == 1: - 韻 = '東' - 等 = '三' - elif 韻編碼 == 37: - 韻 = '歌' - 等 = '一' - elif 韻編碼 == 38: - 韻 = '歌' - 等 = '三' - elif 韻編碼 == 39: - 韻 = '麻' - 等 = '二' - elif 韻編碼 == 40: - 韻 = '麻' - 等 = '三' - elif 韻編碼 == 43: - 韻 = '庚' - 等 = '二' - elif 韻編碼 == 44: - 韻 = '庚' - 等 = '三' + if 韻編碼 in 特別編碼: + 韻, 等 = 特別編碼[韻編碼] else: 韻 = 韻順序表[韻編碼] if 韻 in 常量.一等韻: @@ -575,8 +656,6 @@ def from編碼(編碼: str): assert 重紐 == 'A' 重紐 = None - 音韻地位.驗證(母, 呼, 等, 重紐, 韻, 聲) - return 音韻地位(母, 呼, 等, 重紐, 韻, 聲) @staticmethod @@ -589,12 +668,13 @@ def from描述(描述: str): match = 解析音韻描述.fullmatch(描述) assert match is not None - 母 = match.group(1) - 呼 = match.group(2) or None - 等 = match.group(3) or None - 重紐 = match.group(4) or None - 韻 = match.group(5) - 聲 = match.group(6) + 母, 呼, 等, 重紐, 韻, 聲 = match.groups() + if not 呼: + 呼 = None + if not 等: + 等 = None + if not 重紐: + 重紐 = None if 呼 is None and 母 not in '幫滂並明': if 韻 in 常量.必爲開口的韻: @@ -612,8 +692,6 @@ def from描述(描述: str): elif 韻 in 常量.四等韻: 等 = '四' - 音韻地位.驗證(母, 呼, 等, 重紐, 韻, 聲) - return 音韻地位(母, 呼, 等, 重紐, 韻, 聲) def __repr__(self): diff --git a/test/main.py b/test/main.py index 3534b3d..559f9ba 100644 --- a/test/main.py +++ b/test/main.py @@ -32,15 +32,104 @@ def test1(): next(f) # skip header for line in f: 母, 呼, 等, 重紐, 韻, 聲 = line.rstrip('\n').split(',') - if 呼 == '': + if not 呼: 呼 = None - if 重紐 == '': + if not 重紐: 重紐 = None roundtrip1(母, 呼, 等, 重紐, 韻, 聲) roundtrip2(母, 呼, 等, 重紐, 韻, 聲) def test2(): + ''' + 測試「法」字對應的音韻地位的各項音韻屬性。 + ''' + 當前音韻地位 = QieyunEncoder.音韻地位.from描述('幫三凡入') + + assert 當前音韻地位.母 == '幫' + assert 當前音韻地位.呼 == None + assert 當前音韻地位.等 == '三' + assert 當前音韻地位.重紐 == None + assert 當前音韻地位.韻 == '凡' + assert 當前音韻地位.聲 == '入' + + assert 當前音韻地位.清濁 == '全清' + assert 當前音韻地位.音 == '脣' + assert 當前音韻地位.攝 == '咸' + + assert 當前音韻地位.描述 == '幫三凡入' + assert 當前音韻地位.最簡描述 == '幫凡入' + assert 當前音韻地位.表達式 == '幫母 三等 凡韻 入聲' + assert 當前音韻地位.編碼 == 'A9D' + + assert 當前音韻地位 == QieyunEncoder.音韻地位.from描述('幫凡入') + + +def test3(): + ''' + 測試「祇」字對應的音韻地位的各項音韻屬性。 + ''' + 當前音韻地位 = QieyunEncoder.音韻地位.from描述('羣開三A支平') + + assert 當前音韻地位.母 == '羣' + assert 當前音韻地位.呼 == '開' + assert 當前音韻地位.等 == '三' + assert 當前音韻地位.重紐 == 'A' + assert 當前音韻地位.韻 == '支' + assert 當前音韻地位.聲 == '平' + + assert 當前音韻地位.清濁 == '全濁' + assert 當前音韻地位.音 == '牙' + assert 當前音韻地位.攝 == '止' + + assert 當前音韻地位.描述 == '羣開三A支平' + assert 當前音韻地位.最簡描述 == '羣開A支平' + assert 當前音韻地位.表達式 == '羣母 開口 三等 重紐A類 支韻 平聲' + assert 當前音韻地位.編碼 == 'fFA' + + assert 當前音韻地位 == QieyunEncoder.音韻地位.from描述('羣開A支平') + + +def test4(): + ''' + 測試「法」字對應的音韻地位的屬於函式 + ''' + 當前音韻地位 = QieyunEncoder.音韻地位.from描述('幫三凡入') + + assert 當前音韻地位.屬於('幫母') + assert 當前音韻地位.屬於('幫精組') + assert not 當前音韻地位.屬於('精組') + assert not 當前音韻地位.屬於('重紐A類 或 重紐B類') + assert not 當前音韻地位.屬於('喉音') + assert 當前音韻地位.屬於('仄聲') + assert not 當前音韻地位.屬於('舒聲') + assert 當前音韻地位.屬於('清音') + assert not 當前音韻地位.屬於('全濁') + assert not 當前音韻地位.屬於('次濁') + assert 當前音韻地位.屬於('開合中立') + assert not 當前音韻地位.屬於('開口 或 合口') + assert 當前音韻地位.屬於('幫組 輕脣韻') + assert not 當前音韻地位.屬於('陰聲韻') + assert 當前音韻地位.判斷([ + ('遇果假攝 或 支脂之佳韻', ''), + ('蟹攝 或 微韻', 'i'), + ('效流攝', 'u'), + ('深咸攝', [ + ('舒聲', 'm'), + ('入聲', 'p') + ]), + ('臻山攝', [ + ('舒聲', 'n'), + ('入聲', 't') + ]), + ('通江宕梗曾攝', [ + ('舒聲', 'ng'), + ('入聲', 'k') + ]) + ], '無韻尾規則') == 'p' + + +def test5(): ''' 測試正則化韻。 ''' From 3b1749937ed4983b7c10f9f6e0b7ff55c190d62e Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Thu, 5 Aug 2021 04:57:12 +0800 Subject: [PATCH 2/2] =?UTF-8?q?Update=20=E9=9F=B3=E9=9F=BB=E5=9C=B0?= =?UTF-8?q?=E4=BD=8D.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\351\237\263\351\237\273\345\234\260\344\275\215.py" | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 e17301e..a6c7daf 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" @@ -164,7 +164,7 @@ 編碼表 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$_' 韻順序表 = '東_冬鍾江支脂之微魚虞模齊祭泰佳皆夬灰咍廢眞臻文欣元魂痕寒刪山仙先蕭宵肴豪歌_麻_陽唐庚_耕清青蒸登尤侯幽侵覃談鹽添咸銜嚴凡' -解析音韻描述 = re.compile('([%s])([%s]?)([%s]?)([%s]?)([%s])([%s])' % ( +解析音韻描述 = re.compile('([%s])([%s])?([%s])?([%s])?([%s])([%s])' % ( 常量.所有母, 常量.所有呼, 常量.所有等, 常量.所有重紐, 常量.所有韻, 常量.所有聲)) 特別編碼 = {0: ('東', '一'), 1: ('東', '三'), 37: ('歌', '一'), 38: ('歌', '三'), @@ -669,12 +669,6 @@ def from描述(描述: str): assert match is not None 母, 呼, 等, 重紐, 韻, 聲 = match.groups() - if not 呼: - 呼 = None - if not 等: - 等 = None - if not 重紐: - 重紐 = None if 呼 is None and 母 not in '幫滂並明': if 韻 in 常量.必爲開口的韻: