state.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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. namespace state {
  9. export enum TransitionResult { FAILED, SUCCESS, FINISHED }
  10. interface StateMap {
  11. [index: string]: State
  12. }
  13. interface StateTransitionList {
  14. [index: string]: State
  15. }
  16. export interface Observer {
  17. (result: TransitionResult): void
  18. }
  19. export class State {
  20. display: string;
  21. transitions: StateTransitionList;
  22. constructor(display: string) {
  23. this.display = display;
  24. this.transitions = {};
  25. }
  26. addTransition(input: string, state: State): void {
  27. this.transitions[input] = state;
  28. }
  29. transition(input: string): State | null {
  30. return this.transitions[input];
  31. }
  32. }
  33. export class StateMachine {
  34. initialState: State;
  35. finalState: State;
  36. currentState: State;
  37. observers: Set<Observer>;
  38. constructor(initialState: State, finalState: State) {
  39. this.initialState = initialState;
  40. this.currentState = initialState;
  41. this.finalState = finalState;
  42. this.observers = new Set();
  43. }
  44. transition(input: string): TransitionResult {
  45. let result = this._transition(input);
  46. this.notify(result);
  47. return result;
  48. }
  49. private _transition(input: string): TransitionResult {
  50. let newState = this.currentState.transition(input);
  51. if (newState == null) {
  52. return TransitionResult.FAILED;
  53. } else {
  54. this.currentState = newState;
  55. if (this.finalState === newState) {
  56. return TransitionResult.FINISHED;
  57. } else {
  58. return TransitionResult.SUCCESS;
  59. }
  60. }
  61. }
  62. isNew(): boolean {
  63. return this.currentState === this.initialState;
  64. }
  65. isFinished(): boolean {
  66. return this.currentState === this.finalState;
  67. }
  68. reset(): void {
  69. this.currentState = this.initialState;
  70. }
  71. clone(): StateMachine {
  72. return new StateMachine(this.initialState, this.finalState);
  73. }
  74. getWord(): string {
  75. return this.initialState.display;
  76. }
  77. getDisplay(): string {
  78. return this.currentState.display;
  79. }
  80. addObserver(observer: Observer): void {
  81. this.observers.add(observer);
  82. }
  83. removeObserver(observer: Observer): void {
  84. this.observers.delete(observer);
  85. }
  86. notify(result: TransitionResult): void {
  87. this.observers.forEach(o => o(result));
  88. }
  89. }
  90. interface Transition {
  91. from: string,
  92. input: string,
  93. to: string
  94. }
  95. export function buildFromTransitions(initial: string, transitions: Transition[]): StateMachine {
  96. let states: StateMap = {};
  97. function getState(name: string): State {
  98. if (states[name] === undefined) {
  99. states[name] = new State(name);
  100. }
  101. return states[name];
  102. }
  103. transitions.forEach(t => {
  104. let fromState = getState(t.from);
  105. let toState = getState(t.to);
  106. fromState.addTransition(t.input, toState);
  107. })
  108. let initialState = getState(initial);
  109. let finalState = getState('');
  110. return new StateMachine(initialState, finalState);
  111. }
  112. export function makeTransition(from: string, input: string, to: string): Transition {
  113. return { from, input, to };
  114. }
  115. }