|
@@ -0,0 +1,315 @@
|
|
|
+/**
|
|
|
+ * This module is mainly for handling romaji input to match the provided kana
|
|
|
+ * input. While most kana map one-to-one with romaji, some kana have multiple
|
|
|
+ * ways to be inputted. In addition, we also have to handle っ which causes the
|
|
|
+ * next consonant to be repeated.
|
|
|
+ *
|
|
|
+ * The state management is done by having a state machine for each kana and it
|
|
|
+ * should handle all possible variations of the romaji to be inputted.
|
|
|
+ * Additionally, it also keeps track of what is left to be input, and adjusts
|
|
|
+ * itself accordingly if an alternative romaji was used.
|
|
|
+ */
|
|
|
+
|
|
|
+/// <reference path="state.ts" />
|
|
|
+
|
|
|
+namespace kana {
|
|
|
+ import StateMachine = state.StateMachine;
|
|
|
+ import TransitionResult = state.TransitionResult;
|
|
|
+ import t = state.makeTransition;
|
|
|
+
|
|
|
+ function literal(source: string): StateMachine {
|
|
|
+ let transitions = [];
|
|
|
+ for (let i = 0; i < source.length; ++i) {
|
|
|
+ let from = source.substring(i);
|
|
|
+ let input = source.charAt(i);
|
|
|
+ let to = source.substring(i+1);
|
|
|
+ transitions.push(t(from, input, to));
|
|
|
+ }
|
|
|
+ return state.buildFromTransitions(source, transitions);
|
|
|
+ }
|
|
|
+
|
|
|
+ function shi(): StateMachine {
|
|
|
+ return state.buildFromTransitions('shi', [
|
|
|
+ t('shi', 's', 'hi'),
|
|
|
+ t('hi', 'h', 'i'),
|
|
|
+ t('hi', 'i', ''),
|
|
|
+ t('i', 'i', '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function chi(): StateMachine {
|
|
|
+ return state.buildFromTransitions('chi', [
|
|
|
+ t('chi', 'c', 'hi'),
|
|
|
+ t('chi', 't', 'i'),
|
|
|
+ t('hi', 'h', 'i'),
|
|
|
+ t('i', 'i', '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function tsu(): StateMachine {
|
|
|
+ return state.buildFromTransitions('tsu', [
|
|
|
+ t('tsu', 't', 'su'),
|
|
|
+ t('su', 's', 'u'),
|
|
|
+ t('su', 'u', ''),
|
|
|
+ t('u', 'u', '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function fu(): StateMachine {
|
|
|
+ return state.buildFromTransitions('fu', [
|
|
|
+ t('fu', 'f', 'u'),
|
|
|
+ t('fu', 'h', 'u'),
|
|
|
+ t('u', 'u', '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function ji(): StateMachine {
|
|
|
+ return state.buildFromTransitions('ji', [
|
|
|
+ t('ji', 'j', 'i'),
|
|
|
+ t('ji', 'z', 'i'),
|
|
|
+ t('i', 'i', '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function sh(end: string): StateMachine {
|
|
|
+ let source = 'sh' + end;
|
|
|
+ let middle = 'h' + end;
|
|
|
+ return state.buildFromTransitions(source, [
|
|
|
+ t(source, 's', middle),
|
|
|
+ t(middle, 'h', end),
|
|
|
+ t(middle, 'y', end),
|
|
|
+ t(end, end, '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function ch(end: string): StateMachine {
|
|
|
+ let source = 'ch' + end;
|
|
|
+ let middle = 'h' + end;
|
|
|
+ let altMiddle = 'y' + end;
|
|
|
+
|
|
|
+ return state.buildFromTransitions(source, [
|
|
|
+ t(source, 'c', middle),
|
|
|
+ t(middle, 'h', end),
|
|
|
+ t(source, 't', altMiddle),
|
|
|
+ t(altMiddle, 'y', end),
|
|
|
+ t(end, end, '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ function j(end: string): StateMachine {
|
|
|
+ let source = 'j' + end;
|
|
|
+ let altMiddle = 'y' + end;
|
|
|
+
|
|
|
+ return state.buildFromTransitions(source, [
|
|
|
+ t(source, 'j', end),
|
|
|
+ t(source, 'z', altMiddle),
|
|
|
+ t(end, 'y', end),
|
|
|
+ t(altMiddle, 'y', end),
|
|
|
+ t(end, end, '')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ interface KanaMapping {
|
|
|
+ [index: string]: StateMachine
|
|
|
+ }
|
|
|
+
|
|
|
+ const SINGLE_KANA_MAPPING: KanaMapping = {
|
|
|
+ "あ": literal('a'),
|
|
|
+ "い": literal('i'),
|
|
|
+ "う": literal('u'),
|
|
|
+ "え": literal('e'),
|
|
|
+ "お": literal('o'),
|
|
|
+ "か": literal('ka'),
|
|
|
+ "き": literal('ki'),
|
|
|
+ "く": literal('ku'),
|
|
|
+ "け": literal('ke'),
|
|
|
+ "こ": literal('ko'),
|
|
|
+ "さ": literal('sa'),
|
|
|
+ "し": shi(),
|
|
|
+ "す": literal('su'),
|
|
|
+ "せ": literal('se'),
|
|
|
+ "そ": literal('so'),
|
|
|
+ "た": literal('ta'),
|
|
|
+ "ち": chi(),
|
|
|
+ "つ": tsu(),
|
|
|
+ "て": literal('te'),
|
|
|
+ "と": literal('to'),
|
|
|
+ "な": literal('na'),
|
|
|
+ "に": literal('ni'),
|
|
|
+ "ぬ": literal('nu'),
|
|
|
+ "ね": literal('ne'),
|
|
|
+ "の": literal('no'),
|
|
|
+ "は": literal('ha'),
|
|
|
+ "ひ": literal('hi'),
|
|
|
+ "ふ": fu(),
|
|
|
+ "へ": literal('he'),
|
|
|
+ "ほ": literal('ho'),
|
|
|
+ "ま": literal('ma'),
|
|
|
+ "み": literal('mi'),
|
|
|
+ "む": literal('mu'),
|
|
|
+ "め": literal('me'),
|
|
|
+ "も": literal('mo'),
|
|
|
+ "や": literal('ya'),
|
|
|
+ "ゆ": literal('yu'),
|
|
|
+ "よ": literal('yo'),
|
|
|
+ "ら": literal('ra'),
|
|
|
+ "り": literal('ri'),
|
|
|
+ "る": literal('ru'),
|
|
|
+ "れ": literal('re'),
|
|
|
+ "ろ": literal('ro'),
|
|
|
+ "わ": literal('wa'),
|
|
|
+ "を": literal('wo'),
|
|
|
+ "ん": literal('n'),
|
|
|
+ "が": literal('ga'),
|
|
|
+ "ぎ": literal('gi'),
|
|
|
+ "ぐ": literal('gu'),
|
|
|
+ "げ": literal('ge'),
|
|
|
+ "ご": literal('go'),
|
|
|
+ "ざ": literal('za'),
|
|
|
+ "じ": ji(),
|
|
|
+ "ず": literal('zu'),
|
|
|
+ "ぜ": literal('ze'),
|
|
|
+ "ぞ": literal('zo'),
|
|
|
+ "だ": literal('da'),
|
|
|
+ "ぢ": literal('di'),
|
|
|
+ "づ": literal('du'),
|
|
|
+ "で": literal('de'),
|
|
|
+ "ど": literal('do'),
|
|
|
+ "ば": literal('ba'),
|
|
|
+ "び": literal('bi'),
|
|
|
+ "ぶ": literal('bu'),
|
|
|
+ "べ": literal('be'),
|
|
|
+ "ぼ": literal('bo'),
|
|
|
+ "ぱ": literal('pa'),
|
|
|
+ "ぴ": literal('pi'),
|
|
|
+ "ぷ": literal('pu'),
|
|
|
+ "ぺ": literal('pe'),
|
|
|
+ "ぽ": literal('po')
|
|
|
+ }
|
|
|
+
|
|
|
+ const DOUBLE_KANA_MAPPING: KanaMapping = {
|
|
|
+ "きゃ": literal('kya'),
|
|
|
+ "きゅ": literal('kyu'),
|
|
|
+ "きょ": literal('kyo'),
|
|
|
+ "しゃ": sh('a'),
|
|
|
+ "しゅ": sh('u'),
|
|
|
+ "しょ": sh('o'),
|
|
|
+ "ちゃ": ch('a'),
|
|
|
+ "ちゅ": ch('u'),
|
|
|
+ "ちょ": ch('o'),
|
|
|
+ "にゃ": literal('nya'),
|
|
|
+ "にゅ": literal('nyu'),
|
|
|
+ "にょ": literal('nyo'),
|
|
|
+ "ひゃ": literal('hya'),
|
|
|
+ "ひゅ": literal('hyu'),
|
|
|
+ "ひょ": literal('hyo'),
|
|
|
+ "みゃ": literal('mya'),
|
|
|
+ "みゅ": literal('myu'),
|
|
|
+ "みょ": literal('myo'),
|
|
|
+ "りゃ": literal('rya'),
|
|
|
+ "りゅ": literal('ryu'),
|
|
|
+ "りょ": literal('ryo'),
|
|
|
+ "ぎゃ": literal('gya'),
|
|
|
+ "ぎゅ": literal('gyu'),
|
|
|
+ "ぎょ": literal('gyo'),
|
|
|
+ "じゃ": j('a'),
|
|
|
+ "じゅ": j('u'),
|
|
|
+ "じょ": j('o'),
|
|
|
+ "ぢゃ": literal('dya'),
|
|
|
+ "ぢゅ": literal('dyu'),
|
|
|
+ "ぢょ": literal('dyo'),
|
|
|
+ "びゃ": literal('bya'),
|
|
|
+ "びゅ": literal('byu'),
|
|
|
+ "びょ": literal('byo'),
|
|
|
+ "ぴゃ": literal('pya'),
|
|
|
+ "ぴゅ": literal('pyu'),
|
|
|
+ "ぴょ": literal('pyo')
|
|
|
+ }
|
|
|
+
|
|
|
+ export class KanaInputState {
|
|
|
+ kana: string[];
|
|
|
+ stateMachines: StateMachine[];
|
|
|
+ currentIndex: number;
|
|
|
+
|
|
|
+ constructor(input: string) {
|
|
|
+ let kana: string[] = [];
|
|
|
+ let machines: StateMachine[] = [];
|
|
|
+ let prevTsu = false;
|
|
|
+
|
|
|
+ // we pad the input so checking 2 at a time is simpler
|
|
|
+ let remaining = input.toLowerCase() + ' ';
|
|
|
+ while (remaining.length > 1) {
|
|
|
+ let nextOne = remaining.substring(0, 1);
|
|
|
+ if (/\s/.test(nextOne)) {
|
|
|
+ remaining = remaining.substring(1);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ let nextTwo = remaining.substring(0, 2);
|
|
|
+ let doubleKana = DOUBLE_KANA_MAPPING[nextTwo];
|
|
|
+ if (doubleKana != undefined) {
|
|
|
+ if (prevTsu) {
|
|
|
+ kana.push('っ' + nextTwo);
|
|
|
+ machines.push(doubleKana.extend());
|
|
|
+ prevTsu = false;
|
|
|
+ } else {
|
|
|
+ kana.push(nextTwo);
|
|
|
+ machines.push(doubleKana.clone());
|
|
|
+ }
|
|
|
+ remaining = remaining.substring(2);
|
|
|
+ } else {
|
|
|
+ if (nextOne === 'っ') {
|
|
|
+ prevTsu = true;
|
|
|
+ remaining = remaining.substring(1);
|
|
|
+ } else {
|
|
|
+ let singleKana = SINGLE_KANA_MAPPING[nextOne];
|
|
|
+ if (singleKana != undefined) {
|
|
|
+ if (prevTsu) {
|
|
|
+ kana.push('っ' + nextOne);
|
|
|
+ machines.push(singleKana.extend());
|
|
|
+ } else {
|
|
|
+ kana.push(nextOne);
|
|
|
+ machines.push(singleKana.clone());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ kana.push(nextOne);
|
|
|
+ machines.push(literal(nextOne));
|
|
|
+ }
|
|
|
+
|
|
|
+ prevTsu = false;
|
|
|
+ remaining = remaining.substring(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.kana = kana;
|
|
|
+ this.stateMachines = machines;
|
|
|
+ this.currentIndex = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ handleInput(input: string): boolean {
|
|
|
+ if (this.currentIndex >= this.stateMachines.length) return false;
|
|
|
+
|
|
|
+ let currentMachine = this.stateMachines[this.currentIndex];
|
|
|
+ let result = currentMachine.transition(input);
|
|
|
+ switch (result) {
|
|
|
+ case TransitionResult.FAILED:
|
|
|
+ return false;
|
|
|
+ case TransitionResult.SUCCESS:
|
|
|
+ return true;
|
|
|
+ case TransitionResult.FINISHED:
|
|
|
+ this.currentIndex += 1;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ getRemainingInput(): string {
|
|
|
+ let remaining = '';
|
|
|
+ for (let i = this.currentIndex; i < this.stateMachines.length; ++i) {
|
|
|
+ remaining += this.stateMachines[i].getDisplay();
|
|
|
+ }
|
|
|
+ return remaining;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|