jssm 5.35.4 → 5.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.codeclimate.yml +22 -22
  2. package/.editorconfig +12 -12
  3. package/.eslintrc +20 -20
  4. package/.nycrc +6 -6
  5. package/.travis.yml +8 -8
  6. package/LICENSE.md +21 -21
  7. package/README.md +997 -993
  8. package/dist/es6/jssm-dot.d.ts +6 -6
  9. package/dist/es6/jssm-dot.js +1 -1
  10. package/dist/es6/jssm.d.ts +100 -100
  11. package/dist/es6/jssm.js +792 -792
  12. package/dist/es6/jssm_types.d.ts +151 -151
  13. package/dist/es6/jssm_types.js +1 -1
  14. package/dist/es6/jssm_util.d.ts +8 -8
  15. package/dist/es6/jssm_util.js +34 -34
  16. package/dist/es6/version.d.ts +2 -2
  17. package/dist/es6/version.js +2 -2
  18. package/dist/jssm.es5.cjs.js +1 -1
  19. package/dist/jssm.es5.iife.js +1 -0
  20. package/jest-spec.config.js +27 -27
  21. package/jest-stoch.config.js +27 -27
  22. package/jssm-dot.d.ts +6 -6
  23. package/jssm.d.ts +100 -100
  24. package/jssm_types.d.ts +151 -151
  25. package/jssm_util.d.ts +8 -8
  26. package/package.json +125 -122
  27. package/rollup.config.iife.js +44 -44
  28. package/rollup.config.js +44 -44
  29. package/src/demo/index.html +38 -38
  30. package/src/demo/style.css +1 -1
  31. package/src/ts/jssm-dot.peg +928 -874
  32. package/src/ts/jssm.ts +1120 -1120
  33. package/src/ts/jssm_types.ts +346 -346
  34. package/src/ts/jssm_util.ts +100 -100
  35. package/src/ts/tests/actions.spec.ts +167 -167
  36. package/src/ts/tests/arrange.spec.ts +72 -72
  37. package/src/ts/tests/arrange.stoch.ts +4 -4
  38. package/src/ts/tests/array_box_if_string.spec.ts +30 -31
  39. package/src/ts/tests/array_transitions.spec.ts +129 -129
  40. package/src/ts/tests/arrow unicode.spec.ts +88 -88
  41. package/src/ts/tests/arrow.spec.ts +124 -124
  42. package/src/ts/tests/colors.spec.ts +58 -58
  43. package/src/ts/tests/comment.spec.ts +134 -134
  44. package/src/ts/tests/compile.spec.ts +79 -79
  45. package/src/ts/tests/constants.spec.ts +98 -98
  46. package/src/ts/tests/cycles.spec.ts +153 -153
  47. package/src/ts/tests/dot_preamble.spec.ts +16 -16
  48. package/src/ts/tests/embedded_sm.spec.ts +36 -36
  49. package/src/ts/tests/flow.spec.ts +22 -22
  50. package/src/ts/tests/forced transitions.spec.ts +26 -26
  51. package/src/ts/tests/general.spec.ts +933 -933
  52. package/src/ts/tests/graph node lists.spec.ts +21 -21
  53. package/src/ts/tests/histo.spec.ts +24 -24
  54. package/src/ts/tests/hooks.spec.ts +28 -0
  55. package/src/ts/tests/language.spec.ts +37 -37
  56. package/src/ts/tests/language_data/belarussian.json +13 -13
  57. package/src/ts/tests/language_data/bengali.json +15 -15
  58. package/src/ts/tests/language_data/emoji.json +21 -21
  59. package/src/ts/tests/language_data/english.json +16 -16
  60. package/src/ts/tests/language_data/french.json +16 -16
  61. package/src/ts/tests/language_data/german.json +16 -16
  62. package/src/ts/tests/language_data/hebrew.json +16 -16
  63. package/src/ts/tests/language_data/portuguese.json +12 -12
  64. package/src/ts/tests/language_data/russian.json +12 -12
  65. package/src/ts/tests/language_data/spanish.json +17 -17
  66. package/src/ts/tests/language_data/ukrainian.json +18 -18
  67. package/src/ts/tests/layout.spec.ts +29 -29
  68. package/src/ts/tests/machine_attributes.spec.ts +398 -398
  69. package/src/ts/tests/machine_name.spec.ts +14 -14
  70. package/src/ts/tests/named lists.spec.ts +24 -19
  71. package/src/ts/tests/nominated states.spec.ts +133 -133
  72. package/src/ts/tests/parse actions.spec.ts +32 -32
  73. package/src/ts/tests/parse.spec.ts +94 -94
  74. package/src/ts/tests/probability.spec.ts +146 -146
  75. package/src/ts/tests/r639.spec.ts +27 -27
  76. package/src/ts/tests/sample_select.spec.ts +173 -173
  77. package/src/ts/tests/seq.spec.ts +14 -16
  78. package/src/ts/tests/seq.stoch.ts +83 -0
  79. package/src/ts/tests/shapes.spec.ts +63 -63
  80. package/src/ts/tests/sm_tag.spec.ts +37 -37
  81. package/src/ts/tests/special characters.spec.ts +39 -39
  82. package/src/ts/tests/state_declaration.spec.ts +214 -200
  83. package/src/ts/tests/state_style.spec.ts +82 -39
  84. package/src/ts/tests/stop light.spec.ts +157 -157
  85. package/src/ts/tests/stripes.spec.ts +52 -52
  86. package/src/ts/tests/theme.spec.ts +45 -45
  87. package/src/ts/tests/weighted_histo_key.spec.ts +22 -22
  88. package/src/ts/tests/weighted_rand_select.spec.ts +27 -27
  89. package/src/ts/tests/weighted_sample_select.spec.ts +24 -26
  90. package/src/ts/version.ts +1 -1
  91. package/tree.txt +1794 -1794
  92. package/tsconfig.json +27 -27
  93. package/version.d.ts +2 -2
package/dist/es6/jssm.js CHANGED
@@ -1,792 +1,792 @@
1
- // whargarbl lots of these return arrays could/should be sets
2
- import { reduce as reduce_to_639 } from 'reduce-to-639-1';
3
- import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key, array_box_if_string } from './jssm_util';
4
- import { parse } from './jssm-dot'; // TODO FIXME WHARGARBL this could be post-typed
5
- import { version } from './version'; // replaced from package.js in build // TODO FIXME currently broken
6
- /* eslint-disable complexity */
7
- function arrow_direction(arrow) {
8
- switch (String(arrow)) {
9
- case '->':
10
- case '→':
11
- case '=>':
12
- case '⇒':
13
- case '~>':
14
- case '↛':
15
- return 'right';
16
- case '<-':
17
- case '←':
18
- case '<=':
19
- case '⇐':
20
- case '<~':
21
- case '↚':
22
- return 'left';
23
- case '<->':
24
- case '↔':
25
- case '<-=>':
26
- case '←⇒':
27
- case '←=>':
28
- case '<-⇒':
29
- case '<-~>':
30
- case '←↛':
31
- case '←~>':
32
- case '<-↛':
33
- case '<=>':
34
- case '⇔':
35
- case '<=->':
36
- case '⇐→':
37
- case '⇐->':
38
- case '<=→':
39
- case '<=~>':
40
- case '⇐↛':
41
- case '⇐~>':
42
- case '<=↛':
43
- case '<~>':
44
- case '↮':
45
- case '<~->':
46
- case '↚→':
47
- case '↚->':
48
- case '<~→':
49
- case '<~=>':
50
- case '↚⇒':
51
- case '↚=>':
52
- case '<~⇒':
53
- return 'both';
54
- default:
55
- throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
56
- }
57
- }
58
- /* eslint-enable complexity */
59
- /* eslint-disable complexity */
60
- function arrow_left_kind(arrow) {
61
- switch (String(arrow)) {
62
- case '->':
63
- case '→':
64
- case '=>':
65
- case '⇒':
66
- case '~>':
67
- case '↛':
68
- return 'none';
69
- case '<-':
70
- case '←':
71
- case '<->':
72
- case '↔':
73
- case '<-=>':
74
- case '←⇒':
75
- case '<-~>':
76
- case '←↛':
77
- return 'legal';
78
- case '<=':
79
- case '⇐':
80
- case '<=>':
81
- case '⇔':
82
- case '<=->':
83
- case '⇐→':
84
- case '<=~>':
85
- case '⇐↛':
86
- return 'main';
87
- case '<~':
88
- case '↚':
89
- case '<~>':
90
- case '↮':
91
- case '<~->':
92
- case '↚→':
93
- case '<~=>':
94
- case '↚⇒':
95
- return 'forced';
96
- default:
97
- throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
98
- }
99
- }
100
- /* eslint-enable complexity */
101
- /* eslint-disable complexity */
102
- function arrow_right_kind(arrow) {
103
- switch (String(arrow)) {
104
- case '<-':
105
- case '←':
106
- case '<=':
107
- case '⇐':
108
- case '<~':
109
- case '↚':
110
- return 'none';
111
- case '->':
112
- case '→':
113
- case '<->':
114
- case '↔':
115
- case '<=->':
116
- case '⇐→':
117
- case '<~->':
118
- case '↚→':
119
- return 'legal';
120
- case '=>':
121
- case '⇒':
122
- case '<=>':
123
- case '⇔':
124
- case '<-=>':
125
- case '←⇒':
126
- case '<~=>':
127
- case '↚⇒':
128
- return 'main';
129
- case '~>':
130
- case '↛':
131
- case '<~>':
132
- case '↮':
133
- case '<-~>':
134
- case '←↛':
135
- case '<=~>':
136
- case '⇐↛':
137
- return 'forced';
138
- default:
139
- throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
140
- }
141
- }
142
- /* eslint-enable complexity */
143
- function makeTransition(this_se, from, to, isRight, _wasList, _wasIndex) {
144
- const kind = isRight ? arrow_right_kind(this_se.kind) : arrow_left_kind(this_se.kind), edge = {
145
- from,
146
- to,
147
- kind,
148
- forced_only: kind === 'forced',
149
- main_path: kind === 'main'
150
- };
151
- // if ((wasList !== undefined) && (wasIndex === undefined)) { throw new TypeError("Must have an index if transition was in a list"); }
152
- // if ((wasIndex !== undefined) && (wasList === undefined)) { throw new TypeError("Must be in a list if transition has an index"); }
153
- /*
154
- if (typeof edge.to === 'object') {
155
-
156
- if (edge.to.key === 'cycle') {
157
- if (wasList === undefined) { throw "Must have a waslist if a to is type cycle"; }
158
- const nextIndex = wrapBy(wasIndex, edge.to.value, wasList.length);
159
- edge.to = wasList[nextIndex];
160
- }
161
-
162
- }
163
- */
164
- const action = isRight ? 'r_action' : 'l_action', probability = isRight ? 'r_probability' : 'l_probability';
165
- if (this_se[action]) {
166
- edge.action = this_se[action];
167
- }
168
- if (this_se[probability]) {
169
- edge.probability = this_se[probability];
170
- }
171
- return edge;
172
- }
173
- function wrap_parse(input, options) {
174
- return parse(input, options || {});
175
- }
176
- function compile_rule_transition_step(acc, from, to, this_se, next_se) {
177
- const edges = [];
178
- const uFrom = (Array.isArray(from) ? from : [from]), uTo = (Array.isArray(to) ? to : [to]);
179
- uFrom.map((f) => {
180
- uTo.map((t) => {
181
- const right = makeTransition(this_se, f, t, true);
182
- if (right.kind !== 'none') {
183
- edges.push(right);
184
- }
185
- const left = makeTransition(this_se, t, f, false);
186
- if (left.kind !== 'none') {
187
- edges.push(left);
188
- }
189
- });
190
- });
191
- const new_acc = acc.concat(edges);
192
- if (next_se) {
193
- return compile_rule_transition_step(new_acc, to, next_se.to, next_se, next_se.se);
194
- }
195
- else {
196
- return new_acc;
197
- }
198
- }
199
- function compile_rule_handle_transition(rule) {
200
- return compile_rule_transition_step([], rule.from, rule.se.to, rule.se, rule.se.se);
201
- }
202
- function compile_rule_handler(rule) {
203
- if (rule.key === 'transition') {
204
- return { agg_as: 'transition', val: compile_rule_handle_transition(rule) };
205
- }
206
- if (rule.key === 'machine_language') {
207
- return { agg_as: 'machine_language', val: reduce_to_639(rule.value) };
208
- }
209
- if (rule.key === 'state_declaration') {
210
- if (!rule.name) {
211
- throw new Error('State declarations must have a name');
212
- }
213
- return { agg_as: 'state_declaration', val: { state: rule.name, declarations: rule.value } };
214
- }
215
- if (['arrange_declaration', 'arrange_start_declaration',
216
- 'arrange_end_declaration'].includes(rule.key)) {
217
- return { agg_as: rule.key, val: [rule.value] };
218
- }
219
- const tautologies = [
220
- 'graph_layout', 'start_states', 'end_states', 'machine_name', 'machine_version',
221
- 'machine_comment', 'machine_author', 'machine_contributor', 'machine_definition',
222
- 'machine_reference', 'machine_license', 'fsl_version', 'state_config', 'theme',
223
- 'flow', 'dot_preamble'
224
- ];
225
- if (tautologies.includes(rule.key)) {
226
- return { agg_as: rule.key, val: rule.value };
227
- }
228
- throw new Error(`compile_rule_handler: Unknown rule: ${JSON.stringify(rule)}`);
229
- }
230
- function compile(tree) {
231
- const results = {
232
- graph_layout: [],
233
- transition: [],
234
- start_states: [],
235
- end_states: [],
236
- state_config: [],
237
- state_declaration: [],
238
- fsl_version: [],
239
- machine_author: [],
240
- machine_comment: [],
241
- machine_contributor: [],
242
- machine_definition: [],
243
- machine_language: [],
244
- machine_license: [],
245
- machine_name: [],
246
- machine_reference: [],
247
- theme: [],
248
- flow: [],
249
- dot_preamble: [],
250
- arrange_declaration: [],
251
- arrange_start_declaration: [],
252
- arrange_end_declaration: [],
253
- machine_version: []
254
- };
255
- tree.map((tr) => {
256
- const rule = compile_rule_handler(tr), agg_as = rule.agg_as, val = rule.val; // TODO FIXME no any
257
- results[agg_as] = results[agg_as].concat(val);
258
- });
259
- const assembled_transitions = [].concat(...results['transition']);
260
- const result_cfg = {
261
- start_states: results.start_states.length ? results.start_states : [assembled_transitions[0].from],
262
- transitions: assembled_transitions
263
- };
264
- const oneOnlyKeys = [
265
- 'graph_layout', 'machine_name', 'machine_version', 'machine_comment',
266
- 'fsl_version', 'machine_license', 'machine_definition', 'machine_language',
267
- 'theme', 'flow', 'dot_preamble'
268
- ];
269
- oneOnlyKeys.map((oneOnlyKey) => {
270
- if (results[oneOnlyKey].length > 1) {
271
- throw new Error(`May only have one ${oneOnlyKey} statement maximum: ${JSON.stringify(results[oneOnlyKey])}`);
272
- }
273
- else {
274
- if (results[oneOnlyKey].length) {
275
- result_cfg[oneOnlyKey] = results[oneOnlyKey][0];
276
- }
277
- }
278
- });
279
- ['arrange_declaration', 'arrange_start_declaration', 'arrange_end_declaration',
280
- 'machine_author', 'machine_contributor', 'machine_reference', 'state_declaration'].map((multiKey) => {
281
- if (results[multiKey].length) {
282
- result_cfg[multiKey] = results[multiKey];
283
- }
284
- });
285
- return result_cfg;
286
- }
287
- function make(plan) {
288
- return compile(wrap_parse(plan));
289
- }
290
- function transfer_state_properties(state_decl) {
291
- state_decl.declarations.map((d) => {
292
- switch (d.key) {
293
- case 'shape':
294
- state_decl.shape = d.value;
295
- break;
296
- case 'color':
297
- state_decl.color = d.value;
298
- break;
299
- case 'corners':
300
- state_decl.corners = d.value;
301
- break;
302
- case 'linestyle':
303
- state_decl.linestyle = d.value;
304
- break;
305
- case 'text-color':
306
- state_decl.textColor = d.value;
307
- break;
308
- case 'background-color':
309
- state_decl.backgroundColor = d.value;
310
- break;
311
- case 'border-color':
312
- state_decl.borderColor = d.value;
313
- break;
314
- default: throw new Error(`Unknown state property: '${JSON.stringify(d)}'`);
315
- }
316
- });
317
- return state_decl;
318
- }
319
- class Machine {
320
- // whargarbl this badly needs to be broken up, monolith master
321
- constructor({ start_states, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], theme = 'default', flow = 'down', graph_layout = 'dot' }) {
322
- this._state = start_states[0];
323
- this._states = new Map();
324
- this._state_declarations = new Map();
325
- this._edges = [];
326
- this._edge_map = new Map();
327
- this._named_transitions = new Map();
328
- this._actions = new Map();
329
- this._reverse_actions = new Map();
330
- this._reverse_action_targets = new Map(); // todo
331
- this._machine_author = array_box_if_string(machine_author);
332
- this._machine_comment = machine_comment;
333
- this._machine_contributor = array_box_if_string(machine_contributor);
334
- this._machine_definition = machine_definition;
335
- this._machine_language = machine_language;
336
- this._machine_license = machine_license;
337
- this._machine_name = machine_name;
338
- this._machine_version = machine_version;
339
- this._raw_state_declaration = state_declaration || [];
340
- this._fsl_version = fsl_version;
341
- this._arrange_declaration = arrange_declaration;
342
- this._arrange_start_declaration = arrange_start_declaration;
343
- this._arrange_end_declaration = arrange_end_declaration;
344
- this._dot_preamble = dot_preamble;
345
- this._theme = theme;
346
- this._flow = flow;
347
- this._graph_layout = graph_layout;
348
- if (state_declaration) {
349
- state_declaration.map((state_decl) => {
350
- if (this._state_declarations.has(state_decl.state)) { // no repeats
351
- throw new Error(`Added the same state declaration twice: ${JSON.stringify(state_decl.state)}`);
352
- }
353
- this._state_declarations.set(state_decl.state, transfer_state_properties(state_decl));
354
- });
355
- }
356
- transitions.map((tr) => {
357
- if (tr.from === undefined) {
358
- throw new Error(`transition must define 'from': ${JSON.stringify(tr)}`);
359
- }
360
- if (tr.to === undefined) {
361
- throw new Error(`transition must define 'to': ${JSON.stringify(tr)}`);
362
- }
363
- // get the cursors. what a mess
364
- const cursor_from = this._states.get(tr.from)
365
- || { name: tr.from, from: [], to: [], complete: complete.includes(tr.from) };
366
- if (!(this._states.has(tr.from))) {
367
- this._new_state(cursor_from);
368
- }
369
- const cursor_to = this._states.get(tr.to)
370
- || { name: tr.to, from: [], to: [], complete: complete.includes(tr.to) };
371
- if (!(this._states.has(tr.to))) {
372
- this._new_state(cursor_to);
373
- }
374
- // guard against existing connections being re-added
375
- if (cursor_from.to.includes(tr.to)) {
376
- throw new Error(`already has ${JSON.stringify(tr.from)} to ${JSON.stringify(tr.to)}`);
377
- }
378
- else {
379
- cursor_from.to.push(tr.to);
380
- cursor_to.from.push(tr.from);
381
- }
382
- // add the edge; note its id
383
- this._edges.push(tr);
384
- const thisEdgeId = this._edges.length - 1;
385
- // guard against repeating a transition name
386
- if (tr.name) {
387
- if (this._named_transitions.has(tr.name)) {
388
- throw new Error(`named transition "${JSON.stringify(tr.name)}" already created`);
389
- }
390
- else {
391
- this._named_transitions.set(tr.name, thisEdgeId);
392
- }
393
- }
394
- // set up the mapping, so that edges can be looked up by endpoint pairs
395
- const from_mapping = this._edge_map.get(tr.from) || new Map();
396
- if (!(this._edge_map.has(tr.from))) {
397
- this._edge_map.set(tr.from, from_mapping);
398
- }
399
- // const to_mapping = from_mapping.get(tr.to);
400
- from_mapping.set(tr.to, thisEdgeId); // already checked that this mapping doesn't exist, above
401
- // set up the action mapping, so that actions can be looked up by origin
402
- if (tr.action) {
403
- // forward mapping first by action name
404
- let actionMap = this._actions.get(tr.action); // TODO FIXME ?Map equiv
405
- if (!(actionMap)) {
406
- actionMap = new Map();
407
- this._actions.set(tr.action, actionMap);
408
- }
409
- if (actionMap.has(tr.from)) {
410
- throw new Error(`action ${JSON.stringify(tr.action)} already attached to origin ${JSON.stringify(tr.from)}`);
411
- }
412
- else {
413
- actionMap.set(tr.from, thisEdgeId);
414
- }
415
- // reverse mapping first by state origin name
416
- let rActionMap = this._reverse_actions.get(tr.from); // TODO FIXME ?Map equiv
417
- if (!(rActionMap)) {
418
- rActionMap = new Map();
419
- this._reverse_actions.set(tr.from, rActionMap);
420
- }
421
- // no need to test for reverse mapping pre-presence;
422
- // forward mapping already covers collisions
423
- rActionMap.set(tr.action, thisEdgeId);
424
- // reverse mapping first by state target name
425
- if (!(this._reverse_action_targets.has(tr.to))) {
426
- this._reverse_action_targets.set(tr.to, new Map());
427
- }
428
- /* todo comeback
429
- fundamental problem is roActionMap needs to be a multimap
430
- const roActionMap = this._reverse_action_targets.get(tr.to); // wasteful - already did has - refactor
431
- if (roActionMap) {
432
- if (roActionMap.has(tr.action)) {
433
- throw new Error(`ro-action ${tr.to} already attached to action ${tr.action}`);
434
- } else {
435
- roActionMap.set(tr.action, thisEdgeId);
436
- }
437
- } else {
438
- throw new Error('should be impossible - flow doesn\'t know .set precedes .get yet again. severe error?');
439
- }
440
- */
441
- }
442
- });
443
- }
444
- _new_state(state_config) {
445
- if (this._states.has(state_config.name)) {
446
- throw new Error(`state ${JSON.stringify(state_config.name)} already exists`);
447
- }
448
- this._states.set(state_config.name, state_config);
449
- return state_config.name;
450
- }
451
- state() {
452
- return this._state;
453
- }
454
- /* whargarbl todo major
455
- when we reimplement this, reintroduce this change to the is_final call
456
-
457
- is_changing(): boolean {
458
- return true; // todo whargarbl
459
- }
460
- */
461
- state_is_final(whichState) {
462
- return ((this.state_is_terminal(whichState)) && (this.state_is_complete(whichState)));
463
- }
464
- is_final() {
465
- // return ((!this.is_changing()) && this.state_is_final(this.state()));
466
- return this.state_is_final(this.state());
467
- }
468
- graph_layout() {
469
- return this._graph_layout;
470
- }
471
- dot_preamble() {
472
- return this._dot_preamble;
473
- }
474
- machine_author() {
475
- return this._machine_author;
476
- }
477
- machine_comment() {
478
- return this._machine_comment;
479
- }
480
- machine_contributor() {
481
- return this._machine_contributor;
482
- }
483
- machine_definition() {
484
- return this._machine_definition;
485
- }
486
- machine_language() {
487
- return this._machine_language;
488
- }
489
- machine_license() {
490
- return this._machine_license;
491
- }
492
- machine_name() {
493
- return this._machine_name;
494
- }
495
- machine_version() {
496
- return this._machine_version;
497
- }
498
- raw_state_declarations() {
499
- return this._raw_state_declaration;
500
- }
501
- state_declaration(which) {
502
- return this._state_declarations.get(which);
503
- }
504
- state_declarations() {
505
- return this._state_declarations;
506
- }
507
- fsl_version() {
508
- return this._fsl_version;
509
- }
510
- machine_state() {
511
- return {
512
- internal_state_impl_version: 1,
513
- actions: this._actions,
514
- edge_map: this._edge_map,
515
- edges: this._edges,
516
- named_transitions: this._named_transitions,
517
- reverse_actions: this._reverse_actions,
518
- // reverse_action_targets : this._reverse_action_targets,
519
- state: this._state,
520
- states: this._states
521
- };
522
- }
523
- /*
524
- load_machine_state(): boolean {
525
- return false; // todo whargarbl
526
- }
527
- */
528
- states() {
529
- return Array.from(this._states.keys());
530
- }
531
- state_for(whichState) {
532
- const state = this._states.get(whichState);
533
- if (state) {
534
- return state;
535
- }
536
- else {
537
- throw new Error(`no such state ${JSON.stringify(state)}`);
538
- }
539
- }
540
- has_state(whichState) {
541
- return this._states.get(whichState) !== undefined;
542
- }
543
- list_edges() {
544
- return this._edges;
545
- }
546
- list_named_transitions() {
547
- return this._named_transitions;
548
- }
549
- list_actions() {
550
- return Array.from(this._actions.keys());
551
- }
552
- theme() {
553
- return this._theme; // constructor sets this to "default" otherwise
554
- }
555
- flow() {
556
- return this._flow;
557
- }
558
- get_transition_by_state_names(from, to) {
559
- const emg = this._edge_map.get(from);
560
- if (emg) {
561
- return emg.get(to);
562
- }
563
- else {
564
- return undefined;
565
- }
566
- }
567
- lookup_transition_for(from, to) {
568
- const id = this.get_transition_by_state_names(from, to);
569
- return ((id === undefined) || (id === null)) ? undefined : this._edges[id];
570
- }
571
- list_transitions(whichState = this.state()) {
572
- return { entrances: this.list_entrances(whichState), exits: this.list_exits(whichState) };
573
- }
574
- list_entrances(whichState = this.state()) {
575
- return (this._states.get(whichState)
576
- || { from: undefined }).from
577
- || [];
578
- }
579
- list_exits(whichState = this.state()) {
580
- return (this._states.get(whichState)
581
- || { to: undefined }).to
582
- || [];
583
- }
584
- probable_exits_for(whichState) {
585
- const wstate = this._states.get(whichState);
586
- if (!(wstate)) {
587
- throw new Error(`No such state ${JSON.stringify(whichState)} in probable_exits_for`);
588
- }
589
- const wstate_to = wstate.to, wtf = wstate_to
590
- .map((ws) => this.lookup_transition_for(this.state(), ws))
591
- .filter(Boolean);
592
- return wtf;
593
- }
594
- probabilistic_transition() {
595
- const selected = weighted_rand_select(this.probable_exits_for(this.state()));
596
- return this.transition(selected.to);
597
- }
598
- probabilistic_walk(n) {
599
- return seq(n)
600
- .map(() => {
601
- const state_was = this.state();
602
- this.probabilistic_transition();
603
- return state_was;
604
- })
605
- .concat([this.state()]);
606
- }
607
- probabilistic_histo_walk(n) {
608
- return histograph(this.probabilistic_walk(n));
609
- }
610
- actions(whichState = this.state()) {
611
- const wstate = this._reverse_actions.get(whichState);
612
- if (wstate) {
613
- return Array.from(wstate.keys());
614
- }
615
- else {
616
- throw new Error(`No such state ${JSON.stringify(whichState)}`);
617
- }
618
- }
619
- list_states_having_action(whichState) {
620
- const wstate = this._actions.get(whichState);
621
- if (wstate) {
622
- return Array.from(wstate.keys());
623
- }
624
- else {
625
- throw new Error(`No such state ${JSON.stringify(whichState)}`);
626
- }
627
- }
628
- // comeback
629
- /*
630
- list_entrance_actions(whichState: mNT = this.state() ) : Array<mNT> {
631
- return [... (this._reverse_action_targets.get(whichState) || new Map()).values()] // wasteful
632
- .map( (edgeId:any) => (this._edges[edgeId] : any)) // whargarbl burn out any
633
- .filter( (o:any) => o.to === whichState)
634
- .map( filtered => filtered.from );
635
- }
636
- */
637
- list_exit_actions(whichState = this.state()) {
638
- const ra_base = this._reverse_actions.get(whichState);
639
- if (!(ra_base)) {
640
- throw new Error(`No such state ${JSON.stringify(whichState)}`);
641
- }
642
- return Array.from(ra_base.values())
643
- .map((edgeId) => this._edges[edgeId])
644
- .filter((o) => o.from === whichState)
645
- .map((filtered) => filtered.action);
646
- }
647
- probable_action_exits(whichState = this.state()) {
648
- const ra_base = this._reverse_actions.get(whichState);
649
- if (!(ra_base)) {
650
- throw new Error(`No such state ${JSON.stringify(whichState)}`);
651
- }
652
- return Array.from(ra_base.values())
653
- .map((edgeId) => this._edges[edgeId])
654
- .filter((o) => o.from === whichState)
655
- .map((filtered) => ({ action: filtered.action,
656
- probability: filtered.probability
657
- }));
658
- }
659
- // TODO FIXME test that is_unenterable on non-state throws
660
- is_unenterable(whichState) {
661
- if (!(this.has_state(whichState))) {
662
- throw new Error(`No such state ${whichState}`);
663
- }
664
- return this.list_entrances(whichState).length === 0;
665
- }
666
- has_unenterables() {
667
- return this.states().some((x) => this.is_unenterable(x));
668
- }
669
- is_terminal() {
670
- return this.state_is_terminal(this.state());
671
- }
672
- // TODO FIXME test that state_is_terminal on non-state throws
673
- state_is_terminal(whichState) {
674
- if (!(this.has_state(whichState))) {
675
- throw new Error(`No such state ${whichState}`);
676
- }
677
- return this.list_exits(whichState).length === 0;
678
- }
679
- has_terminals() {
680
- return this.states().some((x) => this.state_is_terminal(x));
681
- }
682
- is_complete() {
683
- return this.state_is_complete(this.state());
684
- }
685
- state_is_complete(whichState) {
686
- const wstate = this._states.get(whichState);
687
- if (wstate) {
688
- return wstate.complete;
689
- }
690
- else {
691
- throw new Error(`No such state ${JSON.stringify(whichState)}`);
692
- }
693
- }
694
- has_completes() {
695
- return this.states().some((x) => this.state_is_complete(x));
696
- }
697
- action(name, newData) {
698
- // todo whargarbl implement hooks
699
- // todo whargarbl implement data stuff
700
- // todo major incomplete whargarbl comeback
701
- if (this.valid_action(name, newData)) {
702
- const edge = this.current_action_edge_for(name);
703
- this._state = edge.to;
704
- return true;
705
- }
706
- else {
707
- return false;
708
- }
709
- }
710
- transition(newState, newData) {
711
- // todo whargarbl implement hooks
712
- // todo whargarbl implement data stuff
713
- // todo major incomplete whargarbl comeback
714
- if (this.valid_transition(newState, newData)) {
715
- this._state = newState;
716
- return true;
717
- }
718
- else {
719
- return false;
720
- }
721
- }
722
- // can leave machine in inconsistent state. generally do not use
723
- force_transition(newState, newData) {
724
- // todo whargarbl implement hooks
725
- // todo whargarbl implement data stuff
726
- // todo major incomplete whargarbl comeback
727
- if (this.valid_force_transition(newState, newData)) {
728
- this._state = newState;
729
- return true;
730
- }
731
- else {
732
- return false;
733
- }
734
- }
735
- current_action_for(action) {
736
- const action_base = this._actions.get(action);
737
- return action_base ? action_base.get(this.state()) : undefined;
738
- }
739
- current_action_edge_for(action) {
740
- const idx = this.current_action_for(action);
741
- if ((idx === undefined) || (idx === null)) {
742
- throw new Error(`No such action ${JSON.stringify(action)}`);
743
- }
744
- return this._edges[idx];
745
- }
746
- valid_action(action, _newData) {
747
- // todo whargarbl implement hooks
748
- // todo whargarbl implement data stuff
749
- // todo major incomplete whargarbl comeback
750
- return this.current_action_for(action) !== undefined;
751
- }
752
- valid_transition(newState, _newData) {
753
- // todo whargarbl implement hooks
754
- // todo whargarbl implement data stuff
755
- // todo major incomplete whargarbl comeback
756
- const transition_for = this.lookup_transition_for(this.state(), newState);
757
- if (!(transition_for)) {
758
- return false;
759
- }
760
- if (transition_for.forced_only) {
761
- return false;
762
- }
763
- return true;
764
- }
765
- valid_force_transition(newState, _newData) {
766
- // todo whargarbl implement hooks
767
- // todo whargarbl implement data stuff
768
- // todo major incomplete whargarbl comeback
769
- return (this.lookup_transition_for(this.state(), newState) !== undefined);
770
- }
771
- /* eslint-disable no-use-before-define */
772
- /* eslint-disable class-methods-use-this */
773
- sm(template_strings, ...remainder /* , arguments */) {
774
- return sm(template_strings, ...remainder);
775
- }
776
- }
777
- function sm(template_strings, ...remainder /* , arguments */) {
778
- // foo`a${1}b${2}c` will come in as (['a','b','c'],1,2)
779
- // this includes when a and c are empty strings
780
- // therefore template_strings will always have one more el than template_args
781
- // therefore map the smaller container and toss the last one on on the way out
782
- return new Machine(make(template_strings.reduce(
783
- // in general avoiding `arguments` is smart. however with the template
784
- // string notation, as designed, it's not really worth the hassle
785
- /* eslint-disable prefer-rest-params */
786
- (acc, val, idx) => `${acc}${remainder[idx - 1]}${val}` // arguments[0] is never loaded, so args doesn't need to be gated
787
- /* eslint-enable prefer-rest-params */
788
- )));
789
- }
790
- export { version, transfer_state_properties, Machine, make, wrap_parse as parse, compile, sm, arrow_direction, arrow_left_kind, arrow_right_kind,
791
- // WHARGARBL TODO these should be exported to a utility library
792
- seq, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key };
1
+ // whargarbl lots of these return arrays could/should be sets
2
+ import { reduce as reduce_to_639 } from 'reduce-to-639-1';
3
+ import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key, array_box_if_string } from './jssm_util';
4
+ import { parse } from './jssm-dot'; // TODO FIXME WHARGARBL this could be post-typed
5
+ import { version } from './version'; // replaced from package.js in build // TODO FIXME currently broken
6
+ /* eslint-disable complexity */
7
+ function arrow_direction(arrow) {
8
+ switch (String(arrow)) {
9
+ case '->':
10
+ case '→':
11
+ case '=>':
12
+ case '⇒':
13
+ case '~>':
14
+ case '↛':
15
+ return 'right';
16
+ case '<-':
17
+ case '←':
18
+ case '<=':
19
+ case '⇐':
20
+ case '<~':
21
+ case '↚':
22
+ return 'left';
23
+ case '<->':
24
+ case '↔':
25
+ case '<-=>':
26
+ case '←⇒':
27
+ case '←=>':
28
+ case '<-⇒':
29
+ case '<-~>':
30
+ case '←↛':
31
+ case '←~>':
32
+ case '<-↛':
33
+ case '<=>':
34
+ case '⇔':
35
+ case '<=->':
36
+ case '⇐→':
37
+ case '⇐->':
38
+ case '<=→':
39
+ case '<=~>':
40
+ case '⇐↛':
41
+ case '⇐~>':
42
+ case '<=↛':
43
+ case '<~>':
44
+ case '↮':
45
+ case '<~->':
46
+ case '↚→':
47
+ case '↚->':
48
+ case '<~→':
49
+ case '<~=>':
50
+ case '↚⇒':
51
+ case '↚=>':
52
+ case '<~⇒':
53
+ return 'both';
54
+ default:
55
+ throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
56
+ }
57
+ }
58
+ /* eslint-enable complexity */
59
+ /* eslint-disable complexity */
60
+ function arrow_left_kind(arrow) {
61
+ switch (String(arrow)) {
62
+ case '->':
63
+ case '→':
64
+ case '=>':
65
+ case '⇒':
66
+ case '~>':
67
+ case '↛':
68
+ return 'none';
69
+ case '<-':
70
+ case '←':
71
+ case '<->':
72
+ case '↔':
73
+ case '<-=>':
74
+ case '←⇒':
75
+ case '<-~>':
76
+ case '←↛':
77
+ return 'legal';
78
+ case '<=':
79
+ case '⇐':
80
+ case '<=>':
81
+ case '⇔':
82
+ case '<=->':
83
+ case '⇐→':
84
+ case '<=~>':
85
+ case '⇐↛':
86
+ return 'main';
87
+ case '<~':
88
+ case '↚':
89
+ case '<~>':
90
+ case '↮':
91
+ case '<~->':
92
+ case '↚→':
93
+ case '<~=>':
94
+ case '↚⇒':
95
+ return 'forced';
96
+ default:
97
+ throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
98
+ }
99
+ }
100
+ /* eslint-enable complexity */
101
+ /* eslint-disable complexity */
102
+ function arrow_right_kind(arrow) {
103
+ switch (String(arrow)) {
104
+ case '<-':
105
+ case '←':
106
+ case '<=':
107
+ case '⇐':
108
+ case '<~':
109
+ case '↚':
110
+ return 'none';
111
+ case '->':
112
+ case '→':
113
+ case '<->':
114
+ case '↔':
115
+ case '<=->':
116
+ case '⇐→':
117
+ case '<~->':
118
+ case '↚→':
119
+ return 'legal';
120
+ case '=>':
121
+ case '⇒':
122
+ case '<=>':
123
+ case '⇔':
124
+ case '<-=>':
125
+ case '←⇒':
126
+ case '<~=>':
127
+ case '↚⇒':
128
+ return 'main';
129
+ case '~>':
130
+ case '↛':
131
+ case '<~>':
132
+ case '↮':
133
+ case '<-~>':
134
+ case '←↛':
135
+ case '<=~>':
136
+ case '⇐↛':
137
+ return 'forced';
138
+ default:
139
+ throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
140
+ }
141
+ }
142
+ /* eslint-enable complexity */
143
+ function makeTransition(this_se, from, to, isRight, _wasList, _wasIndex) {
144
+ const kind = isRight ? arrow_right_kind(this_se.kind) : arrow_left_kind(this_se.kind), edge = {
145
+ from,
146
+ to,
147
+ kind,
148
+ forced_only: kind === 'forced',
149
+ main_path: kind === 'main'
150
+ };
151
+ // if ((wasList !== undefined) && (wasIndex === undefined)) { throw new TypeError("Must have an index if transition was in a list"); }
152
+ // if ((wasIndex !== undefined) && (wasList === undefined)) { throw new TypeError("Must be in a list if transition has an index"); }
153
+ /*
154
+ if (typeof edge.to === 'object') {
155
+
156
+ if (edge.to.key === 'cycle') {
157
+ if (wasList === undefined) { throw "Must have a waslist if a to is type cycle"; }
158
+ const nextIndex = wrapBy(wasIndex, edge.to.value, wasList.length);
159
+ edge.to = wasList[nextIndex];
160
+ }
161
+
162
+ }
163
+ */
164
+ const action = isRight ? 'r_action' : 'l_action', probability = isRight ? 'r_probability' : 'l_probability';
165
+ if (this_se[action]) {
166
+ edge.action = this_se[action];
167
+ }
168
+ if (this_se[probability]) {
169
+ edge.probability = this_se[probability];
170
+ }
171
+ return edge;
172
+ }
173
+ function wrap_parse(input, options) {
174
+ return parse(input, options || {});
175
+ }
176
+ function compile_rule_transition_step(acc, from, to, this_se, next_se) {
177
+ const edges = [];
178
+ const uFrom = (Array.isArray(from) ? from : [from]), uTo = (Array.isArray(to) ? to : [to]);
179
+ uFrom.map((f) => {
180
+ uTo.map((t) => {
181
+ const right = makeTransition(this_se, f, t, true);
182
+ if (right.kind !== 'none') {
183
+ edges.push(right);
184
+ }
185
+ const left = makeTransition(this_se, t, f, false);
186
+ if (left.kind !== 'none') {
187
+ edges.push(left);
188
+ }
189
+ });
190
+ });
191
+ const new_acc = acc.concat(edges);
192
+ if (next_se) {
193
+ return compile_rule_transition_step(new_acc, to, next_se.to, next_se, next_se.se);
194
+ }
195
+ else {
196
+ return new_acc;
197
+ }
198
+ }
199
+ function compile_rule_handle_transition(rule) {
200
+ return compile_rule_transition_step([], rule.from, rule.se.to, rule.se, rule.se.se);
201
+ }
202
+ function compile_rule_handler(rule) {
203
+ if (rule.key === 'transition') {
204
+ return { agg_as: 'transition', val: compile_rule_handle_transition(rule) };
205
+ }
206
+ if (rule.key === 'machine_language') {
207
+ return { agg_as: 'machine_language', val: reduce_to_639(rule.value) };
208
+ }
209
+ if (rule.key === 'state_declaration') {
210
+ if (!rule.name) {
211
+ throw new Error('State declarations must have a name');
212
+ }
213
+ return { agg_as: 'state_declaration', val: { state: rule.name, declarations: rule.value } };
214
+ }
215
+ if (['arrange_declaration', 'arrange_start_declaration',
216
+ 'arrange_end_declaration'].includes(rule.key)) {
217
+ return { agg_as: rule.key, val: [rule.value] };
218
+ }
219
+ const tautologies = [
220
+ 'graph_layout', 'start_states', 'end_states', 'machine_name', 'machine_version',
221
+ 'machine_comment', 'machine_author', 'machine_contributor', 'machine_definition',
222
+ 'machine_reference', 'machine_license', 'fsl_version', 'state_config', 'theme',
223
+ 'flow', 'dot_preamble'
224
+ ];
225
+ if (tautologies.includes(rule.key)) {
226
+ return { agg_as: rule.key, val: rule.value };
227
+ }
228
+ throw new Error(`compile_rule_handler: Unknown rule: ${JSON.stringify(rule)}`);
229
+ }
230
+ function compile(tree) {
231
+ const results = {
232
+ graph_layout: [],
233
+ transition: [],
234
+ start_states: [],
235
+ end_states: [],
236
+ state_config: [],
237
+ state_declaration: [],
238
+ fsl_version: [],
239
+ machine_author: [],
240
+ machine_comment: [],
241
+ machine_contributor: [],
242
+ machine_definition: [],
243
+ machine_language: [],
244
+ machine_license: [],
245
+ machine_name: [],
246
+ machine_reference: [],
247
+ theme: [],
248
+ flow: [],
249
+ dot_preamble: [],
250
+ arrange_declaration: [],
251
+ arrange_start_declaration: [],
252
+ arrange_end_declaration: [],
253
+ machine_version: []
254
+ };
255
+ tree.map((tr) => {
256
+ const rule = compile_rule_handler(tr), agg_as = rule.agg_as, val = rule.val; // TODO FIXME no any
257
+ results[agg_as] = results[agg_as].concat(val);
258
+ });
259
+ const assembled_transitions = [].concat(...results['transition']);
260
+ const result_cfg = {
261
+ start_states: results.start_states.length ? results.start_states : [assembled_transitions[0].from],
262
+ transitions: assembled_transitions
263
+ };
264
+ const oneOnlyKeys = [
265
+ 'graph_layout', 'machine_name', 'machine_version', 'machine_comment',
266
+ 'fsl_version', 'machine_license', 'machine_definition', 'machine_language',
267
+ 'theme', 'flow', 'dot_preamble'
268
+ ];
269
+ oneOnlyKeys.map((oneOnlyKey) => {
270
+ if (results[oneOnlyKey].length > 1) {
271
+ throw new Error(`May only have one ${oneOnlyKey} statement maximum: ${JSON.stringify(results[oneOnlyKey])}`);
272
+ }
273
+ else {
274
+ if (results[oneOnlyKey].length) {
275
+ result_cfg[oneOnlyKey] = results[oneOnlyKey][0];
276
+ }
277
+ }
278
+ });
279
+ ['arrange_declaration', 'arrange_start_declaration', 'arrange_end_declaration',
280
+ 'machine_author', 'machine_contributor', 'machine_reference', 'state_declaration'].map((multiKey) => {
281
+ if (results[multiKey].length) {
282
+ result_cfg[multiKey] = results[multiKey];
283
+ }
284
+ });
285
+ return result_cfg;
286
+ }
287
+ function make(plan) {
288
+ return compile(wrap_parse(plan));
289
+ }
290
+ function transfer_state_properties(state_decl) {
291
+ state_decl.declarations.map((d) => {
292
+ switch (d.key) {
293
+ case 'shape':
294
+ state_decl.shape = d.value;
295
+ break;
296
+ case 'color':
297
+ state_decl.color = d.value;
298
+ break;
299
+ case 'corners':
300
+ state_decl.corners = d.value;
301
+ break;
302
+ case 'linestyle':
303
+ state_decl.linestyle = d.value;
304
+ break;
305
+ case 'text-color':
306
+ state_decl.textColor = d.value;
307
+ break;
308
+ case 'background-color':
309
+ state_decl.backgroundColor = d.value;
310
+ break;
311
+ case 'border-color':
312
+ state_decl.borderColor = d.value;
313
+ break;
314
+ default: throw new Error(`Unknown state property: '${JSON.stringify(d)}'`);
315
+ }
316
+ });
317
+ return state_decl;
318
+ }
319
+ class Machine {
320
+ // whargarbl this badly needs to be broken up, monolith master
321
+ constructor({ start_states, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], theme = 'default', flow = 'down', graph_layout = 'dot' }) {
322
+ this._state = start_states[0];
323
+ this._states = new Map();
324
+ this._state_declarations = new Map();
325
+ this._edges = [];
326
+ this._edge_map = new Map();
327
+ this._named_transitions = new Map();
328
+ this._actions = new Map();
329
+ this._reverse_actions = new Map();
330
+ this._reverse_action_targets = new Map(); // todo
331
+ this._machine_author = array_box_if_string(machine_author);
332
+ this._machine_comment = machine_comment;
333
+ this._machine_contributor = array_box_if_string(machine_contributor);
334
+ this._machine_definition = machine_definition;
335
+ this._machine_language = machine_language;
336
+ this._machine_license = machine_license;
337
+ this._machine_name = machine_name;
338
+ this._machine_version = machine_version;
339
+ this._raw_state_declaration = state_declaration || [];
340
+ this._fsl_version = fsl_version;
341
+ this._arrange_declaration = arrange_declaration;
342
+ this._arrange_start_declaration = arrange_start_declaration;
343
+ this._arrange_end_declaration = arrange_end_declaration;
344
+ this._dot_preamble = dot_preamble;
345
+ this._theme = theme;
346
+ this._flow = flow;
347
+ this._graph_layout = graph_layout;
348
+ if (state_declaration) {
349
+ state_declaration.map((state_decl) => {
350
+ if (this._state_declarations.has(state_decl.state)) { // no repeats
351
+ throw new Error(`Added the same state declaration twice: ${JSON.stringify(state_decl.state)}`);
352
+ }
353
+ this._state_declarations.set(state_decl.state, transfer_state_properties(state_decl));
354
+ });
355
+ }
356
+ transitions.map((tr) => {
357
+ if (tr.from === undefined) {
358
+ throw new Error(`transition must define 'from': ${JSON.stringify(tr)}`);
359
+ }
360
+ if (tr.to === undefined) {
361
+ throw new Error(`transition must define 'to': ${JSON.stringify(tr)}`);
362
+ }
363
+ // get the cursors. what a mess
364
+ const cursor_from = this._states.get(tr.from)
365
+ || { name: tr.from, from: [], to: [], complete: complete.includes(tr.from) };
366
+ if (!(this._states.has(tr.from))) {
367
+ this._new_state(cursor_from);
368
+ }
369
+ const cursor_to = this._states.get(tr.to)
370
+ || { name: tr.to, from: [], to: [], complete: complete.includes(tr.to) };
371
+ if (!(this._states.has(tr.to))) {
372
+ this._new_state(cursor_to);
373
+ }
374
+ // guard against existing connections being re-added
375
+ if (cursor_from.to.includes(tr.to)) {
376
+ throw new Error(`already has ${JSON.stringify(tr.from)} to ${JSON.stringify(tr.to)}`);
377
+ }
378
+ else {
379
+ cursor_from.to.push(tr.to);
380
+ cursor_to.from.push(tr.from);
381
+ }
382
+ // add the edge; note its id
383
+ this._edges.push(tr);
384
+ const thisEdgeId = this._edges.length - 1;
385
+ // guard against repeating a transition name
386
+ if (tr.name) {
387
+ if (this._named_transitions.has(tr.name)) {
388
+ throw new Error(`named transition "${JSON.stringify(tr.name)}" already created`);
389
+ }
390
+ else {
391
+ this._named_transitions.set(tr.name, thisEdgeId);
392
+ }
393
+ }
394
+ // set up the mapping, so that edges can be looked up by endpoint pairs
395
+ const from_mapping = this._edge_map.get(tr.from) || new Map();
396
+ if (!(this._edge_map.has(tr.from))) {
397
+ this._edge_map.set(tr.from, from_mapping);
398
+ }
399
+ // const to_mapping = from_mapping.get(tr.to);
400
+ from_mapping.set(tr.to, thisEdgeId); // already checked that this mapping doesn't exist, above
401
+ // set up the action mapping, so that actions can be looked up by origin
402
+ if (tr.action) {
403
+ // forward mapping first by action name
404
+ let actionMap = this._actions.get(tr.action); // TODO FIXME ?Map equiv
405
+ if (!(actionMap)) {
406
+ actionMap = new Map();
407
+ this._actions.set(tr.action, actionMap);
408
+ }
409
+ if (actionMap.has(tr.from)) {
410
+ throw new Error(`action ${JSON.stringify(tr.action)} already attached to origin ${JSON.stringify(tr.from)}`);
411
+ }
412
+ else {
413
+ actionMap.set(tr.from, thisEdgeId);
414
+ }
415
+ // reverse mapping first by state origin name
416
+ let rActionMap = this._reverse_actions.get(tr.from); // TODO FIXME ?Map equiv
417
+ if (!(rActionMap)) {
418
+ rActionMap = new Map();
419
+ this._reverse_actions.set(tr.from, rActionMap);
420
+ }
421
+ // no need to test for reverse mapping pre-presence;
422
+ // forward mapping already covers collisions
423
+ rActionMap.set(tr.action, thisEdgeId);
424
+ // reverse mapping first by state target name
425
+ if (!(this._reverse_action_targets.has(tr.to))) {
426
+ this._reverse_action_targets.set(tr.to, new Map());
427
+ }
428
+ /* todo comeback
429
+ fundamental problem is roActionMap needs to be a multimap
430
+ const roActionMap = this._reverse_action_targets.get(tr.to); // wasteful - already did has - refactor
431
+ if (roActionMap) {
432
+ if (roActionMap.has(tr.action)) {
433
+ throw new Error(`ro-action ${tr.to} already attached to action ${tr.action}`);
434
+ } else {
435
+ roActionMap.set(tr.action, thisEdgeId);
436
+ }
437
+ } else {
438
+ throw new Error('should be impossible - flow doesn\'t know .set precedes .get yet again. severe error?');
439
+ }
440
+ */
441
+ }
442
+ });
443
+ }
444
+ _new_state(state_config) {
445
+ if (this._states.has(state_config.name)) {
446
+ throw new Error(`state ${JSON.stringify(state_config.name)} already exists`);
447
+ }
448
+ this._states.set(state_config.name, state_config);
449
+ return state_config.name;
450
+ }
451
+ state() {
452
+ return this._state;
453
+ }
454
+ /* whargarbl todo major
455
+ when we reimplement this, reintroduce this change to the is_final call
456
+
457
+ is_changing(): boolean {
458
+ return true; // todo whargarbl
459
+ }
460
+ */
461
+ state_is_final(whichState) {
462
+ return ((this.state_is_terminal(whichState)) && (this.state_is_complete(whichState)));
463
+ }
464
+ is_final() {
465
+ // return ((!this.is_changing()) && this.state_is_final(this.state()));
466
+ return this.state_is_final(this.state());
467
+ }
468
+ graph_layout() {
469
+ return this._graph_layout;
470
+ }
471
+ dot_preamble() {
472
+ return this._dot_preamble;
473
+ }
474
+ machine_author() {
475
+ return this._machine_author;
476
+ }
477
+ machine_comment() {
478
+ return this._machine_comment;
479
+ }
480
+ machine_contributor() {
481
+ return this._machine_contributor;
482
+ }
483
+ machine_definition() {
484
+ return this._machine_definition;
485
+ }
486
+ machine_language() {
487
+ return this._machine_language;
488
+ }
489
+ machine_license() {
490
+ return this._machine_license;
491
+ }
492
+ machine_name() {
493
+ return this._machine_name;
494
+ }
495
+ machine_version() {
496
+ return this._machine_version;
497
+ }
498
+ raw_state_declarations() {
499
+ return this._raw_state_declaration;
500
+ }
501
+ state_declaration(which) {
502
+ return this._state_declarations.get(which);
503
+ }
504
+ state_declarations() {
505
+ return this._state_declarations;
506
+ }
507
+ fsl_version() {
508
+ return this._fsl_version;
509
+ }
510
+ machine_state() {
511
+ return {
512
+ internal_state_impl_version: 1,
513
+ actions: this._actions,
514
+ edge_map: this._edge_map,
515
+ edges: this._edges,
516
+ named_transitions: this._named_transitions,
517
+ reverse_actions: this._reverse_actions,
518
+ // reverse_action_targets : this._reverse_action_targets,
519
+ state: this._state,
520
+ states: this._states
521
+ };
522
+ }
523
+ /*
524
+ load_machine_state(): boolean {
525
+ return false; // todo whargarbl
526
+ }
527
+ */
528
+ states() {
529
+ return Array.from(this._states.keys());
530
+ }
531
+ state_for(whichState) {
532
+ const state = this._states.get(whichState);
533
+ if (state) {
534
+ return state;
535
+ }
536
+ else {
537
+ throw new Error(`no such state ${JSON.stringify(state)}`);
538
+ }
539
+ }
540
+ has_state(whichState) {
541
+ return this._states.get(whichState) !== undefined;
542
+ }
543
+ list_edges() {
544
+ return this._edges;
545
+ }
546
+ list_named_transitions() {
547
+ return this._named_transitions;
548
+ }
549
+ list_actions() {
550
+ return Array.from(this._actions.keys());
551
+ }
552
+ theme() {
553
+ return this._theme; // constructor sets this to "default" otherwise
554
+ }
555
+ flow() {
556
+ return this._flow;
557
+ }
558
+ get_transition_by_state_names(from, to) {
559
+ const emg = this._edge_map.get(from);
560
+ if (emg) {
561
+ return emg.get(to);
562
+ }
563
+ else {
564
+ return undefined;
565
+ }
566
+ }
567
+ lookup_transition_for(from, to) {
568
+ const id = this.get_transition_by_state_names(from, to);
569
+ return ((id === undefined) || (id === null)) ? undefined : this._edges[id];
570
+ }
571
+ list_transitions(whichState = this.state()) {
572
+ return { entrances: this.list_entrances(whichState), exits: this.list_exits(whichState) };
573
+ }
574
+ list_entrances(whichState = this.state()) {
575
+ return (this._states.get(whichState)
576
+ || { from: undefined }).from
577
+ || [];
578
+ }
579
+ list_exits(whichState = this.state()) {
580
+ return (this._states.get(whichState)
581
+ || { to: undefined }).to
582
+ || [];
583
+ }
584
+ probable_exits_for(whichState) {
585
+ const wstate = this._states.get(whichState);
586
+ if (!(wstate)) {
587
+ throw new Error(`No such state ${JSON.stringify(whichState)} in probable_exits_for`);
588
+ }
589
+ const wstate_to = wstate.to, wtf = wstate_to
590
+ .map((ws) => this.lookup_transition_for(this.state(), ws))
591
+ .filter(Boolean);
592
+ return wtf;
593
+ }
594
+ probabilistic_transition() {
595
+ const selected = weighted_rand_select(this.probable_exits_for(this.state()));
596
+ return this.transition(selected.to);
597
+ }
598
+ probabilistic_walk(n) {
599
+ return seq(n)
600
+ .map(() => {
601
+ const state_was = this.state();
602
+ this.probabilistic_transition();
603
+ return state_was;
604
+ })
605
+ .concat([this.state()]);
606
+ }
607
+ probabilistic_histo_walk(n) {
608
+ return histograph(this.probabilistic_walk(n));
609
+ }
610
+ actions(whichState = this.state()) {
611
+ const wstate = this._reverse_actions.get(whichState);
612
+ if (wstate) {
613
+ return Array.from(wstate.keys());
614
+ }
615
+ else {
616
+ throw new Error(`No such state ${JSON.stringify(whichState)}`);
617
+ }
618
+ }
619
+ list_states_having_action(whichState) {
620
+ const wstate = this._actions.get(whichState);
621
+ if (wstate) {
622
+ return Array.from(wstate.keys());
623
+ }
624
+ else {
625
+ throw new Error(`No such state ${JSON.stringify(whichState)}`);
626
+ }
627
+ }
628
+ // comeback
629
+ /*
630
+ list_entrance_actions(whichState: mNT = this.state() ) : Array<mNT> {
631
+ return [... (this._reverse_action_targets.get(whichState) || new Map()).values()] // wasteful
632
+ .map( (edgeId:any) => (this._edges[edgeId] : any)) // whargarbl burn out any
633
+ .filter( (o:any) => o.to === whichState)
634
+ .map( filtered => filtered.from );
635
+ }
636
+ */
637
+ list_exit_actions(whichState = this.state()) {
638
+ const ra_base = this._reverse_actions.get(whichState);
639
+ if (!(ra_base)) {
640
+ throw new Error(`No such state ${JSON.stringify(whichState)}`);
641
+ }
642
+ return Array.from(ra_base.values())
643
+ .map((edgeId) => this._edges[edgeId])
644
+ .filter((o) => o.from === whichState)
645
+ .map((filtered) => filtered.action);
646
+ }
647
+ probable_action_exits(whichState = this.state()) {
648
+ const ra_base = this._reverse_actions.get(whichState);
649
+ if (!(ra_base)) {
650
+ throw new Error(`No such state ${JSON.stringify(whichState)}`);
651
+ }
652
+ return Array.from(ra_base.values())
653
+ .map((edgeId) => this._edges[edgeId])
654
+ .filter((o) => o.from === whichState)
655
+ .map((filtered) => ({ action: filtered.action,
656
+ probability: filtered.probability
657
+ }));
658
+ }
659
+ // TODO FIXME test that is_unenterable on non-state throws
660
+ is_unenterable(whichState) {
661
+ if (!(this.has_state(whichState))) {
662
+ throw new Error(`No such state ${whichState}`);
663
+ }
664
+ return this.list_entrances(whichState).length === 0;
665
+ }
666
+ has_unenterables() {
667
+ return this.states().some((x) => this.is_unenterable(x));
668
+ }
669
+ is_terminal() {
670
+ return this.state_is_terminal(this.state());
671
+ }
672
+ // TODO FIXME test that state_is_terminal on non-state throws
673
+ state_is_terminal(whichState) {
674
+ if (!(this.has_state(whichState))) {
675
+ throw new Error(`No such state ${whichState}`);
676
+ }
677
+ return this.list_exits(whichState).length === 0;
678
+ }
679
+ has_terminals() {
680
+ return this.states().some((x) => this.state_is_terminal(x));
681
+ }
682
+ is_complete() {
683
+ return this.state_is_complete(this.state());
684
+ }
685
+ state_is_complete(whichState) {
686
+ const wstate = this._states.get(whichState);
687
+ if (wstate) {
688
+ return wstate.complete;
689
+ }
690
+ else {
691
+ throw new Error(`No such state ${JSON.stringify(whichState)}`);
692
+ }
693
+ }
694
+ has_completes() {
695
+ return this.states().some((x) => this.state_is_complete(x));
696
+ }
697
+ action(name, newData) {
698
+ // todo whargarbl implement hooks
699
+ // todo whargarbl implement data stuff
700
+ // todo major incomplete whargarbl comeback
701
+ if (this.valid_action(name, newData)) {
702
+ const edge = this.current_action_edge_for(name);
703
+ this._state = edge.to;
704
+ return true;
705
+ }
706
+ else {
707
+ return false;
708
+ }
709
+ }
710
+ transition(newState, newData) {
711
+ // todo whargarbl implement hooks
712
+ // todo whargarbl implement data stuff
713
+ // todo major incomplete whargarbl comeback
714
+ if (this.valid_transition(newState, newData)) {
715
+ this._state = newState;
716
+ return true;
717
+ }
718
+ else {
719
+ return false;
720
+ }
721
+ }
722
+ // can leave machine in inconsistent state. generally do not use
723
+ force_transition(newState, newData) {
724
+ // todo whargarbl implement hooks
725
+ // todo whargarbl implement data stuff
726
+ // todo major incomplete whargarbl comeback
727
+ if (this.valid_force_transition(newState, newData)) {
728
+ this._state = newState;
729
+ return true;
730
+ }
731
+ else {
732
+ return false;
733
+ }
734
+ }
735
+ current_action_for(action) {
736
+ const action_base = this._actions.get(action);
737
+ return action_base ? action_base.get(this.state()) : undefined;
738
+ }
739
+ current_action_edge_for(action) {
740
+ const idx = this.current_action_for(action);
741
+ if ((idx === undefined) || (idx === null)) {
742
+ throw new Error(`No such action ${JSON.stringify(action)}`);
743
+ }
744
+ return this._edges[idx];
745
+ }
746
+ valid_action(action, _newData) {
747
+ // todo whargarbl implement hooks
748
+ // todo whargarbl implement data stuff
749
+ // todo major incomplete whargarbl comeback
750
+ return this.current_action_for(action) !== undefined;
751
+ }
752
+ valid_transition(newState, _newData) {
753
+ // todo whargarbl implement hooks
754
+ // todo whargarbl implement data stuff
755
+ // todo major incomplete whargarbl comeback
756
+ const transition_for = this.lookup_transition_for(this.state(), newState);
757
+ if (!(transition_for)) {
758
+ return false;
759
+ }
760
+ if (transition_for.forced_only) {
761
+ return false;
762
+ }
763
+ return true;
764
+ }
765
+ valid_force_transition(newState, _newData) {
766
+ // todo whargarbl implement hooks
767
+ // todo whargarbl implement data stuff
768
+ // todo major incomplete whargarbl comeback
769
+ return (this.lookup_transition_for(this.state(), newState) !== undefined);
770
+ }
771
+ /* eslint-disable no-use-before-define */
772
+ /* eslint-disable class-methods-use-this */
773
+ sm(template_strings, ...remainder /* , arguments */) {
774
+ return sm(template_strings, ...remainder);
775
+ }
776
+ }
777
+ function sm(template_strings, ...remainder /* , arguments */) {
778
+ // foo`a${1}b${2}c` will come in as (['a','b','c'],1,2)
779
+ // this includes when a and c are empty strings
780
+ // therefore template_strings will always have one more el than template_args
781
+ // therefore map the smaller container and toss the last one on on the way out
782
+ return new Machine(make(template_strings.reduce(
783
+ // in general avoiding `arguments` is smart. however with the template
784
+ // string notation, as designed, it's not really worth the hassle
785
+ /* eslint-disable prefer-rest-params */
786
+ (acc, val, idx) => `${acc}${remainder[idx - 1]}${val}` // arguments[0] is never loaded, so args doesn't need to be gated
787
+ /* eslint-enable prefer-rest-params */
788
+ )));
789
+ }
790
+ export { version, transfer_state_properties, Machine, make, wrap_parse as parse, compile, sm, arrow_direction, arrow_left_kind, arrow_right_kind,
791
+ // WHARGARBL TODO these should be exported to a utility library
792
+ seq, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key };