UNPKG

23.7 kBJavaScriptView Raw
1import { reduce as reduce_to_639 } from 'reduce-to-639-1';
2import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key, array_box_if_string } from './jssm_util';
3import { parse } from './jssm-dot';
4import { version } from './version';
5function arrow_direction(arrow) {
6 switch (String(arrow)) {
7 case '->':
8 case '→':
9 case '=>':
10 case '⇒':
11 case '~>':
12 case '↛':
13 return 'right';
14 case '<-':
15 case '←':
16 case '<=':
17 case '⇐':
18 case '<~':
19 case '↚':
20 return 'left';
21 case '<->':
22 case '↔':
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 return 'both';
52 default:
53 throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
54 }
55}
56function arrow_left_kind(arrow) {
57 switch (String(arrow)) {
58 case '->':
59 case '→':
60 case '=>':
61 case '⇒':
62 case '~>':
63 case '↛':
64 return 'none';
65 case '<-':
66 case '←':
67 case '<->':
68 case '↔':
69 case '<-=>':
70 case '←⇒':
71 case '<-~>':
72 case '←↛':
73 return 'legal';
74 case '<=':
75 case '⇐':
76 case '<=>':
77 case '⇔':
78 case '<=->':
79 case '⇐→':
80 case '<=~>':
81 case '⇐↛':
82 return 'main';
83 case '<~':
84 case '↚':
85 case '<~>':
86 case '↮':
87 case '<~->':
88 case '↚→':
89 case '<~=>':
90 case '↚⇒':
91 return 'forced';
92 default:
93 throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
94 }
95}
96function arrow_right_kind(arrow) {
97 switch (String(arrow)) {
98 case '<-':
99 case '←':
100 case '<=':
101 case '⇐':
102 case '<~':
103 case '↚':
104 return 'none';
105 case '->':
106 case '→':
107 case '<->':
108 case '↔':
109 case '<=->':
110 case '⇐→':
111 case '<~->':
112 case '↚→':
113 return 'legal';
114 case '=>':
115 case '⇒':
116 case '<=>':
117 case '⇔':
118 case '<-=>':
119 case '←⇒':
120 case '<~=>':
121 case '↚⇒':
122 return 'main';
123 case '~>':
124 case '↛':
125 case '<~>':
126 case '↮':
127 case '<-~>':
128 case '←↛':
129 case '<=~>':
130 case '⇐↛':
131 return 'forced';
132 default:
133 throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
134 }
135}
136function makeTransition(this_se, from, to, isRight, _wasList, _wasIndex) {
137 const kind = isRight ? arrow_right_kind(this_se.kind) : arrow_left_kind(this_se.kind), edge = {
138 from,
139 to,
140 kind,
141 forced_only: kind === 'forced',
142 main_path: kind === 'main'
143 };
144 const action = isRight ? 'r_action' : 'l_action', probability = isRight ? 'r_probability' : 'l_probability';
145 if (this_se[action]) {
146 edge.action = this_se[action];
147 }
148 if (this_se[probability]) {
149 edge.probability = this_se[probability];
150 }
151 return edge;
152}
153function compile_rule_transition_step(acc, from, to, this_se, next_se) {
154 const edges = [];
155 const uFrom = (Array.isArray(from) ? from : [from]), uTo = (Array.isArray(to) ? to : [to]);
156 uFrom.map((f) => {
157 uTo.map((t) => {
158 const right = makeTransition(this_se, f, t, true);
159 if (right.kind !== 'none') {
160 edges.push(right);
161 }
162 const left = makeTransition(this_se, t, f, false);
163 if (left.kind !== 'none') {
164 edges.push(left);
165 }
166 });
167 });
168 const new_acc = acc.concat(edges);
169 if (next_se) {
170 return compile_rule_transition_step(new_acc, to, next_se.to, next_se, next_se.se);
171 }
172 else {
173 return new_acc;
174 }
175}
176function compile_rule_handle_transition(rule) {
177 return compile_rule_transition_step([], rule.from, rule.se.to, rule.se, rule.se.se);
178}
179function compile_rule_handler(rule) {
180 if (rule.key === 'transition') {
181 return { agg_as: 'transition', val: compile_rule_handle_transition(rule) };
182 }
183 if (rule.key === 'machine_language') {
184 return { agg_as: 'machine_language', val: reduce_to_639(rule.value) };
185 }
186 if (rule.key === 'state_declaration') {
187 if (!rule.name) {
188 throw new Error('State declarations must have a name');
189 }
190 return { agg_as: 'state_declaration', val: { state: rule.name, declarations: rule.value } };
191 }
192 if (['arrange_declaration', 'arrange_start_declaration',
193 'arrange_end_declaration'].includes(rule.key)) {
194 return { agg_as: rule.key, val: [rule.value] };
195 }
196 const tautologies = [
197 'graph_layout', 'start_states', 'end_states', 'machine_name', 'machine_version',
198 'machine_comment', 'machine_author', 'machine_contributor', 'machine_definition',
199 'machine_reference', 'machine_license', 'fsl_version', 'state_config', 'theme',
200 'flow', 'dot_preamble'
201 ];
202 if (tautologies.includes(rule.key)) {
203 return { agg_as: rule.key, val: rule.value };
204 }
205 throw new Error(`compile_rule_handler: Unknown rule: ${JSON.stringify(rule)}`);
206}
207function compile(tree) {
208 const results = {
209 graph_layout: [],
210 transition: [],
211 start_states: [],
212 end_states: [],
213 state_config: [],
214 state_declaration: [],
215 fsl_version: [],
216 machine_author: [],
217 machine_comment: [],
218 machine_contributor: [],
219 machine_definition: [],
220 machine_language: [],
221 machine_license: [],
222 machine_name: [],
223 machine_reference: [],
224 theme: [],
225 flow: [],
226 dot_preamble: [],
227 arrange_declaration: [],
228 arrange_start_declaration: [],
229 arrange_end_declaration: [],
230 machine_version: []
231 };
232 tree.map((tr) => {
233 const rule = compile_rule_handler(tr), agg_as = rule.agg_as, val = rule.val;
234 results[agg_as] = results[agg_as].concat(val);
235 });
236 const assembled_transitions = [].concat(...results['transition']);
237 const result_cfg = {
238 start_states: results.start_states.length ? results.start_states : [assembled_transitions[0].from],
239 transitions: assembled_transitions
240 };
241 const oneOnlyKeys = [
242 'graph_layout', 'machine_name', 'machine_version', 'machine_comment',
243 'fsl_version', 'machine_license', 'machine_definition', 'machine_language',
244 'theme', 'flow', 'dot_preamble'
245 ];
246 oneOnlyKeys.map((oneOnlyKey) => {
247 if (results[oneOnlyKey].length > 1) {
248 throw new Error(`May only have one ${oneOnlyKey} statement maximum: ${JSON.stringify(results[oneOnlyKey])}`);
249 }
250 else {
251 if (results[oneOnlyKey].length) {
252 result_cfg[oneOnlyKey] = results[oneOnlyKey][0];
253 }
254 }
255 });
256 ['arrange_declaration', 'arrange_start_declaration', 'arrange_end_declaration',
257 'machine_author', 'machine_contributor', 'machine_reference', 'state_declaration'].map((multiKey) => {
258 if (results[multiKey].length) {
259 result_cfg[multiKey] = results[multiKey];
260 }
261 });
262 return result_cfg;
263}
264function make(plan) {
265 return compile(parse(plan, {}));
266}
267function transfer_state_properties(state_decl) {
268 state_decl.declarations.map((d) => {
269 switch (d.key) {
270 case 'shape':
271 state_decl.shape = d.value;
272 break;
273 case 'color':
274 state_decl.color = d.value;
275 break;
276 case 'corners':
277 state_decl.corners = d.value;
278 break;
279 case 'linestyle':
280 state_decl.linestyle = d.value;
281 break;
282 case 'text-color':
283 state_decl.textColor = d.value;
284 break;
285 case 'background-color':
286 state_decl.backgroundColor = d.value;
287 break;
288 case 'border-color':
289 state_decl.borderColor = d.value;
290 break;
291 default: throw new Error(`Unknown state property: '${JSON.stringify(d)}'`);
292 }
293 });
294 return state_decl;
295}
296class Machine {
297 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' }) {
298 this._state = start_states[0];
299 this._states = new Map();
300 this._state_declarations = new Map();
301 this._edges = [];
302 this._edge_map = new Map();
303 this._named_transitions = new Map();
304 this._actions = new Map();
305 this._reverse_actions = new Map();
306 this._reverse_action_targets = new Map();
307 this._machine_author = array_box_if_string(machine_author);
308 this._machine_comment = machine_comment;
309 this._machine_contributor = array_box_if_string(machine_contributor);
310 this._machine_definition = machine_definition;
311 this._machine_language = machine_language;
312 this._machine_license = machine_license;
313 this._machine_name = machine_name;
314 this._machine_version = machine_version;
315 this._raw_state_declaration = state_declaration || [];
316 this._fsl_version = fsl_version;
317 this._arrange_declaration = arrange_declaration;
318 this._arrange_start_declaration = arrange_start_declaration;
319 this._arrange_end_declaration = arrange_end_declaration;
320 this._dot_preamble = dot_preamble;
321 this._theme = theme;
322 this._flow = flow;
323 this._graph_layout = graph_layout;
324 if (state_declaration) {
325 state_declaration.map((state_decl) => {
326 if (this._state_declarations.has(state_decl.state)) {
327 throw new Error(`Added the same state declaration twice: ${JSON.stringify(state_decl.state)}`);
328 }
329 this._state_declarations.set(state_decl.state, transfer_state_properties(state_decl));
330 });
331 }
332 transitions.map((tr) => {
333 if (tr.from === undefined) {
334 throw new Error(`transition must define 'from': ${JSON.stringify(tr)}`);
335 }
336 if (tr.to === undefined) {
337 throw new Error(`transition must define 'to': ${JSON.stringify(tr)}`);
338 }
339 const cursor_from = this._states.get(tr.from)
340 || { name: tr.from, from: [], to: [], complete: complete.includes(tr.from) };
341 if (!(this._states.has(tr.from))) {
342 this._new_state(cursor_from);
343 }
344 const cursor_to = this._states.get(tr.to)
345 || { name: tr.to, from: [], to: [], complete: complete.includes(tr.to) };
346 if (!(this._states.has(tr.to))) {
347 this._new_state(cursor_to);
348 }
349 if (cursor_from.to.includes(tr.to)) {
350 throw new Error(`already has ${JSON.stringify(tr.from)} to ${JSON.stringify(tr.to)}`);
351 }
352 else {
353 cursor_from.to.push(tr.to);
354 cursor_to.from.push(tr.from);
355 }
356 this._edges.push(tr);
357 const thisEdgeId = this._edges.length - 1;
358 if (tr.name) {
359 if (this._named_transitions.has(tr.name)) {
360 throw new Error(`named transition "${JSON.stringify(tr.name)}" already created`);
361 }
362 else {
363 this._named_transitions.set(tr.name, thisEdgeId);
364 }
365 }
366 const from_mapping = this._edge_map.get(tr.from) || new Map();
367 if (!(this._edge_map.has(tr.from))) {
368 this._edge_map.set(tr.from, from_mapping);
369 }
370 from_mapping.set(tr.to, thisEdgeId);
371 if (tr.action) {
372 let actionMap = this._actions.get(tr.action);
373 if (!(actionMap)) {
374 actionMap = new Map();
375 this._actions.set(tr.action, actionMap);
376 }
377 if (actionMap.has(tr.from)) {
378 throw new Error(`action ${JSON.stringify(tr.action)} already attached to origin ${JSON.stringify(tr.from)}`);
379 }
380 else {
381 actionMap.set(tr.from, thisEdgeId);
382 }
383 let rActionMap = this._reverse_actions.get(tr.from);
384 if (!(rActionMap)) {
385 rActionMap = new Map();
386 this._reverse_actions.set(tr.from, rActionMap);
387 }
388 rActionMap.set(tr.action, thisEdgeId);
389 if (!(this._reverse_action_targets.has(tr.to))) {
390 this._reverse_action_targets.set(tr.to, new Map());
391 }
392 }
393 });
394 }
395 _new_state(state_config) {
396 if (this._states.has(state_config.name)) {
397 throw new Error(`state ${JSON.stringify(state_config.name)} already exists`);
398 }
399 this._states.set(state_config.name, state_config);
400 return state_config.name;
401 }
402 state() {
403 return this._state;
404 }
405 state_is_final(whichState) {
406 return ((this.state_is_terminal(whichState)) && (this.state_is_complete(whichState)));
407 }
408 is_final() {
409 return this.state_is_final(this.state());
410 }
411 graph_layout() {
412 return this._graph_layout;
413 }
414 dot_preamble() {
415 return this._dot_preamble;
416 }
417 machine_author() {
418 return this._machine_author;
419 }
420 machine_comment() {
421 return this._machine_comment;
422 }
423 machine_contributor() {
424 return this._machine_contributor;
425 }
426 machine_definition() {
427 return this._machine_definition;
428 }
429 machine_language() {
430 return this._machine_language;
431 }
432 machine_license() {
433 return this._machine_license;
434 }
435 machine_name() {
436 return this._machine_name;
437 }
438 machine_version() {
439 return this._machine_version;
440 }
441 raw_state_declarations() {
442 return this._raw_state_declaration;
443 }
444 state_declaration(which) {
445 return this._state_declarations.get(which);
446 }
447 state_declarations() {
448 return this._state_declarations;
449 }
450 fsl_version() {
451 return this._fsl_version;
452 }
453 machine_state() {
454 return {
455 internal_state_impl_version: 1,
456 actions: this._actions,
457 edge_map: this._edge_map,
458 edges: this._edges,
459 named_transitions: this._named_transitions,
460 reverse_actions: this._reverse_actions,
461 state: this._state,
462 states: this._states
463 };
464 }
465 states() {
466 return Array.from(this._states.keys());
467 }
468 state_for(whichState) {
469 const state = this._states.get(whichState);
470 if (state) {
471 return state;
472 }
473 else {
474 throw new Error(`no such state ${JSON.stringify(state)}`);
475 }
476 }
477 has_state(whichState) {
478 return this._states.get(whichState) !== undefined;
479 }
480 list_edges() {
481 return this._edges;
482 }
483 list_named_transitions() {
484 return this._named_transitions;
485 }
486 list_actions() {
487 return Array.from(this._actions.keys());
488 }
489 theme() {
490 return this._theme;
491 }
492 flow() {
493 return this._flow;
494 }
495 get_transition_by_state_names(from, to) {
496 const emg = this._edge_map.get(from);
497 if (emg) {
498 return emg.get(to);
499 }
500 else {
501 return undefined;
502 }
503 }
504 lookup_transition_for(from, to) {
505 const id = this.get_transition_by_state_names(from, to);
506 return ((id === undefined) || (id === null)) ? undefined : this._edges[id];
507 }
508 list_transitions(whichState = this.state()) {
509 return { entrances: this.list_entrances(whichState), exits: this.list_exits(whichState) };
510 }
511 list_entrances(whichState = this.state()) {
512 return (this._states.get(whichState)
513 || { from: undefined }).from
514 || [];
515 }
516 list_exits(whichState = this.state()) {
517 return (this._states.get(whichState)
518 || { to: undefined }).to
519 || [];
520 }
521 probable_exits_for(whichState) {
522 const wstate = this._states.get(whichState);
523 if (!(wstate)) {
524 throw new Error(`No such state ${JSON.stringify(whichState)} in probable_exits_for`);
525 }
526 const wstate_to = wstate.to, wtf = wstate_to
527 .map((ws) => this.lookup_transition_for(this.state(), ws))
528 .filter(Boolean);
529 return wtf;
530 }
531 probabilistic_transition() {
532 const selected = weighted_rand_select(this.probable_exits_for(this.state()));
533 return this.transition(selected.to);
534 }
535 probabilistic_walk(n) {
536 return seq(n)
537 .map(() => {
538 const state_was = this.state();
539 this.probabilistic_transition();
540 return state_was;
541 })
542 .concat([this.state()]);
543 }
544 probabilistic_histo_walk(n) {
545 return histograph(this.probabilistic_walk(n));
546 }
547 actions(whichState = this.state()) {
548 const wstate = this._reverse_actions.get(whichState);
549 if (wstate) {
550 return Array.from(wstate.keys());
551 }
552 else {
553 throw new Error(`No such state ${JSON.stringify(whichState)}`);
554 }
555 }
556 list_states_having_action(whichState) {
557 const wstate = this._actions.get(whichState);
558 if (wstate) {
559 return Array.from(wstate.keys());
560 }
561 else {
562 throw new Error(`No such state ${JSON.stringify(whichState)}`);
563 }
564 }
565 list_exit_actions(whichState = this.state()) {
566 const ra_base = this._reverse_actions.get(whichState);
567 if (!(ra_base)) {
568 throw new Error(`No such state ${JSON.stringify(whichState)}`);
569 }
570 return Array.from(ra_base.values())
571 .map((edgeId) => this._edges[edgeId])
572 .filter((o) => o.from === whichState)
573 .map((filtered) => filtered.action);
574 }
575 probable_action_exits(whichState = this.state()) {
576 const ra_base = this._reverse_actions.get(whichState);
577 if (!(ra_base)) {
578 throw new Error(`No such state ${JSON.stringify(whichState)}`);
579 }
580 return Array.from(ra_base.values())
581 .map((edgeId) => this._edges[edgeId])
582 .filter((o) => o.from === whichState)
583 .map((filtered) => ({ action: filtered.action,
584 probability: filtered.probability
585 }));
586 }
587 is_unenterable(whichState) {
588 if (!(this.has_state(whichState))) {
589 throw new Error(`No such state ${whichState}`);
590 }
591 return this.list_entrances(whichState).length === 0;
592 }
593 has_unenterables() {
594 return this.states().some((x) => this.is_unenterable(x));
595 }
596 is_terminal() {
597 return this.state_is_terminal(this.state());
598 }
599 state_is_terminal(whichState) {
600 if (!(this.has_state(whichState))) {
601 throw new Error(`No such state ${whichState}`);
602 }
603 return this.list_exits(whichState).length === 0;
604 }
605 has_terminals() {
606 return this.states().some((x) => this.state_is_terminal(x));
607 }
608 is_complete() {
609 return this.state_is_complete(this.state());
610 }
611 state_is_complete(whichState) {
612 const wstate = this._states.get(whichState);
613 if (wstate) {
614 return wstate.complete;
615 }
616 else {
617 throw new Error(`No such state ${JSON.stringify(whichState)}`);
618 }
619 }
620 has_completes() {
621 return this.states().some((x) => this.state_is_complete(x));
622 }
623 action(name, newData) {
624 if (this.valid_action(name, newData)) {
625 const edge = this.current_action_edge_for(name);
626 this._state = edge.to;
627 return true;
628 }
629 else {
630 return false;
631 }
632 }
633 transition(newState, newData) {
634 if (this.valid_transition(newState, newData)) {
635 this._state = newState;
636 return true;
637 }
638 else {
639 return false;
640 }
641 }
642 force_transition(newState, newData) {
643 if (this.valid_force_transition(newState, newData)) {
644 this._state = newState;
645 return true;
646 }
647 else {
648 return false;
649 }
650 }
651 current_action_for(action) {
652 const action_base = this._actions.get(action);
653 return action_base ? action_base.get(this.state()) : undefined;
654 }
655 current_action_edge_for(action) {
656 const idx = this.current_action_for(action);
657 if ((idx === undefined) || (idx === null)) {
658 throw new Error(`No such action ${JSON.stringify(action)}`);
659 }
660 return this._edges[idx];
661 }
662 valid_action(action, _newData) {
663 return this.current_action_for(action) !== undefined;
664 }
665 valid_transition(newState, _newData) {
666 const transition_for = this.lookup_transition_for(this.state(), newState);
667 if (!(transition_for)) {
668 return false;
669 }
670 if (transition_for.forced_only) {
671 return false;
672 }
673 return true;
674 }
675 valid_force_transition(newState, _newData) {
676 return (this.lookup_transition_for(this.state(), newState) !== undefined);
677 }
678 sm(template_strings, ...remainder) {
679 return sm(template_strings, ...remainder);
680 }
681}
682function sm(template_strings, ...remainder) {
683 return new Machine(make(template_strings.reduce((acc, val, idx) => `${acc}${remainder[idx - 1]}${val}`)));
684}
685export { version, transfer_state_properties, Machine, make, parse, compile, sm, arrow_direction, arrow_left_kind, arrow_right_kind, seq, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key };