Procházet zdrojové kódy

Properly handle ん

Previously, it was always treated as just "n" but now we handle it
differently depending on the succeeding kana.
Thomas Dy před 3 roky
rodič
revize
a7ea9b0e8b
3 změnil soubory, kde provedl 64 přidání a 2 odebrání
  1. 31 1
      src/kana.ts
  2. 1 1
      src/state.ts
  3. 32 0
      tests/kana.ts

+ 31 - 1
src/kana.ts

@@ -133,6 +133,30 @@ function smallKana(base: StateMachine): StateMachine {
   return new StateMachine(newState, base.finalState);
 }
 
+function n(base: StateMachine): StateMachine {
+  const { display, transitions } = base.initialState;
+  const allowSingleN = ['n', 'a', 'i', 'u', 'e', 'o', 'y'].every((k) => {
+    return base.initialState.transition(k) === undefined;
+  });
+
+  if (allowSingleN) {
+    const newState = new State('nn' + display);
+    const middleState = new State('n' + display);
+
+    newState.addTransition('n', middleState, true);
+    middleState.addTransition('n', base.initialState);
+    Object.keys(transitions).forEach((k) => {
+      let [nextState, _] = transitions[k];
+      middleState.addTransition(k, nextState);
+    });
+    return new StateMachine(newState, base.finalState);
+  } else {
+    throw new Error(
+      `Invalid base ${base.initialState.display}, just defer to literal`
+    );
+  }
+}
+
 interface KanaMapping {
   [index: string]: StateMachine;
 }
@@ -280,7 +304,7 @@ const SINGLE_KANA_MAPPING: KanaMapping = {
   ゐ: literal('i'),
   ゑ: literal('e'),
   を: literal('wo'),
-  ん: literal('n'),
+  ん: literal('nn'),
   が: literal('ga'),
   ぎ: literal('gi'),
   ぐ: literal('gu'),
@@ -425,6 +449,7 @@ const TRIPLE_KANA_MAPPING: KanaMapping = {};
   'ゔ',
 ].forEach((kana) => {
   DOUBLE_KANA_MAPPING['っ' + kana] = smallTsu(SINGLE_KANA_MAPPING[kana]);
+  DOUBLE_KANA_MAPPING['ん' + kana] = n(SINGLE_KANA_MAPPING[kana]);
 });
 [
   'きゃ',
@@ -461,6 +486,7 @@ const TRIPLE_KANA_MAPPING: KanaMapping = {};
   'ゔぉ',
 ].forEach((kana) => {
   TRIPLE_KANA_MAPPING['っ' + kana] = smallTsu(DOUBLE_KANA_MAPPING[kana]);
+  TRIPLE_KANA_MAPPING['ん' + kana] = n(DOUBLE_KANA_MAPPING[kana]);
 });
 
 /**
@@ -555,6 +581,10 @@ export class KanaInputState {
     return this.currentIndex >= this.stateMachines.length;
   }
 
+  isFinished(): boolean {
+    return this.currentIndex >= this.stateMachines.length;
+  }
+
   getRemainingInput(): string {
     let remaining = '';
     for (let i = this.currentIndex; i < this.stateMachines.length; ++i) {

+ 1 - 1
src/state.ts

@@ -37,7 +37,7 @@ export class State {
     this.transitions[input] = [state, boundary];
   }
 
-  transition(input: string): [State, boolean] | null {
+  transition(input: string): [State, boolean] | undefined {
     return this.transitions[input];
   }
 

+ 32 - 0
tests/kana.ts

@@ -20,6 +20,24 @@ function testInput(input: string, line: string) {
   }
 }
 
+function testFail(input: string, line: string) {
+  const inputState = new KanaInputState(line);
+  let fail = false;
+  inputState.map((_, m) => {
+    m.addObserver((result, _boundary) => {
+      fail =
+        fail ||
+        result === TransitionResult.FAILED ||
+        result === TransitionResult.SKIPPED;
+    });
+  });
+  for (const c of input.split('')) {
+    inputState.handleInput(c);
+  }
+  fail = fail || !inputState.isFinished();
+  assert.ok(fail, `Expected ${input} to fail on ${line}`);
+}
+
 test('normalizeInput', () => {
   assert.is(normalizeInput('ABCdef'), 'abcdef');
   assert.is(normalizeInput('フェスティバル'), 'ふぇすてぃばる');
@@ -72,4 +90,18 @@ test('small tsu', () => {
   // testInput('haltusya', 'はっしゃ');
 });
 
+test('nn', () => {
+  testInput('nn', 'ん');
+  testInput('nna', 'んあ');
+  testFail('na', 'んあ');
+  testInput('nda', 'んだ');
+  testInput('nnda', 'んだ');
+  testFail('nnnda', 'んだ');
+  testInput('nnnda', 'んんだ');
+  testInput('nnnnda', 'んんだ');
+  testFail('nya', 'んにゃ');
+  testFail('nnya', 'んにゃ');
+  testInput('nnnya', 'んにゃ');
+});
+
 test.run();