Skip to content

Commit bb56af9

Browse files
Template overload for .調整
Fix unrecognized `重紐A類`
1 parent 0d81f63 commit bb56af9

File tree

2 files changed

+120
-15
lines changed

2 files changed

+120
-15
lines changed

src/lib/音韻地位.spec.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,20 @@ test('測試「祇」字對應的音韻地位的各項音韻屬性', t => {
6060
test('音韻地位.調整', t => {
6161
const 地位 = 音韻地位.from描述('幫三元上');
6262
t.is(地位.調整({ : '平' }).描述, '幫三元平');
63+
t.is(地位.調整('平聲').描述, '幫三元平');
64+
t.is(地位.調整`平聲`.描述, '幫三元平');
6365
t.throws(() => 地位.調整({ : '見' }), { message: /: need / }, '.調整() 會驗證新地位');
6466
t.is(地位.調整({ : '見', : '合' }).描述, '見合三元上');
67+
t.is(地位.調整`見母 合口`.描述, '見合三元上');
68+
t.is(地位.調整`${'見'}${'合口'}`.描述, '見合三元上');
69+
t.is(地位.調整`仙韻 重紐A類`.描述, '幫三A仙上');
70+
t.is(地位.調整`仙韻 重紐${'A'}類`.描述, '幫三A仙上');
71+
t.throws(() => 地位.調整`壞耶`, { message: 'unrecognized expression: 壞耶' });
72+
t.throws(() => 地位.調整`見影母`, { message: 'unrecognized expression: 見影母' });
73+
t.throws(() => 地位.調整`${'見影'}母`, { message: 'unexpected 母: 見影' });
74+
t.throws(() => 地位.調整`見母 ${'影'}母`, { message: 'duplicated assignment of 母' });
75+
t.throws(() => 地位.調整`${'開合'}中立`, { message: 'unrecognized expression: 開合, 中立' });
76+
t.throws(() => 地位.調整`仙韻 重紐${'A類'}`, { message: 'unrecognized expression: 重紐, A類' });
6577
t.is(地位.描述, '幫三元上', '.調整() 不修改原對象');
6678
});
6779

@@ -97,7 +109,7 @@ test('測試「法」字對應的音韻地位的屬於(複雜用法)及判
97109
t.true(當前音韻地位.屬於`一四等 或 ${當前音韻地位.描述 === '幫三凡入'}`);
98110
t.true(當前音韻地位.屬於`${() => '三等'}${() => '短路〔或〕'}`);
99111
t.false(當前音韻地位.屬於`非 ${() => '三等'}${() => '短路〔且〕'}`);
100-
t.throws(() => 當前音韻地位.屬於`${() => '三等'}${'立即求值'}`, { message: 'unreconized test condition: 立即求值' });
112+
t.throws(() => 當前音韻地位.屬於`${() => '三等'}${'立即求值'}`, { message: 'unrecognized test condition: 立即求值' });
101113
t.is(
102114
當前音韻地位.判斷(
103115
[

src/lib/音韻地位.ts

+107-14
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ function assert(value: unknown, error: string): asserts value {
3838
if (!value) throw new Error(error);
3939
}
4040

41+
// `Array.isArray`, but with more conservative type inference
42+
const isArray: (x: unknown) => x is readonly unknown[] = Array.isArray;
43+
4144
type Falsy = '' | 0 | false | null | undefined;
4245
export type 規則<T = unknown> = [unknown, T | 規則<T>][];
4346

@@ -182,7 +185,7 @@ export class 音韻地位 {
182185
readonly : string;
183186

184187
/**
185-
* 初始化音韻地位物件。(各項參數詳見 [`音韻地位`] 說明)
188+
* 初始化音韻地位物件。(各項參數詳見 [[`音韻地位`]] 說明)
186189
* @param 母 聲母:幫, 滂, 並, 明, …
187190
* @param 呼 呼:`null`, 開, 合
188191
* @param 等 等:一, 二, 三, 四
@@ -351,7 +354,7 @@ export class 音韻地位 {
351354
}
352355

353356
/**
354-
* 表達式,可用於 [[`屬於`]] 函數
357+
* 表達式,可用於 [[`.屬於`]] 函數
355358
* @example
356359
* ```typescript
357360
* > 音韻地位 = Qieyun.音韻地位.from描述('幫三凡入');
@@ -394,6 +397,8 @@ export class 音韻地位 {
394397
/**
395398
* 調整該音韻地位的屬性,會驗證調整後地位的合法性,返回新的對象。
396399
*
400+
* 本方法可使用一般形式(`.調整({ ... })`)、字串形式(`.調整('...')`)或標籤模板語法(`` .調整`...` ``)。
401+
*
397402
* **注意**:原對象不會被修改。
398403
*
399404
* @param 調整屬性 對象,其屬性可為六項基本屬性中的若干項,各屬性的值為欲修改成的值。
@@ -410,7 +415,97 @@ export class 音韻地位 {
410415
* '見合三元上'
411416
* ```
412417
*/
413-
調整(調整屬性: 部分音韻屬性): 音韻地位 {
418+
調整(調整屬性: 部分音韻屬性): 音韻地位;
419+
420+
/**
421+
* @example
422+
* ```typescript
423+
* > 音韻地位 = Qieyun.音韻地位.from描述('幫三元上');
424+
* > 音韻地位.調整`平聲`.描述 // 標籤模板語法(表達式為字面值時推薦)
425+
* '幫三元平'
426+
* > 音韻地位.調整('平聲').描述
427+
* '幫三元平'
428+
* > 音韻地位.調整`見母 合口`.描述
429+
* '見合三元上'
430+
* ```
431+
*/
432+
調整(調整屬性: string): 音韻地位;
433+
434+
/**
435+
* @example
436+
* ```typescript
437+
* > 音韻地位 = Qieyun.音韻地位.from描述('幫三元上');
438+
* > 音韻地位.調整`${'見'}母 ${'合口'}`.描述
439+
* '見合三元上'
440+
* ```
441+
*/
442+
調整(調整屬性: TemplateStringsArray, ...參數: string[]): 音韻地位;
443+
444+
調整(調整屬性: string | readonly string[] | 部分音韻屬性, ...參數: string[]): 音韻地位 {
445+
if (typeof 調整屬性 === 'string') 調整屬性 = [調整屬性];
446+
447+
if (isArray(調整屬性)) {
448+
const tokenGroups: string[][] = [[]];
449+
for (let i = 0; i < 調整屬性.length; i++) {
450+
let fragment = 調整屬性[i];
451+
if (!i) {
452+
fragment = fragment.trimStart();
453+
}
454+
if (i === 調整屬性.length - 1) {
455+
fragment = fragment.trimEnd();
456+
}
457+
458+
const tokens = fragment.split(/\s+/);
459+
for (let j = 0; j < tokens.length; j++) {
460+
if (tokens[j]) {
461+
tokenGroups[tokenGroups.length - 1].push(tokens[j]);
462+
}
463+
if (j < tokens.length - 1) {
464+
tokenGroups.push([]);
465+
}
466+
}
467+
if (i < 參數.length) {
468+
tokenGroups[tokenGroups.length - 1].push(參數[i]);
469+
}
470+
}
471+
472+
const 音韻屬性: 部分音韻屬性 = {};
473+
const tryAssign = <T extends keyof 部分音韻屬性>(屬性: T, : 音韻地位[T]) => {
474+
assert(!(屬性 in 音韻屬性), `duplicated assignment of ${屬性}`);
475+
音韻屬性[屬性] = ;
476+
};
477+
478+
for (let tokens of tokenGroups) {
479+
assert(tokens.length, 'empty expression');
480+
let original: string | undefined;
481+
if (tokens.length === 1) {
482+
switch (tokens[0]) {
483+
case '開合中立':
484+
tryAssign('呼', null);
485+
continue;
486+
case '不分重紐':
487+
tryAssign('重紐', null);
488+
continue;
489+
}
490+
original = tokens[0];
491+
tokens = [...tokens[0]];
492+
}
493+
let 屬性 = tokens[tokens.length - 1];
494+
const = tokens[tokens.length - 2];
495+
assert(
496+
屬性 === '類' ? tokens.slice(0, -2).join('') === '重紐' : tokens.length === 2 && ['母', '等', '韻', '聲', '口'].includes(屬性),
497+
`unrecognized expression: ${original ?? tokens.join(', ')}`
498+
);
499+
if (屬性 === '口') 屬性 = '呼';
500+
if (屬性 === '類') 屬性 = '重紐';
501+
const check = 檢查[屬性 as keyof 部分音韻屬性];
502+
assert(check.includes(), `unexpected ${屬性}: ${}`);
503+
tryAssign(屬性 as keyof 部分音韻屬性, );
504+
}
505+
506+
調整屬性 = 音韻屬性;
507+
}
508+
414509
const {= this.,= this.,= this., 重紐 = this.重紐,= this.,= this. } = 調整屬性;
415510
return new 音韻地位(, , , 重紐, , );
416511
}
@@ -449,17 +544,15 @@ export class 音韻地位 {
449544
* * NOT 運算子:`非`, `not`, `~`, `!`
450545
* * 括號:`(……)`, `(……)`
451546
*
452-
* 各表達式及運算子之間以空格隔開。
453-
*
454-
* AND 運算子可省略。
547+
* 各表達式及運算子之間必須以空格隔開。
455548
*
456-
* 如 `(端精組 且 入聲) 或 (以母 且 四等 且 去聲)` 與 `端精組 入聲 或 以母 四等 去聲` 同義。
549+
* AND 運算子可省略,如 `(端精組 且 入聲) 或 (以母 且 四等 且 去聲)` 與 `端精組 入聲 或 以母 四等 去聲` 同義。
457550
* @returns 若描述音韻地位的字串符合該音韻地位,回傳 `true`;否則回傳 `false`。
458551
* @throws 若表達式為空、不合語法、或限定條件不合法,則拋出異常。
459552
* @example
460553
* ```typescript
461554
* > 音韻地位 = Qieyun.音韻地位.from描述('幫三凡入');
462-
* > 音韻地位.屬於`章母`; // 標籤模板語法(表達式為字面值時推荐
555+
* > 音韻地位.屬於`章母`; // 標籤模板語法(表達式為字面值時推薦
463556
* false
464557
* > 音韻地位.屬於('章母'); // 一般形式
465558
* false
@@ -476,8 +569,8 @@ export class 音韻地位 {
476569
*
477570
* 嵌入的參數可以是:
478571
*
479-
* * 函數:會被執行;若其傳回值為字串,會遞迴套用至 [[`音韻地位.屬於`]] 函數,否則會檢測其真值
480-
* * 字串:會遞迴套用至 [[`音韻地位.屬於`]] 函數
572+
* * 函數:會被執行;若其傳回值為字串,會遞迴套用至 [[`.屬於`]] 函數,否則會檢測其真值
573+
* * 字串:會遞迴套用至 [[`.屬於`]] 函數
481574
* * 其他:會檢測其真值
482575
*
483576
* **注意**:
@@ -496,7 +589,7 @@ export class 音韻地位 {
496589
* true
497590
* ```
498591
*/
499-
屬於(表達式: readonly string[], ...參數: unknown[]): boolean;
592+
屬於(表達式: TemplateStringsArray, ...參數: unknown[]): boolean;
500593

501594
屬於(表達式: string | readonly string[], ...參數: unknown[]): boolean {
502595
if (typeof 表達式 === 'string') 表達式 = [表達式];
@@ -526,7 +619,7 @@ export class 音韻地位 {
526619
assert(!invalid, invalid + match[2] + '不存在');
527620
return values.includes(this[match[2] as keyof typeof 檢查]);
528621
}
529-
throw new Error(`unreconized test condition: ${token}`);
622+
throw new Error(`unrecognized test condition: ${token}`);
530623
};
531624

532625
// 詞法分析,同時給普通運算元求值(惟函數型運算元留待後面惰性求值)
@@ -684,8 +777,8 @@ export class 音韻地位 {
684777
*
685778
* 判斷式可以是:
686779
*
687-
* * &#x3000;&#x3000;函數:會被執行;若其傳回值為非空字串,會套用至 [[`音韻地位.屬於`]] 函數,若為布林值則直接決定是否跳過本規則,否則規則永遠不會被跳過
688-
* * 非空字串:描述音韻地位的表達式,會套用至 [[`音韻地位.屬於`]] 函數
780+
* * &#x3000;&#x3000;函數:會被執行;若其傳回值為非空字串,會套用至 [[`.屬於`]] 函數,若為布林值則直接決定是否跳過本規則,否則規則永遠不會被跳過
781+
* * 非空字串:描述音韻地位的表達式,會套用至 [[`.屬於`]] 函數
689782
* * &#x3000;布林值:直接決定是否跳過本規則
690783
* * &#x3000;&#x3000;其他:規則永遠不會被跳過(可用作指定後備結果)
691784
*

0 commit comments

Comments
 (0)