state.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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<Meta> {
  14. [index: string]: State<Meta>;
  15. }
  16. interface StateTransitionList<Meta> {
  17. [index: string]: State<Meta>;
  18. }
  19. export interface Observer<Meta> {
  20. (result: TransitionResult, meta: Meta, finished: boolean): void;
  21. }
  22. export class State<Meta> {
  23. display: string;
  24. meta: Meta;
  25. transitions: StateTransitionList<Meta>;
  26. constructor(display: string, meta: Meta) {
  27. this.display = display;
  28. this.meta = meta;
  29. this.transitions = {};
  30. }
  31. addTransition(input: string, state: State<Meta>): void {
  32. this.transitions[input] = state;
  33. }
  34. transition(input: string): State<Meta> | undefined {
  35. return this.transitions[input];
  36. }
  37. merge(other: State<Meta>): State<Meta> {
  38. const newState = this.clone();
  39. for (const key in other.transitions) {
  40. const otherNextState = other.transitions[key];
  41. const thisNextState = this.transition(key);
  42. if (thisNextState === undefined) {
  43. newState.addTransition(key, otherNextState);
  44. } else {
  45. newState.addTransition(key, thisNextState.merge(otherNextState));
  46. }
  47. }
  48. return newState;
  49. }
  50. transform(fn: (state: State<Meta>) => [string, Meta]): State<Meta> {
  51. const [newDisplay, newMeta] = fn(this);
  52. const newState = new State(newDisplay, newMeta);
  53. for (const key in this.transitions) {
  54. newState.transitions[key] = this.transitions[key].transform(fn);
  55. }
  56. return newState;
  57. }
  58. closure(): Set<State<Meta>> {
  59. const closure: Set<State<Meta>> = new Set([this]);
  60. for (const key in this.transitions) {
  61. const nextState = this.transitions[key];
  62. if (!closure.has(nextState)) {
  63. nextState.closure().forEach((state) => {
  64. closure.add(state);
  65. });
  66. }
  67. }
  68. return closure;
  69. }
  70. isEnd(): boolean {
  71. return Object.values(this.transitions).length === 0;
  72. }
  73. clone(): State<Meta> {
  74. const state = new State(this.display, this.meta);
  75. state.transitions = { ...this.transitions };
  76. return state;
  77. }
  78. debug(name?: string): this {
  79. if (name) {
  80. console.group(name);
  81. }
  82. this.closure().forEach((state) => console.log(state.toJSON()));
  83. if (name) {
  84. console.groupEnd();
  85. }
  86. return this;
  87. }
  88. toJSON(): string {
  89. const transitions = [];
  90. for (const key in this.transitions) {
  91. transitions.push(`${key}->${this.transitions[key].display}`);
  92. }
  93. return `${this.display}(${this.meta}): ${transitions.join(',')}`;
  94. }
  95. }
  96. export class MetaStateMachine<Meta> {
  97. initialState: State<Meta>;
  98. currentState: State<Meta>;
  99. observers: Set<Observer<Meta>>;
  100. nextMachine: MetaStateMachine<Meta> | null;
  101. constructor(initialState: State<Meta>) {
  102. this.initialState = initialState;
  103. this.currentState = initialState;
  104. this.observers = new Set();
  105. this.nextMachine = null;
  106. }
  107. transition(input: string) {
  108. const nextState = this.currentState.transition(input);
  109. if (nextState === undefined) {
  110. this.skipTransition(input);
  111. } else {
  112. this.currentState = nextState;
  113. this.notifyResult(
  114. TransitionResult.SUCCESS,
  115. this.currentState.meta,
  116. this.currentState.isEnd()
  117. );
  118. }
  119. }
  120. private skipTransition(input: string): boolean {
  121. let potentialNextStates: Array<State<Meta>> = Object.keys(
  122. this.currentState.transitions
  123. ).map((k) => this.currentState.transitions[k]);
  124. for (let i = 0; i < potentialNextStates.length; ++i) {
  125. const state = potentialNextStates[i];
  126. if (state.isEnd()) {
  127. if (this.nextMachine != null) {
  128. let result = this.nextMachine.initialState.transition(input);
  129. if (result != null) {
  130. const newState = result;
  131. this.currentState = state;
  132. this.nextMachine.currentState = newState;
  133. this.notifyResult(
  134. TransitionResult.SKIPPED,
  135. state.meta,
  136. state.isEnd()
  137. );
  138. this.nextMachine.notifyResult(
  139. TransitionResult.SUCCESS,
  140. newState.meta,
  141. newState.isEnd()
  142. );
  143. return true;
  144. }
  145. }
  146. } else {
  147. let result = state.transition(input);
  148. if (result != null) {
  149. const newState = result;
  150. this.currentState = newState;
  151. this.notifyResult(
  152. TransitionResult.SKIPPED,
  153. state.meta,
  154. state.isEnd()
  155. );
  156. this.notifyResult(
  157. TransitionResult.SUCCESS,
  158. newState.meta,
  159. newState.isEnd()
  160. );
  161. return true;
  162. }
  163. }
  164. }
  165. this.notifyResult(TransitionResult.FAILED, this.currentState.meta, false);
  166. return false;
  167. }
  168. isNew(): boolean {
  169. return this.currentState === this.initialState;
  170. }
  171. isFinished(): boolean {
  172. return this.currentState.isEnd();
  173. }
  174. reset(): void {
  175. this.currentState = this.initialState;
  176. }
  177. clone(): MetaStateMachine<Meta> {
  178. return new MetaStateMachine(this.initialState);
  179. }
  180. getWord(): string {
  181. return this.initialState.display;
  182. }
  183. getDisplay(): string {
  184. return this.currentState.display;
  185. }
  186. addObserver(observer: Observer<Meta>): void {
  187. this.observers.add(observer);
  188. }
  189. removeObserver(observer: Observer<Meta>): void {
  190. this.observers.delete(observer);
  191. }
  192. notifyResult(result: TransitionResult, meta: Meta, finished: boolean): void {
  193. this.observers.forEach((o) => o(result, meta, finished));
  194. }
  195. debug(): this {
  196. this.initialState.debug(this.initialState.display);
  197. return this;
  198. }
  199. }
  200. export interface Transition {
  201. from: string;
  202. input: string;
  203. to: string;
  204. meta: number;
  205. }
  206. export class StateMachine extends MetaStateMachine<number> {}
  207. export function buildFromTransitions(
  208. initial: string,
  209. transitions: Transition[]
  210. ): StateMachine {
  211. let states: StateMap<number> = {};
  212. function getState(name: string, meta: number): State<number> {
  213. if (states[name] === undefined) {
  214. states[name] = new State(name, meta);
  215. }
  216. return states[name];
  217. }
  218. transitions.forEach((t) => {
  219. let fromState = getState(t.from, 0);
  220. let toState = getState(t.to, Math.max(fromState.meta, t.meta));
  221. fromState.addTransition(t.input, toState);
  222. });
  223. let initialState = getState(initial, 0);
  224. return new StateMachine(initialState);
  225. }
  226. export function mergeMachines(...machines: StateMachine[]): StateMachine {
  227. const newState = machines
  228. .map((machine) => machine.initialState)
  229. .reduce((acc, state) => acc.merge(state));
  230. return new StateMachine(newState);
  231. }
  232. export function appendMachines(...machines: StateMachine[]): StateMachine {
  233. const newState = machines
  234. .map((machine) => machine.initialState)
  235. .reduce(appendStates);
  236. return new StateMachine(newState);
  237. }
  238. export function makeTransition(
  239. from: string,
  240. input: string,
  241. to: string,
  242. meta: number = 0
  243. ): Transition {
  244. return { from, input, to, meta };
  245. }
  246. export function appendStates(
  247. a: State<number>,
  248. b: State<number>
  249. ): State<number> {
  250. const newState = a.transform((state) => {
  251. return [state.display + b.display, state.meta];
  252. });
  253. const finalStates: Set<State<number>> = new Set();
  254. let lastMeta = 0;
  255. for (const state of newState.closure()) {
  256. if (state.isEnd()) {
  257. lastMeta = state.meta;
  258. finalStates.add(state);
  259. }
  260. }
  261. const { transitions } = b.transform((state) => {
  262. return [state.display, state.meta + lastMeta];
  263. });
  264. finalStates.forEach((finalState) => {
  265. finalState.transitions = transitions;
  266. });
  267. return newState;
  268. }