state.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /**
  2. * This module is a simple state machine implementation. We model states as a
  3. * graph, and the state machine simply keeps track of the current and final
  4. * state. The state also has a display field as metadata. This serves as the
  5. * state name and is also used to represent the remaining text to be input at
  6. * that particular state.
  7. */
  8. export enum TransitionResult {
  9. FAILED,
  10. SUCCESS,
  11. SKIPPED,
  12. }
  13. interface StateMap {
  14. [index: string]: State;
  15. }
  16. interface StateTransitionList {
  17. [index: string]: [State, boolean];
  18. }
  19. export interface Observer {
  20. (result: TransitionResult, boundary: boolean): void;
  21. }
  22. export class State {
  23. display: string;
  24. transitions: StateTransitionList;
  25. constructor(display: string) {
  26. this.display = display;
  27. this.transitions = {};
  28. }
  29. addTransition(input: string, state: State, boundary: boolean = false): void {
  30. this.transitions[input] = [state, boundary];
  31. }
  32. transition(input: string): [State, boolean] | undefined {
  33. return this.transitions[input];
  34. }
  35. clone(): State {
  36. let state = new State(this.display);
  37. state.transitions = { ...this.transitions };
  38. return state;
  39. }
  40. }
  41. export class StateMachine {
  42. initialState: State;
  43. finalState: State;
  44. currentState: State;
  45. observers: Set<Observer>;
  46. nextMachine: StateMachine | null;
  47. constructor(initialState: State, finalState: State) {
  48. this.initialState = initialState;
  49. this.currentState = initialState;
  50. this.finalState = finalState;
  51. this.observers = new Set();
  52. this.nextMachine = null;
  53. }
  54. transition(input: string) {
  55. let result = this.currentState.transition(input);
  56. if (result == null) {
  57. this.skipTransition(input);
  58. } else {
  59. let [newState, boundary] = result;
  60. this.currentState = newState;
  61. this.notifyResult(TransitionResult.SUCCESS, boundary);
  62. }
  63. }
  64. private skipTransition(input: string): boolean {
  65. let potentialNextStates: Array<[State, boolean]> = Object.keys(
  66. this.currentState.transitions
  67. ).map((k) => this.currentState.transitions[k]);
  68. for (let i = 0; i < potentialNextStates.length; ++i) {
  69. let [state, skippedBoundary] = potentialNextStates[i];
  70. if (state === this.finalState) {
  71. if (this.nextMachine != null) {
  72. let result = this.nextMachine.initialState.transition(input);
  73. if (result != null) {
  74. const [newState, boundary] = result;
  75. this.currentState = state;
  76. this.nextMachine.currentState = newState;
  77. this.notifyResult(TransitionResult.SKIPPED, skippedBoundary);
  78. this.nextMachine.notifyResult(TransitionResult.SUCCESS, boundary);
  79. return true;
  80. }
  81. }
  82. } else {
  83. let result = state.transition(input);
  84. if (result != null) {
  85. let [newState, boundary] = result;
  86. this.currentState = newState;
  87. this.notifyResult(TransitionResult.SKIPPED, skippedBoundary);
  88. this.notifyResult(TransitionResult.SUCCESS, boundary);
  89. return true;
  90. }
  91. }
  92. }
  93. this.notifyResult(TransitionResult.FAILED, false);
  94. return false;
  95. }
  96. isNew(): boolean {
  97. return this.currentState === this.initialState;
  98. }
  99. isFinished(): boolean {
  100. return this.currentState === this.finalState;
  101. }
  102. reset(): void {
  103. this.currentState = this.initialState;
  104. }
  105. clone(): StateMachine {
  106. return new StateMachine(this.initialState, this.finalState);
  107. }
  108. getWord(): string {
  109. return this.initialState.display;
  110. }
  111. getDisplay(): string {
  112. return this.currentState.display;
  113. }
  114. addObserver(observer: Observer): void {
  115. this.observers.add(observer);
  116. }
  117. removeObserver(observer: Observer): void {
  118. this.observers.delete(observer);
  119. }
  120. notifyResult(result: TransitionResult, boundary: boolean): void {
  121. this.observers.forEach((o) => o(result, boundary));
  122. }
  123. }
  124. export interface Transition {
  125. from: string;
  126. input: string;
  127. to: string;
  128. boundary: boolean;
  129. }
  130. export function buildFromTransitions(
  131. initial: string,
  132. transitions: Transition[]
  133. ): StateMachine {
  134. let states: StateMap = {};
  135. function getState(name: string): State {
  136. if (states[name] === undefined) {
  137. states[name] = new State(name);
  138. }
  139. return states[name];
  140. }
  141. transitions.forEach((t) => {
  142. let fromState = getState(t.from);
  143. let toState = getState(t.to);
  144. fromState.addTransition(t.input, toState, t.boundary);
  145. });
  146. let initialState = getState(initial);
  147. let finalState = getState('');
  148. return new StateMachine(initialState, finalState);
  149. }
  150. export function makeTransition(
  151. from: string,
  152. input: string,
  153. to: string,
  154. boundary: boolean = false
  155. ): Transition {
  156. return { from, input, to, boundary };
  157. }