state.ts 4.9 KB

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