jssm 5.104.2 → 5.112.4
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.
- package/MIGRATING-jssm-viz.md +67 -0
- package/README.md +179 -882
- package/dist/deno/README.md +347 -0
- package/dist/{es6 → deno}/jssm.d.ts +773 -39
- package/dist/deno/jssm.js +1 -0
- package/{jssm_compiler.d.ts → dist/deno/jssm_compiler.d.ts} +17 -2
- package/dist/deno/jssm_constants.d.ts +37 -0
- package/dist/deno/jssm_error.d.ts +27 -0
- package/dist/deno/jssm_theme.d.ts +15 -0
- package/dist/{es6 → deno}/jssm_types.d.ts +327 -8
- package/dist/deno/jssm_util.d.ts +258 -0
- package/dist/deno/jssm_viz.d.ts +175 -0
- package/dist/deno/jssm_viz_colors.d.ts +63 -0
- package/dist/jssm.es5.cjs +1 -1
- package/dist/jssm.es5.iife.js +1 -0
- package/dist/jssm.es6.mjs +1 -1
- package/dist/jssm_viz.cjs +1 -0
- package/dist/jssm_viz.iife.cjs +1 -0
- package/dist/jssm_viz.mjs +1 -0
- package/jssm.es5.d.cts +1191 -43
- package/jssm.es6.d.ts +1191 -43
- package/jssm_viz.es5.d.cts +2341 -0
- package/jssm_viz.es6.d.ts +2341 -0
- package/package.json +73 -24
- package/.clocignore +0 -1
- package/.codeclimate.yml +0 -22
- package/.editorconfig +0 -12
- package/.eslintrc +0 -20
- package/.gitattributes +0 -6
- package/.nycrc +0 -6
- package/.travis.yml +0 -9
- package/CHANGELOG.md +0 -178
- package/dist/es6/fsl_parser.js +0 -1
- package/dist/es6/jssm.js +0 -2488
- package/dist/es6/jssm_arrow.js +0 -187
- package/dist/es6/jssm_compiler.d.ts +0 -135
- package/dist/es6/jssm_compiler.js +0 -366
- package/dist/es6/jssm_constants.d.ts +0 -5
- package/dist/es6/jssm_constants.js +0 -94
- package/dist/es6/jssm_error.d.ts +0 -8
- package/dist/es6/jssm_error.js +0 -28
- package/dist/es6/jssm_theme.d.ts +0 -4
- package/dist/es6/jssm_theme.js +0 -13
- package/dist/es6/jssm_types.js +0 -3
- package/dist/es6/jssm_util.d.ts +0 -106
- package/dist/es6/jssm_util.js +0 -180
- package/dist/es6/themes/jssm_base_stylesheet.d.ts +0 -11
- package/dist/es6/themes/jssm_base_stylesheet.js +0 -58
- package/dist/es6/themes/jssm_theme_bold.d.ts +0 -11
- package/dist/es6/themes/jssm_theme_bold.js +0 -58
- package/dist/es6/themes/jssm_theme_default.d.ts +0 -11
- package/dist/es6/themes/jssm_theme_default.js +0 -58
- package/dist/es6/themes/jssm_theme_modern.d.ts +0 -11
- package/dist/es6/themes/jssm_theme_modern.js +0 -58
- package/dist/es6/themes/jssm_theme_ocean.d.ts +0 -11
- package/dist/es6/themes/jssm_theme_ocean.js +0 -56
- package/dist/es6/themes/jssm_theme_plain.d.ts +0 -11
- package/dist/es6/themes/jssm_theme_plain.js +0 -70
- package/dist/es6/version.js +0 -2
- package/dist/jssm.es5.iife.cjs +0 -1
- package/dist/jssm.es5.iife.nonmin.cjs +0 -23180
- package/dist/jssm.es5.nonmin.cjs +0 -23175
- package/dist/jssm.es6.nonmin.cjs +0 -23144
- package/fsl_parser.d.ts +0 -6
- package/jest-dragon.config.cjs +0 -33
- package/jest-spec.config.cjs +0 -33
- package/jest-stoch.config.cjs +0 -33
- package/jest-unicode.config.cjs +0 -33
- package/jssm.d.ts +0 -1141
- package/jssm_arrow.d.ts +0 -53
- package/jssm_constants.d.ts +0 -5
- package/jssm_error.d.ts +0 -8
- package/jssm_theme.d.ts +0 -4
- package/jssm_types.d.ts +0 -378
- package/jssm_util.d.ts +0 -106
- package/rollup.config.deno.js +0 -44
- package/rollup.config.es5.js +0 -52
- package/rollup.config.es6.js +0 -55
- package/tutorial_learn_testing.md +0 -168
- package/typedoc-options.cjs +0 -68
- package/version.d.ts +0 -2
- /package/dist/{es6 → deno}/fsl_parser.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_arrow.d.ts +0 -0
- /package/dist/{es6 → deno}/version.d.ts +0 -0
package/dist/es6/jssm.js
DELETED
|
@@ -1,2488 +0,0 @@
|
|
|
1
|
-
// whargarbl lots of these return arrays could/should be sets
|
|
2
|
-
import { circular_buffer } from 'circular_buffer_js';
|
|
3
|
-
import { FslDirections } from './jssm_types';
|
|
4
|
-
import { arrow_direction, arrow_left_kind, arrow_right_kind } from './jssm_arrow';
|
|
5
|
-
import { compile, make, wrap_parse } from './jssm_compiler';
|
|
6
|
-
import { theme_mapping, base_theme } from './jssm_theme';
|
|
7
|
-
import { seq, unique, find_repeated, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key, array_box_if_string, name_bind_prop_and_state, hook_name, named_hook_name, gen_splitmix32, sleep } from './jssm_util';
|
|
8
|
-
import * as constants from './jssm_constants';
|
|
9
|
-
const { shapes, gviz_shapes, named_colors } = constants;
|
|
10
|
-
import { version, build_time } from './version'; // replaced from package.js in build
|
|
11
|
-
import { JssmError } from './jssm_error';
|
|
12
|
-
/*********
|
|
13
|
-
*
|
|
14
|
-
* An internal method meant to take a series of declarations and fold them into
|
|
15
|
-
* a single multi-faceted declaration, in the process of building a state. Not
|
|
16
|
-
* generally meant for external use.
|
|
17
|
-
*
|
|
18
|
-
* @internal
|
|
19
|
-
*
|
|
20
|
-
*/
|
|
21
|
-
function transfer_state_properties(state_decl) {
|
|
22
|
-
state_decl.declarations.map((d) => {
|
|
23
|
-
switch (d.key) {
|
|
24
|
-
case 'shape':
|
|
25
|
-
state_decl.shape = d.value;
|
|
26
|
-
break;
|
|
27
|
-
case 'color':
|
|
28
|
-
state_decl.color = d.value;
|
|
29
|
-
break;
|
|
30
|
-
case 'corners':
|
|
31
|
-
state_decl.corners = d.value;
|
|
32
|
-
break;
|
|
33
|
-
case 'line-style':
|
|
34
|
-
state_decl.lineStyle = d.value;
|
|
35
|
-
break;
|
|
36
|
-
case 'text-color':
|
|
37
|
-
state_decl.textColor = d.value;
|
|
38
|
-
break;
|
|
39
|
-
case 'background-color':
|
|
40
|
-
state_decl.backgroundColor = d.value;
|
|
41
|
-
break;
|
|
42
|
-
case 'state-label':
|
|
43
|
-
state_decl.stateLabel = d.value;
|
|
44
|
-
break;
|
|
45
|
-
case 'border-color':
|
|
46
|
-
state_decl.borderColor = d.value;
|
|
47
|
-
break;
|
|
48
|
-
case 'state_property':
|
|
49
|
-
state_decl.property = { name: d.name, value: d.value };
|
|
50
|
-
break;
|
|
51
|
-
default: throw new JssmError(undefined, `Unknown state property: '${JSON.stringify(d)}'`);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
return state_decl;
|
|
55
|
-
}
|
|
56
|
-
function state_style_condense(jssk) {
|
|
57
|
-
const state_style = {};
|
|
58
|
-
if (Array.isArray(jssk)) {
|
|
59
|
-
jssk.forEach((key, i) => {
|
|
60
|
-
if (typeof key !== 'object') {
|
|
61
|
-
throw new JssmError(this, `invalid state item ${i} in state_style_condense list: ${JSON.stringify(key)}`);
|
|
62
|
-
}
|
|
63
|
-
switch (key.key) {
|
|
64
|
-
case 'shape':
|
|
65
|
-
if (state_style.shape !== undefined) {
|
|
66
|
-
throw new JssmError(this, `cannot redefine 'shape' in state_style_condense, already defined`);
|
|
67
|
-
}
|
|
68
|
-
state_style.shape = key.value;
|
|
69
|
-
break;
|
|
70
|
-
case 'color':
|
|
71
|
-
if (state_style.color !== undefined) {
|
|
72
|
-
throw new JssmError(this, `cannot redefine 'color' in state_style_condense, already defined`);
|
|
73
|
-
}
|
|
74
|
-
state_style.color = key.value;
|
|
75
|
-
break;
|
|
76
|
-
case 'text-color':
|
|
77
|
-
if (state_style.textColor !== undefined) {
|
|
78
|
-
throw new JssmError(this, `cannot redefine 'text-color' in state_style_condense, already defined`);
|
|
79
|
-
}
|
|
80
|
-
state_style.textColor = key.value;
|
|
81
|
-
break;
|
|
82
|
-
case 'corners':
|
|
83
|
-
if (state_style.corners !== undefined) {
|
|
84
|
-
throw new JssmError(this, `cannot redefine 'corners' in state_style_condense, already defined`);
|
|
85
|
-
}
|
|
86
|
-
state_style.corners = key.value;
|
|
87
|
-
break;
|
|
88
|
-
case 'line-style':
|
|
89
|
-
if (state_style.lineStyle !== undefined) {
|
|
90
|
-
throw new JssmError(this, `cannot redefine 'line-style' in state_style_condense, already defined`);
|
|
91
|
-
}
|
|
92
|
-
state_style.lineStyle = key.value;
|
|
93
|
-
break;
|
|
94
|
-
case 'background-color':
|
|
95
|
-
if (state_style.backgroundColor !== undefined) {
|
|
96
|
-
throw new JssmError(this, `cannot redefine 'background-color' in state_style_condense, already defined`);
|
|
97
|
-
}
|
|
98
|
-
state_style.backgroundColor = key.value;
|
|
99
|
-
break;
|
|
100
|
-
case 'state-label':
|
|
101
|
-
if (state_style.stateLabel !== undefined) {
|
|
102
|
-
throw new JssmError(this, `cannot redefine 'state-label' in state_style_condense, already defined`);
|
|
103
|
-
}
|
|
104
|
-
state_style.stateLabel = key.value;
|
|
105
|
-
break;
|
|
106
|
-
case 'border-color':
|
|
107
|
-
if (state_style.borderColor !== undefined) {
|
|
108
|
-
throw new JssmError(this, `cannot redefine 'border-color' in state_style_condense, already defined`);
|
|
109
|
-
}
|
|
110
|
-
state_style.borderColor = key.value;
|
|
111
|
-
break;
|
|
112
|
-
default:
|
|
113
|
-
// TODO do that <never> trick to assert this list is complete
|
|
114
|
-
throw new JssmError(this, `unknown state style key in condense: ${key.key}`);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
else if (jssk === undefined) {
|
|
119
|
-
// do nothing, undefined is legal and means we should return the empty container above
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
throw new JssmError(this, 'state_style_condense received a non-array');
|
|
123
|
-
}
|
|
124
|
-
return state_style;
|
|
125
|
-
}
|
|
126
|
-
// TODO add a lotta docblock here
|
|
127
|
-
class Machine {
|
|
128
|
-
// whargarbl this badly needs to be broken up, monolith master
|
|
129
|
-
constructor({ start_states, end_states = [], initial_state, start_states_no_enforce, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, property_definition, state_property, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], theme = ['default'], flow = 'down', graph_layout = 'dot', instance_name, history, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, allows_override, config_allows_override, rng_seed, time_source, timeout_source, clear_timeout_source }) {
|
|
130
|
-
this._time_source = () => new Date().getTime();
|
|
131
|
-
this._create_started = this._time_source();
|
|
132
|
-
this._instance_name = instance_name;
|
|
133
|
-
this._states = new Map();
|
|
134
|
-
this._state_declarations = new Map();
|
|
135
|
-
this._edges = [];
|
|
136
|
-
this._edge_map = new Map();
|
|
137
|
-
this._named_transitions = new Map();
|
|
138
|
-
this._actions = new Map();
|
|
139
|
-
this._reverse_actions = new Map();
|
|
140
|
-
this._reverse_action_targets = new Map(); // todo
|
|
141
|
-
this._start_states = new Set(start_states);
|
|
142
|
-
this._end_states = new Set(end_states); // todo consider what to do about incorporating complete too
|
|
143
|
-
this._machine_author = array_box_if_string(machine_author);
|
|
144
|
-
this._machine_comment = machine_comment;
|
|
145
|
-
this._machine_contributor = array_box_if_string(machine_contributor);
|
|
146
|
-
this._machine_definition = machine_definition;
|
|
147
|
-
this._machine_language = machine_language;
|
|
148
|
-
this._machine_license = machine_license;
|
|
149
|
-
this._machine_name = machine_name;
|
|
150
|
-
this._machine_version = machine_version;
|
|
151
|
-
this._raw_state_declaration = state_declaration || [];
|
|
152
|
-
this._fsl_version = fsl_version;
|
|
153
|
-
this._arrange_declaration = arrange_declaration;
|
|
154
|
-
this._arrange_start_declaration = arrange_start_declaration;
|
|
155
|
-
this._arrange_end_declaration = arrange_end_declaration;
|
|
156
|
-
this._dot_preamble = dot_preamble;
|
|
157
|
-
this._themes = theme;
|
|
158
|
-
this._flow = flow;
|
|
159
|
-
this._graph_layout = graph_layout;
|
|
160
|
-
this._has_hooks = false;
|
|
161
|
-
this._has_basic_hooks = false;
|
|
162
|
-
this._has_named_hooks = false;
|
|
163
|
-
this._has_entry_hooks = false;
|
|
164
|
-
this._has_exit_hooks = false;
|
|
165
|
-
this._has_after_hooks = false;
|
|
166
|
-
this._has_global_action_hooks = false;
|
|
167
|
-
this._has_transition_hooks = true;
|
|
168
|
-
// no need for a boolean for single hooks, just test for undefinedness
|
|
169
|
-
this._has_forced_transitions = false;
|
|
170
|
-
this._hooks = new Map();
|
|
171
|
-
this._named_hooks = new Map();
|
|
172
|
-
this._entry_hooks = new Map();
|
|
173
|
-
this._exit_hooks = new Map();
|
|
174
|
-
this._after_hooks = new Map();
|
|
175
|
-
this._global_action_hooks = new Map();
|
|
176
|
-
this._any_action_hook = undefined;
|
|
177
|
-
this._standard_transition_hook = undefined;
|
|
178
|
-
this._main_transition_hook = undefined;
|
|
179
|
-
this._forced_transition_hook = undefined;
|
|
180
|
-
this._any_transition_hook = undefined;
|
|
181
|
-
this._has_post_hooks = false;
|
|
182
|
-
this._has_post_basic_hooks = false;
|
|
183
|
-
this._has_post_named_hooks = false;
|
|
184
|
-
this._has_post_entry_hooks = false;
|
|
185
|
-
this._has_post_exit_hooks = false;
|
|
186
|
-
this._has_post_global_action_hooks = false;
|
|
187
|
-
this._has_post_transition_hooks = true;
|
|
188
|
-
// no need for a boolean for single hooks, just test for undefinedness
|
|
189
|
-
this._code_allows_override = allows_override;
|
|
190
|
-
this._config_allows_override = config_allows_override;
|
|
191
|
-
if ((allows_override === false) && (config_allows_override === true)) {
|
|
192
|
-
throw new JssmError(undefined, "Code specifies no override, but config tries to permit; config may not be less strict than code");
|
|
193
|
-
}
|
|
194
|
-
this._post_hooks = new Map();
|
|
195
|
-
this._post_named_hooks = new Map();
|
|
196
|
-
this._post_entry_hooks = new Map();
|
|
197
|
-
this._post_exit_hooks = new Map();
|
|
198
|
-
this._post_global_action_hooks = new Map();
|
|
199
|
-
this._post_any_action_hook = undefined;
|
|
200
|
-
this._post_standard_transition_hook = undefined;
|
|
201
|
-
this._post_main_transition_hook = undefined;
|
|
202
|
-
this._post_forced_transition_hook = undefined;
|
|
203
|
-
this._post_any_transition_hook = undefined;
|
|
204
|
-
this._data = data;
|
|
205
|
-
this._property_keys = new Set();
|
|
206
|
-
this._default_properties = new Map();
|
|
207
|
-
this._state_properties = new Map();
|
|
208
|
-
this._required_properties = new Set();
|
|
209
|
-
this._state_style = state_style_condense(default_state_config);
|
|
210
|
-
this._active_state_style = state_style_condense(default_active_state_config);
|
|
211
|
-
this._hooked_state_style = state_style_condense(default_hooked_state_config);
|
|
212
|
-
this._terminal_state_style = state_style_condense(default_terminal_state_config);
|
|
213
|
-
this._start_state_style = state_style_condense(default_start_state_config);
|
|
214
|
-
this._end_state_style = state_style_condense(default_end_state_config);
|
|
215
|
-
this._history_length = history || 0;
|
|
216
|
-
this._history = new circular_buffer(this._history_length);
|
|
217
|
-
this._state_labels = new Map();
|
|
218
|
-
this._rng_seed = rng_seed !== null && rng_seed !== void 0 ? rng_seed : new Date().getTime();
|
|
219
|
-
this._rng = gen_splitmix32(this._rng_seed);
|
|
220
|
-
this._timeout_source = timeout_source !== null && timeout_source !== void 0 ? timeout_source : ((f, a) => setTimeout(f, a));
|
|
221
|
-
this._clear_timeout_source = clear_timeout_source !== null && clear_timeout_source !== void 0 ? clear_timeout_source : ((h) => clearTimeout(h));
|
|
222
|
-
this._timeout_handle = undefined;
|
|
223
|
-
this._timeout_target = undefined;
|
|
224
|
-
this._timeout_target_time = undefined;
|
|
225
|
-
this._after_mapping = new Map();
|
|
226
|
-
// consolidate the state declarations
|
|
227
|
-
if (state_declaration) {
|
|
228
|
-
state_declaration.map((state_decl) => {
|
|
229
|
-
if (this._state_declarations.has(state_decl.state)) { // no repeats
|
|
230
|
-
throw new JssmError(this, `Added the same state declaration twice: ${JSON.stringify(state_decl.state)}`);
|
|
231
|
-
}
|
|
232
|
-
this._state_declarations.set(state_decl.state, transfer_state_properties(state_decl));
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
// walk the decls for labels; aggregate them when found
|
|
236
|
-
[...this._state_declarations].map(sd => {
|
|
237
|
-
const [key, decl] = sd, labelled = decl.declarations.filter(d => d.key === 'state-label');
|
|
238
|
-
if (labelled.length > 1) {
|
|
239
|
-
throw new JssmError(this, `state ${key} may only have one state-label; has ${labelled.length}`);
|
|
240
|
-
}
|
|
241
|
-
if (labelled.length === 1) {
|
|
242
|
-
this._state_labels.set(key, labelled[0].value);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
// walk the transitions
|
|
246
|
-
transitions.map((tr) => {
|
|
247
|
-
if (tr.from === undefined) {
|
|
248
|
-
throw new JssmError(this, `transition must define 'from': ${JSON.stringify(tr)}`);
|
|
249
|
-
}
|
|
250
|
-
if (tr.to === undefined) {
|
|
251
|
-
throw new JssmError(this, `transition must define 'to': ${JSON.stringify(tr)}`);
|
|
252
|
-
}
|
|
253
|
-
// get the cursors. what a mess
|
|
254
|
-
const cursor_from = this._states.get(tr.from)
|
|
255
|
-
|| { name: tr.from, from: [], to: [], complete: complete.includes(tr.from) };
|
|
256
|
-
if (!(this._states.has(tr.from))) {
|
|
257
|
-
this._new_state(cursor_from);
|
|
258
|
-
}
|
|
259
|
-
const cursor_to = this._states.get(tr.to)
|
|
260
|
-
|| { name: tr.to, from: [], to: [], complete: complete.includes(tr.to) };
|
|
261
|
-
if (!(this._states.has(tr.to))) {
|
|
262
|
-
this._new_state(cursor_to);
|
|
263
|
-
}
|
|
264
|
-
// guard against existing connections being re-added
|
|
265
|
-
if (cursor_from.to.includes(tr.to)) {
|
|
266
|
-
throw new JssmError(this, `already has ${JSON.stringify(tr.from)} to ${JSON.stringify(tr.to)}`);
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
cursor_from.to.push(tr.to);
|
|
270
|
-
cursor_to.from.push(tr.from);
|
|
271
|
-
}
|
|
272
|
-
// add the edge; note its id
|
|
273
|
-
this._edges.push(tr);
|
|
274
|
-
const thisEdgeId = this._edges.length - 1;
|
|
275
|
-
if (tr.forced_only) {
|
|
276
|
-
this._has_forced_transitions = true;
|
|
277
|
-
}
|
|
278
|
-
// guard against repeating a transition name
|
|
279
|
-
if (tr.name) {
|
|
280
|
-
if (this._named_transitions.has(tr.name)) {
|
|
281
|
-
throw new JssmError(this, `named transition "${JSON.stringify(tr.name)}" already created`);
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
this._named_transitions.set(tr.name, thisEdgeId);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
// set up the after mapping, if any
|
|
288
|
-
if (tr.after_time) {
|
|
289
|
-
this._after_mapping.set(tr.from, [tr.to, tr.after_time]);
|
|
290
|
-
}
|
|
291
|
-
// set up the mapping, so that edges can be looked up by endpoint pairs
|
|
292
|
-
const from_mapping = this._edge_map.get(tr.from) || new Map();
|
|
293
|
-
if (!(this._edge_map.has(tr.from))) {
|
|
294
|
-
this._edge_map.set(tr.from, from_mapping);
|
|
295
|
-
}
|
|
296
|
-
// const to_mapping = from_mapping.get(tr.to);
|
|
297
|
-
from_mapping.set(tr.to, thisEdgeId); // already checked that this mapping doesn't exist, above
|
|
298
|
-
// set up the action mapping, so that actions can be looked up by origin
|
|
299
|
-
if (tr.action) {
|
|
300
|
-
// forward mapping first by action name
|
|
301
|
-
let actionMap = this._actions.get(tr.action);
|
|
302
|
-
if (!(actionMap)) {
|
|
303
|
-
actionMap = new Map();
|
|
304
|
-
this._actions.set(tr.action, actionMap);
|
|
305
|
-
}
|
|
306
|
-
if (actionMap.has(tr.from)) {
|
|
307
|
-
throw new JssmError(this, `action ${JSON.stringify(tr.action)} already attached to origin ${JSON.stringify(tr.from)}`);
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
actionMap.set(tr.from, thisEdgeId);
|
|
311
|
-
}
|
|
312
|
-
// reverse mapping first by state origin name
|
|
313
|
-
let rActionMap = this._reverse_actions.get(tr.from);
|
|
314
|
-
if (!(rActionMap)) {
|
|
315
|
-
rActionMap = new Map();
|
|
316
|
-
this._reverse_actions.set(tr.from, rActionMap);
|
|
317
|
-
}
|
|
318
|
-
// no need to test for reverse mapping pre-presence;
|
|
319
|
-
// forward mapping already covers collisions
|
|
320
|
-
rActionMap.set(tr.action, thisEdgeId);
|
|
321
|
-
// reverse mapping first by state target name
|
|
322
|
-
if (!(this._reverse_action_targets.has(tr.to))) {
|
|
323
|
-
this._reverse_action_targets.set(tr.to, new Map());
|
|
324
|
-
}
|
|
325
|
-
/* todo comeback
|
|
326
|
-
fundamental problem is roActionMap needs to be a multimap
|
|
327
|
-
const roActionMap = this._reverse_action_targets.get(tr.to); // wasteful - already did has - refactor
|
|
328
|
-
if (roActionMap) {
|
|
329
|
-
if (roActionMap.has(tr.action)) {
|
|
330
|
-
throw new JssmError(this, `ro-action ${tr.to} already attached to action ${tr.action}`);
|
|
331
|
-
} else {
|
|
332
|
-
roActionMap.set(tr.action, thisEdgeId);
|
|
333
|
-
}
|
|
334
|
-
} else {
|
|
335
|
-
throw new JssmError(this, `should be impossible - flow doesn\'t know .set precedes .get yet again. severe error?');
|
|
336
|
-
}
|
|
337
|
-
*/
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
if (Array.isArray(property_definition)) {
|
|
341
|
-
property_definition.forEach(pr => {
|
|
342
|
-
this._property_keys.add(pr.name);
|
|
343
|
-
if (pr.hasOwnProperty('default_value')) {
|
|
344
|
-
this._default_properties.set(pr.name, pr.default_value);
|
|
345
|
-
}
|
|
346
|
-
if (pr.hasOwnProperty('required') && (pr.required === true)) {
|
|
347
|
-
this._required_properties.add(pr.name);
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
if (Array.isArray(state_property)) {
|
|
352
|
-
state_property.forEach(sp => {
|
|
353
|
-
this._state_properties.set(sp.name, sp.default_value);
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
// set initial state either from the specified or the start state list. validate admission behavior.
|
|
357
|
-
if (initial_state) {
|
|
358
|
-
if (!(this._states.has(initial_state))) {
|
|
359
|
-
throw new JssmError(this, `requested start state ${initial_state} does not exist`);
|
|
360
|
-
}
|
|
361
|
-
if ((!(start_states_no_enforce)) && (!(start_states.includes(initial_state)))) {
|
|
362
|
-
throw new JssmError(this, `requested start state ${initial_state} is not in start state list; add {start_states_no_enforce:true} to constructor options if desired`);
|
|
363
|
-
}
|
|
364
|
-
this._state = initial_state;
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
this._state = start_states[0];
|
|
368
|
-
}
|
|
369
|
-
// done building, do checks
|
|
370
|
-
// assert all props are valid
|
|
371
|
-
this._state_properties.forEach((_value, key) => {
|
|
372
|
-
const inside = JSON.parse(key);
|
|
373
|
-
if (Array.isArray(inside)) {
|
|
374
|
-
const j_property = inside[0];
|
|
375
|
-
if (typeof j_property === 'string') {
|
|
376
|
-
const j_state = inside[1];
|
|
377
|
-
if (typeof j_state === 'string') {
|
|
378
|
-
if (!(this.known_prop(j_property))) {
|
|
379
|
-
throw new JssmError(this, `State "${j_state}" has property "${j_property}" which is not globally declared`);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
// assert all required properties are serviced
|
|
386
|
-
this._required_properties.forEach(dp_key => {
|
|
387
|
-
if (this._default_properties.has(dp_key)) {
|
|
388
|
-
throw new JssmError(this, `The property "${dp_key}" is required, but also has a default; these conflict`);
|
|
389
|
-
}
|
|
390
|
-
this.states().forEach(s => {
|
|
391
|
-
const bound_name = name_bind_prop_and_state(dp_key, s);
|
|
392
|
-
if (!(this._state_properties.has(bound_name))) {
|
|
393
|
-
throw new JssmError(this, `State "${s}" is missing required property "${dp_key}"`);
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
// assert chosen starting state is valid
|
|
398
|
-
if (!(this.has_state(this.state()))) {
|
|
399
|
-
throw new JssmError(this, `Current start state "${this.state()}" does not exist`);
|
|
400
|
-
}
|
|
401
|
-
// assert all starting states are valid
|
|
402
|
-
start_states.forEach((ss, ssi) => {
|
|
403
|
-
if (!(this.has_state(ss))) {
|
|
404
|
-
throw new JssmError(this, `Start state ${ssi} "${ss}" does not exist`);
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
// assert chosen starting state is valid
|
|
408
|
-
if (!(start_states.length === this._start_states.size)) {
|
|
409
|
-
throw new JssmError(this, `Start states cannot be repeated`);
|
|
410
|
-
}
|
|
411
|
-
this._created = this._time_source();
|
|
412
|
-
this.auto_set_state_timeout();
|
|
413
|
-
this._arrange_declaration.forEach((arrange_pair) => arrange_pair.forEach((possibleState) => {
|
|
414
|
-
if (!(this._states.has(possibleState))) {
|
|
415
|
-
throw new JssmError(this, `Cannot arrange state that does not exist "${possibleState}"`);
|
|
416
|
-
}
|
|
417
|
-
}));
|
|
418
|
-
}
|
|
419
|
-
/********
|
|
420
|
-
*
|
|
421
|
-
* Internal method for fabricating states. Not meant for external use.
|
|
422
|
-
*
|
|
423
|
-
* @internal
|
|
424
|
-
*
|
|
425
|
-
*/
|
|
426
|
-
_new_state(state_config) {
|
|
427
|
-
if (this._states.has(state_config.name)) {
|
|
428
|
-
throw new JssmError(this, `state ${JSON.stringify(state_config.name)} already exists`);
|
|
429
|
-
}
|
|
430
|
-
this._states.set(state_config.name, state_config);
|
|
431
|
-
return state_config.name;
|
|
432
|
-
}
|
|
433
|
-
/*********
|
|
434
|
-
*
|
|
435
|
-
* Get the current state of a machine.
|
|
436
|
-
*
|
|
437
|
-
* ```typescript
|
|
438
|
-
* import * as jssm from 'jssm';
|
|
439
|
-
*
|
|
440
|
-
* const lswitch = jssm.from('on <=> off;');
|
|
441
|
-
* console.log( lswitch.state() ); // 'on'
|
|
442
|
-
*
|
|
443
|
-
* lswitch.transition('off');
|
|
444
|
-
* console.log( lswitch.state() ); // 'off'
|
|
445
|
-
* ```
|
|
446
|
-
*
|
|
447
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
448
|
-
*
|
|
449
|
-
*/
|
|
450
|
-
state() {
|
|
451
|
-
return this._state;
|
|
452
|
-
}
|
|
453
|
-
/*********
|
|
454
|
-
*
|
|
455
|
-
* Get the label for a given state, if any; return `undefined` otherwise.
|
|
456
|
-
*
|
|
457
|
-
* ```typescript
|
|
458
|
-
* import * as jssm from 'jssm';
|
|
459
|
-
*
|
|
460
|
-
* const lswitch = jssm.from('a -> b; state a: { label: "Foo!"; };');
|
|
461
|
-
* console.log( lswitch.label_for('a') ); // 'Foo!'
|
|
462
|
-
* console.log( lswitch.label_for('b') ); // undefined
|
|
463
|
-
* ```
|
|
464
|
-
*
|
|
465
|
-
* See also {@link display_text}.
|
|
466
|
-
*
|
|
467
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
468
|
-
*
|
|
469
|
-
*/
|
|
470
|
-
label_for(state) {
|
|
471
|
-
return this._state_labels.get(state);
|
|
472
|
-
}
|
|
473
|
-
/*********
|
|
474
|
-
*
|
|
475
|
-
* Get whatever the node should show as text.
|
|
476
|
-
*
|
|
477
|
-
* Currently, this means to get the label for a given state, if any;
|
|
478
|
-
* otherwise to return the node's name. However, this definition is expected
|
|
479
|
-
* to grow with time, and it is currently considered ill-advised to manually
|
|
480
|
-
* parse this text.
|
|
481
|
-
*
|
|
482
|
-
* See also {@link label_for}.
|
|
483
|
-
*
|
|
484
|
-
* ```typescript
|
|
485
|
-
* import * as jssm from 'jssm';
|
|
486
|
-
*
|
|
487
|
-
* const lswitch = jssm.from('a -> b; state a: { label: "Foo!"; };');
|
|
488
|
-
* console.log( lswitch.display_text('a') ); // 'Foo!'
|
|
489
|
-
* console.log( lswitch.display_text('b') ); // 'b'
|
|
490
|
-
* ```
|
|
491
|
-
*
|
|
492
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
493
|
-
*
|
|
494
|
-
*/
|
|
495
|
-
display_text(state) {
|
|
496
|
-
var _a;
|
|
497
|
-
return (_a = this._state_labels.get(state)) !== null && _a !== void 0 ? _a : state;
|
|
498
|
-
}
|
|
499
|
-
/*********
|
|
500
|
-
*
|
|
501
|
-
* Get the current data of a machine.
|
|
502
|
-
*
|
|
503
|
-
* ```typescript
|
|
504
|
-
* import * as jssm from 'jssm';
|
|
505
|
-
*
|
|
506
|
-
* const lswitch = jssm.from('on <=> off;', {data: 1});
|
|
507
|
-
* console.log( lswitch.data() ); // 1
|
|
508
|
-
* ```
|
|
509
|
-
*
|
|
510
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
511
|
-
*
|
|
512
|
-
*/
|
|
513
|
-
data() {
|
|
514
|
-
return structuredClone(this._data);
|
|
515
|
-
}
|
|
516
|
-
// NEEDS_DOCS
|
|
517
|
-
/*********
|
|
518
|
-
*
|
|
519
|
-
* Get the current value of a given property name.
|
|
520
|
-
*
|
|
521
|
-
* ```typescript
|
|
522
|
-
*
|
|
523
|
-
* ```
|
|
524
|
-
*
|
|
525
|
-
* @param name The relevant property name to look up
|
|
526
|
-
*
|
|
527
|
-
* @returns The value behind the prop name. Because functional props are
|
|
528
|
-
* evaluated as getters, this can be anything.
|
|
529
|
-
*
|
|
530
|
-
*/
|
|
531
|
-
prop(name) {
|
|
532
|
-
const bound_name = name_bind_prop_and_state(name, this.state());
|
|
533
|
-
if (this._state_properties.has(bound_name)) {
|
|
534
|
-
return this._state_properties.get(bound_name);
|
|
535
|
-
}
|
|
536
|
-
else if (this._default_properties.has(name)) {
|
|
537
|
-
return this._default_properties.get(name);
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
return undefined;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// NEEDS_DOCS
|
|
544
|
-
/*********
|
|
545
|
-
*
|
|
546
|
-
* Get the current value of a given property name. If missing on the state
|
|
547
|
-
* and without a global default, throw, unlike {@link prop}, which would
|
|
548
|
-
* return `undefined` instead.
|
|
549
|
-
*
|
|
550
|
-
* ```typescript
|
|
551
|
-
*
|
|
552
|
-
* ```
|
|
553
|
-
*
|
|
554
|
-
* @param name The relevant property name to look up
|
|
555
|
-
*
|
|
556
|
-
* @returns The value behind the prop name. Because functional props are
|
|
557
|
-
* evaluated as getters, this can be anything.
|
|
558
|
-
*
|
|
559
|
-
*/
|
|
560
|
-
strict_prop(name) {
|
|
561
|
-
const bound_name = name_bind_prop_and_state(name, this.state());
|
|
562
|
-
if (this._state_properties.has(bound_name)) {
|
|
563
|
-
return this._state_properties.get(bound_name);
|
|
564
|
-
}
|
|
565
|
-
else if (this._default_properties.has(name)) {
|
|
566
|
-
return this._default_properties.get(name);
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
throw new JssmError(this, `Strictly requested a prop '${name}' which doesn't exist on current state '${this.state()}' and has no default`);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
// NEEDS_DOCS
|
|
573
|
-
// COMEBACK add prop_map, sparse_props and strict_props to doc text when implemented
|
|
574
|
-
/*********
|
|
575
|
-
*
|
|
576
|
-
* Get the current value of every prop, as an object. If no current definition
|
|
577
|
-
* exists for a prop - that is, if the prop was defined without a default and
|
|
578
|
-
* the current state also doesn't define the prop - then that prop will be listed
|
|
579
|
-
* in the returned object with a value of `undefined`.
|
|
580
|
-
*
|
|
581
|
-
* ```typescript
|
|
582
|
-
* const traffic_light = sm`
|
|
583
|
-
*
|
|
584
|
-
* property can_go default true;
|
|
585
|
-
* property hesitate default true;
|
|
586
|
-
* property stop_first default false;
|
|
587
|
-
*
|
|
588
|
-
* Off -> Red => Green => Yellow => Red;
|
|
589
|
-
* [Red Yellow Green] ~> [Off FlashingRed];
|
|
590
|
-
* FlashingRed -> Red;
|
|
591
|
-
*
|
|
592
|
-
* state Red: { property stop_first true; property can_go false; };
|
|
593
|
-
* state Off: { property stop_first true; };
|
|
594
|
-
* state FlashingRed: { property stop_first true; };
|
|
595
|
-
* state Green: { property hesitate false; };
|
|
596
|
-
*
|
|
597
|
-
* `;
|
|
598
|
-
*
|
|
599
|
-
* traffic_light.state(); // Off
|
|
600
|
-
* traffic_light.props(); // { can_go: true, hesitate: true, stop_first: true; }
|
|
601
|
-
*
|
|
602
|
-
* traffic_light.go('Red');
|
|
603
|
-
* traffic_light.props(); // { can_go: false, hesitate: true, stop_first: true; }
|
|
604
|
-
*
|
|
605
|
-
* traffic_light.go('Green');
|
|
606
|
-
* traffic_light.props(); // { can_go: true, hesitate: false, stop_first: false; }
|
|
607
|
-
* ```
|
|
608
|
-
*
|
|
609
|
-
*/
|
|
610
|
-
props() {
|
|
611
|
-
const ret = {};
|
|
612
|
-
this.known_props().forEach(p => ret[p] = this.prop(p));
|
|
613
|
-
return ret;
|
|
614
|
-
}
|
|
615
|
-
// NEEDS_DOCS
|
|
616
|
-
// TODO COMEBACK
|
|
617
|
-
/*********
|
|
618
|
-
*
|
|
619
|
-
* Get the current value of every prop, as an object. Compare
|
|
620
|
-
* {@link prop_map}, which returns a `Map`.
|
|
621
|
-
*
|
|
622
|
-
* ```typescript
|
|
623
|
-
*
|
|
624
|
-
* ```
|
|
625
|
-
*
|
|
626
|
-
*/
|
|
627
|
-
// sparse_props(name: string): object {
|
|
628
|
-
// }
|
|
629
|
-
// NEEDS_DOCS
|
|
630
|
-
// TODO COMEBACK
|
|
631
|
-
/*********
|
|
632
|
-
*
|
|
633
|
-
* Get the current value of every prop, as an object. Compare
|
|
634
|
-
* {@link prop_map}, which returns a `Map`. Akin to {@link strict_prop},
|
|
635
|
-
* this throws if a required prop is missing.
|
|
636
|
-
*
|
|
637
|
-
* ```typescript
|
|
638
|
-
*
|
|
639
|
-
* ```
|
|
640
|
-
*
|
|
641
|
-
*/
|
|
642
|
-
// strict_props(name: string): object {
|
|
643
|
-
// }
|
|
644
|
-
/*********
|
|
645
|
-
*
|
|
646
|
-
* Check whether a given string is a known property's name.
|
|
647
|
-
*
|
|
648
|
-
* ```typescript
|
|
649
|
-
* const example = sm`property foo default 1; a->b;`;
|
|
650
|
-
*
|
|
651
|
-
* example.known_prop('foo'); // true
|
|
652
|
-
* example.known_prop('bar'); // false
|
|
653
|
-
* ```
|
|
654
|
-
*
|
|
655
|
-
* @param prop_name The relevant property name to look up
|
|
656
|
-
*
|
|
657
|
-
*/
|
|
658
|
-
known_prop(prop_name) {
|
|
659
|
-
return this._property_keys.has(prop_name);
|
|
660
|
-
}
|
|
661
|
-
// NEEDS_DOCS
|
|
662
|
-
/*********
|
|
663
|
-
*
|
|
664
|
-
* List all known property names. If you'd also like values, use
|
|
665
|
-
* {@link props} instead. The order of the properties is not defined, and
|
|
666
|
-
* the properties generally will not be sorted.
|
|
667
|
-
*
|
|
668
|
-
* ```typescript
|
|
669
|
-
* ```
|
|
670
|
-
*
|
|
671
|
-
*/
|
|
672
|
-
known_props() {
|
|
673
|
-
return [...this._property_keys];
|
|
674
|
-
}
|
|
675
|
-
/********
|
|
676
|
-
*
|
|
677
|
-
* Check whether a given state is a valid start state (either because it was
|
|
678
|
-
* explicitly named as such, or because it was the first mentioned state.)
|
|
679
|
-
*
|
|
680
|
-
* ```typescript
|
|
681
|
-
* import { sm, is_start_state } from 'jssm';
|
|
682
|
-
*
|
|
683
|
-
* const example = sm`a -> b;`;
|
|
684
|
-
*
|
|
685
|
-
* console.log( final_test.is_start_state('a') ); // true
|
|
686
|
-
* console.log( final_test.is_start_state('b') ); // false
|
|
687
|
-
*
|
|
688
|
-
* const example = sm`start_states: [a b]; a -> b;`;
|
|
689
|
-
*
|
|
690
|
-
* console.log( final_test.is_start_state('a') ); // true
|
|
691
|
-
* console.log( final_test.is_start_state('b') ); // true
|
|
692
|
-
* ```
|
|
693
|
-
*
|
|
694
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
695
|
-
*
|
|
696
|
-
* @param whichState The name of the state to check
|
|
697
|
-
*
|
|
698
|
-
*/
|
|
699
|
-
is_start_state(whichState) {
|
|
700
|
-
return this._start_states.has(whichState);
|
|
701
|
-
}
|
|
702
|
-
/********
|
|
703
|
-
*
|
|
704
|
-
* Check whether a given state is a valid start state (either because it was
|
|
705
|
-
* explicitly named as such, or because it was the first mentioned state.)
|
|
706
|
-
*
|
|
707
|
-
* ```typescript
|
|
708
|
-
* import { sm, is_end_state } from 'jssm';
|
|
709
|
-
*
|
|
710
|
-
* const example = sm`a -> b;`;
|
|
711
|
-
*
|
|
712
|
-
* console.log( final_test.is_start_state('a') ); // false
|
|
713
|
-
* console.log( final_test.is_start_state('b') ); // true
|
|
714
|
-
*
|
|
715
|
-
* const example = sm`end_states: [a b]; a -> b;`;
|
|
716
|
-
*
|
|
717
|
-
* console.log( final_test.is_start_state('a') ); // true
|
|
718
|
-
* console.log( final_test.is_start_state('b') ); // true
|
|
719
|
-
* ```
|
|
720
|
-
*
|
|
721
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
722
|
-
*
|
|
723
|
-
* @param whichState The name of the state to check
|
|
724
|
-
*
|
|
725
|
-
*/
|
|
726
|
-
is_end_state(whichState) {
|
|
727
|
-
return this._end_states.has(whichState);
|
|
728
|
-
}
|
|
729
|
-
/********
|
|
730
|
-
*
|
|
731
|
-
* Check whether a given state is final (either has no exits or is marked
|
|
732
|
-
* `complete`.)
|
|
733
|
-
*
|
|
734
|
-
* ```typescript
|
|
735
|
-
* import { sm, state_is_final } from 'jssm';
|
|
736
|
-
*
|
|
737
|
-
* const final_test = sm`first -> second;`;
|
|
738
|
-
*
|
|
739
|
-
* console.log( final_test.state_is_final('first') ); // false
|
|
740
|
-
* console.log( final_test.state_is_final('second') ); // true
|
|
741
|
-
* ```
|
|
742
|
-
*
|
|
743
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
744
|
-
*
|
|
745
|
-
* @param whichState The name of the state to check for finality
|
|
746
|
-
*
|
|
747
|
-
*/
|
|
748
|
-
state_is_final(whichState) {
|
|
749
|
-
return ((this.state_is_terminal(whichState)) || (this.state_is_complete(whichState)));
|
|
750
|
-
}
|
|
751
|
-
/********
|
|
752
|
-
*
|
|
753
|
-
* Check whether the current state is final (either has no exits or is marked
|
|
754
|
-
* `complete`.)
|
|
755
|
-
*
|
|
756
|
-
* ```typescript
|
|
757
|
-
* import { sm, is_final } from 'jssm';
|
|
758
|
-
*
|
|
759
|
-
* const final_test = sm`first -> second;`;
|
|
760
|
-
*
|
|
761
|
-
* console.log( final_test.is_final() ); // false
|
|
762
|
-
* state.transition('second');
|
|
763
|
-
* console.log( final_test.is_final() ); // true
|
|
764
|
-
* ```
|
|
765
|
-
*
|
|
766
|
-
*/
|
|
767
|
-
is_final() {
|
|
768
|
-
// return ((!this.is_changing()) && this.state_is_final(this.state()));
|
|
769
|
-
return this.state_is_final(this.state());
|
|
770
|
-
}
|
|
771
|
-
/********
|
|
772
|
-
*
|
|
773
|
-
* Serialize the current machine, including all defining state but not the
|
|
774
|
-
* machine string, to a structure. This means you will need the machine
|
|
775
|
-
* string to recreate (to not waste repeated space;) if you want the machine
|
|
776
|
-
* string embedded, call {@link serialize_with_string} instead.
|
|
777
|
-
*
|
|
778
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
779
|
-
*
|
|
780
|
-
*/
|
|
781
|
-
serialize(comment) {
|
|
782
|
-
return {
|
|
783
|
-
comment,
|
|
784
|
-
state: this._state,
|
|
785
|
-
data: this._data,
|
|
786
|
-
jssm_version: version,
|
|
787
|
-
history: this._history.toArray(),
|
|
788
|
-
history_capacity: this._history.capacity,
|
|
789
|
-
timestamp: new Date().getTime(),
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
graph_layout() {
|
|
793
|
-
return this._graph_layout;
|
|
794
|
-
}
|
|
795
|
-
dot_preamble() {
|
|
796
|
-
return this._dot_preamble;
|
|
797
|
-
}
|
|
798
|
-
machine_author() {
|
|
799
|
-
return this._machine_author;
|
|
800
|
-
}
|
|
801
|
-
machine_comment() {
|
|
802
|
-
return this._machine_comment;
|
|
803
|
-
}
|
|
804
|
-
machine_contributor() {
|
|
805
|
-
return this._machine_contributor;
|
|
806
|
-
}
|
|
807
|
-
machine_definition() {
|
|
808
|
-
return this._machine_definition;
|
|
809
|
-
}
|
|
810
|
-
machine_language() {
|
|
811
|
-
return this._machine_language;
|
|
812
|
-
}
|
|
813
|
-
machine_license() {
|
|
814
|
-
return this._machine_license;
|
|
815
|
-
}
|
|
816
|
-
machine_name() {
|
|
817
|
-
return this._machine_name;
|
|
818
|
-
}
|
|
819
|
-
machine_version() {
|
|
820
|
-
return this._machine_version;
|
|
821
|
-
}
|
|
822
|
-
raw_state_declarations() {
|
|
823
|
-
return this._raw_state_declaration;
|
|
824
|
-
}
|
|
825
|
-
state_declaration(which) {
|
|
826
|
-
return this._state_declarations.get(which);
|
|
827
|
-
}
|
|
828
|
-
state_declarations() {
|
|
829
|
-
return this._state_declarations;
|
|
830
|
-
}
|
|
831
|
-
fsl_version() {
|
|
832
|
-
return this._fsl_version;
|
|
833
|
-
}
|
|
834
|
-
machine_state() {
|
|
835
|
-
return {
|
|
836
|
-
internal_state_impl_version: 1,
|
|
837
|
-
actions: this._actions,
|
|
838
|
-
edge_map: this._edge_map,
|
|
839
|
-
edges: this._edges,
|
|
840
|
-
named_transitions: this._named_transitions,
|
|
841
|
-
reverse_actions: this._reverse_actions,
|
|
842
|
-
// reverse_action_targets : this._reverse_action_targets,
|
|
843
|
-
state: this._state,
|
|
844
|
-
states: this._states
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
/*********
|
|
848
|
-
*
|
|
849
|
-
* List all the states known by the machine. Please note that the order of
|
|
850
|
-
* these states is not guaranteed.
|
|
851
|
-
*
|
|
852
|
-
* ```typescript
|
|
853
|
-
* import * as jssm from 'jssm';
|
|
854
|
-
*
|
|
855
|
-
* const lswitch = jssm.from('on <=> off;');
|
|
856
|
-
* console.log( lswitch.states() ); // ['on', 'off']
|
|
857
|
-
* ```
|
|
858
|
-
*
|
|
859
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
860
|
-
*
|
|
861
|
-
*/
|
|
862
|
-
states() {
|
|
863
|
-
return Array.from(this._states.keys());
|
|
864
|
-
}
|
|
865
|
-
state_for(whichState) {
|
|
866
|
-
const state = this._states.get(whichState);
|
|
867
|
-
if (state) {
|
|
868
|
-
return state;
|
|
869
|
-
}
|
|
870
|
-
else {
|
|
871
|
-
throw new JssmError(this, 'No such state', { requested_state: whichState });
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
/*********
|
|
875
|
-
*
|
|
876
|
-
* Check whether the machine knows a given state.
|
|
877
|
-
*
|
|
878
|
-
* ```typescript
|
|
879
|
-
* import * as jssm from 'jssm';
|
|
880
|
-
*
|
|
881
|
-
* const lswitch = jssm.from('on <=> off;');
|
|
882
|
-
*
|
|
883
|
-
* console.log( lswitch.has_state('off') ); // true
|
|
884
|
-
* console.log( lswitch.has_state('dance') ); // false
|
|
885
|
-
* ```
|
|
886
|
-
*
|
|
887
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
888
|
-
*
|
|
889
|
-
* @param whichState The state to be checked for extance
|
|
890
|
-
*
|
|
891
|
-
*/
|
|
892
|
-
has_state(whichState) {
|
|
893
|
-
return this._states.get(whichState) !== undefined;
|
|
894
|
-
}
|
|
895
|
-
/*********
|
|
896
|
-
*
|
|
897
|
-
* Lists all edges of a machine.
|
|
898
|
-
*
|
|
899
|
-
* ```typescript
|
|
900
|
-
* import { sm } from 'jssm';
|
|
901
|
-
*
|
|
902
|
-
* const lswitch = sm`on 'toggle' <=> 'toggle' off;`;
|
|
903
|
-
*
|
|
904
|
-
* lswitch.list_edges();
|
|
905
|
-
* [
|
|
906
|
-
* {
|
|
907
|
-
* from: 'on',
|
|
908
|
-
* to: 'off',
|
|
909
|
-
* kind: 'main',
|
|
910
|
-
* forced_only: false,
|
|
911
|
-
* main_path: true,
|
|
912
|
-
* action: 'toggle'
|
|
913
|
-
* },
|
|
914
|
-
* {
|
|
915
|
-
* from: 'off',
|
|
916
|
-
* to: 'on',
|
|
917
|
-
* kind: 'main',
|
|
918
|
-
* forced_only: false,
|
|
919
|
-
* main_path: true,
|
|
920
|
-
* action: 'toggle'
|
|
921
|
-
* }
|
|
922
|
-
* ]
|
|
923
|
-
* ```
|
|
924
|
-
*
|
|
925
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
926
|
-
*
|
|
927
|
-
*/
|
|
928
|
-
list_edges() {
|
|
929
|
-
return this._edges;
|
|
930
|
-
}
|
|
931
|
-
list_named_transitions() {
|
|
932
|
-
return this._named_transitions;
|
|
933
|
-
}
|
|
934
|
-
list_actions() {
|
|
935
|
-
return Array.from(this._actions.keys());
|
|
936
|
-
}
|
|
937
|
-
get uses_actions() {
|
|
938
|
-
return Array.from(this._actions.keys()).length > 0;
|
|
939
|
-
}
|
|
940
|
-
get uses_forced_transitions() {
|
|
941
|
-
return this._has_forced_transitions;
|
|
942
|
-
}
|
|
943
|
-
/*********
|
|
944
|
-
*
|
|
945
|
-
* Check if the code that built the machine allows overriding state and data.
|
|
946
|
-
*
|
|
947
|
-
*/
|
|
948
|
-
get code_allows_override() {
|
|
949
|
-
return this._code_allows_override;
|
|
950
|
-
}
|
|
951
|
-
/*********
|
|
952
|
-
*
|
|
953
|
-
* Check if the machine config allows overriding state and data.
|
|
954
|
-
*
|
|
955
|
-
*/
|
|
956
|
-
get config_allows_override() {
|
|
957
|
-
return this._config_allows_override;
|
|
958
|
-
}
|
|
959
|
-
/*********
|
|
960
|
-
*
|
|
961
|
-
* Check if a machine allows overriding state and data.
|
|
962
|
-
*
|
|
963
|
-
*/
|
|
964
|
-
get allows_override() {
|
|
965
|
-
// code false? config true, throw. config false, false. config undefined, false.
|
|
966
|
-
if (this._code_allows_override === false) {
|
|
967
|
-
/* istanbul ignore next */
|
|
968
|
-
if (this._config_allows_override === true) {
|
|
969
|
-
/* istanbul ignore next */
|
|
970
|
-
throw new JssmError(this, "Code specifies no override, but config tries to permit; config may not be less strict than code; should be unreachable");
|
|
971
|
-
}
|
|
972
|
-
else {
|
|
973
|
-
return false;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
// code true? config true, true. config false, false. config undefined, true.
|
|
977
|
-
if (this._code_allows_override === true) {
|
|
978
|
-
if (this._config_allows_override === false) {
|
|
979
|
-
return false;
|
|
980
|
-
}
|
|
981
|
-
else {
|
|
982
|
-
return true;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
// code must be undefined. config false, false. config true, true. config undefined, false.
|
|
986
|
-
if (this._config_allows_override === true) {
|
|
987
|
-
return true;
|
|
988
|
-
}
|
|
989
|
-
else {
|
|
990
|
-
return false;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
all_themes() {
|
|
994
|
-
return [...theme_mapping.keys()]; // constructor sets this to "default" otherwise
|
|
995
|
-
}
|
|
996
|
-
// This will always return an array of FSL themes; the reason we spuriously
|
|
997
|
-
// add the single type is that the setter and getter need matching accept/return
|
|
998
|
-
// types, and the setter can take both as a convenience
|
|
999
|
-
get themes() {
|
|
1000
|
-
return this._themes; // constructor sets this to "default" otherwise
|
|
1001
|
-
}
|
|
1002
|
-
set themes(to) {
|
|
1003
|
-
if (typeof to === 'string') {
|
|
1004
|
-
this._themes = [to];
|
|
1005
|
-
}
|
|
1006
|
-
else {
|
|
1007
|
-
this._themes = to;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
flow() {
|
|
1011
|
-
return this._flow;
|
|
1012
|
-
}
|
|
1013
|
-
get_transition_by_state_names(from, to) {
|
|
1014
|
-
const emg = this._edge_map.get(from);
|
|
1015
|
-
if (emg) {
|
|
1016
|
-
return emg.get(to);
|
|
1017
|
-
}
|
|
1018
|
-
else {
|
|
1019
|
-
return undefined;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
lookup_transition_for(from, to) {
|
|
1023
|
-
const id = this.get_transition_by_state_names(from, to);
|
|
1024
|
-
return ((id === undefined) || (id === null)) ? undefined : this._edges[id];
|
|
1025
|
-
}
|
|
1026
|
-
/********
|
|
1027
|
-
*
|
|
1028
|
-
* List all transitions attached to the current state, sorted by entrance and
|
|
1029
|
-
* exit. The order of each sublist is not defined. A node could appear in
|
|
1030
|
-
* both lists.
|
|
1031
|
-
*
|
|
1032
|
-
* ```typescript
|
|
1033
|
-
* import { sm } from 'jssm';
|
|
1034
|
-
*
|
|
1035
|
-
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
|
|
1036
|
-
*
|
|
1037
|
-
* light.state(); // 'red'
|
|
1038
|
-
* light.list_transitions(); // { entrances: [ 'yellow', 'off' ], exits: [ 'green', 'off' ] }
|
|
1039
|
-
* ```
|
|
1040
|
-
*
|
|
1041
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1042
|
-
*
|
|
1043
|
-
* @param whichState The state whose transitions to have listed
|
|
1044
|
-
*
|
|
1045
|
-
*/
|
|
1046
|
-
list_transitions(whichState = this.state()) {
|
|
1047
|
-
return { entrances: this.list_entrances(whichState), exits: this.list_exits(whichState) };
|
|
1048
|
-
}
|
|
1049
|
-
/********
|
|
1050
|
-
*
|
|
1051
|
-
* List all entrances attached to the current state. Please note that the
|
|
1052
|
-
* order of the list is not defined. This list includes both unforced and
|
|
1053
|
-
* forced entrances; if this isn't desired, consider
|
|
1054
|
-
* {@link list_unforced_entrances} or {@link list_forced_entrances} as
|
|
1055
|
-
* appropriate.
|
|
1056
|
-
*
|
|
1057
|
-
* ```typescript
|
|
1058
|
-
* import { sm } from 'jssm';
|
|
1059
|
-
*
|
|
1060
|
-
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
|
|
1061
|
-
*
|
|
1062
|
-
* light.state(); // 'red'
|
|
1063
|
-
* light.list_entrances(); // [ 'yellow', 'off' ]
|
|
1064
|
-
* ```
|
|
1065
|
-
*
|
|
1066
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1067
|
-
*
|
|
1068
|
-
* @param whichState The state whose entrances to have listed
|
|
1069
|
-
*
|
|
1070
|
-
*/
|
|
1071
|
-
list_entrances(whichState = this.state()) {
|
|
1072
|
-
var _a, _b;
|
|
1073
|
-
const guaranteed = ((_a = this._states.get(whichState)) !== null && _a !== void 0 ? _a : { from: undefined });
|
|
1074
|
-
return (_b = guaranteed.from) !== null && _b !== void 0 ? _b : [];
|
|
1075
|
-
}
|
|
1076
|
-
/********
|
|
1077
|
-
*
|
|
1078
|
-
* List all exits attached to the current state. Please note that the order
|
|
1079
|
-
* of the list is not defined. This list includes both unforced and forced
|
|
1080
|
-
* exits; if this isn't desired, consider {@link list_unforced_exits} or
|
|
1081
|
-
* {@link list_forced_exits} as appropriate.
|
|
1082
|
-
*
|
|
1083
|
-
* ```typescript
|
|
1084
|
-
* import { sm } from 'jssm';
|
|
1085
|
-
*
|
|
1086
|
-
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
|
|
1087
|
-
*
|
|
1088
|
-
* light.state(); // 'red'
|
|
1089
|
-
* light.list_exits(); // [ 'green', 'off' ]
|
|
1090
|
-
* ```
|
|
1091
|
-
*
|
|
1092
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1093
|
-
*
|
|
1094
|
-
* @param whichState The state whose exits to have listed
|
|
1095
|
-
*
|
|
1096
|
-
*/
|
|
1097
|
-
list_exits(whichState = this.state()) {
|
|
1098
|
-
var _a, _b;
|
|
1099
|
-
const guaranteed = ((_a = this._states.get(whichState)) !== null && _a !== void 0 ? _a : { to: undefined });
|
|
1100
|
-
return (_b = guaranteed.to) !== null && _b !== void 0 ? _b : [];
|
|
1101
|
-
}
|
|
1102
|
-
probable_exits_for(whichState) {
|
|
1103
|
-
const wstate = this._states.get(whichState);
|
|
1104
|
-
if (!(wstate)) {
|
|
1105
|
-
throw new JssmError(this, `No such state ${JSON.stringify(whichState)} in probable_exits_for`);
|
|
1106
|
-
}
|
|
1107
|
-
const wstate_to = wstate.to, wtf // wstate_to_filtered -> wtf
|
|
1108
|
-
= wstate_to
|
|
1109
|
-
.map((ws) => this.lookup_transition_for(this.state(), ws))
|
|
1110
|
-
.filter(Boolean);
|
|
1111
|
-
return wtf;
|
|
1112
|
-
}
|
|
1113
|
-
probabilistic_transition() {
|
|
1114
|
-
const selected = weighted_rand_select(this.probable_exits_for(this.state()), undefined, this._rng);
|
|
1115
|
-
return this.transition(selected.to);
|
|
1116
|
-
}
|
|
1117
|
-
probabilistic_walk(n) {
|
|
1118
|
-
return seq(n)
|
|
1119
|
-
.map(() => {
|
|
1120
|
-
const state_was = this.state();
|
|
1121
|
-
this.probabilistic_transition();
|
|
1122
|
-
return state_was;
|
|
1123
|
-
})
|
|
1124
|
-
.concat([this.state()]);
|
|
1125
|
-
}
|
|
1126
|
-
probabilistic_histo_walk(n) {
|
|
1127
|
-
return histograph(this.probabilistic_walk(n));
|
|
1128
|
-
}
|
|
1129
|
-
/********
|
|
1130
|
-
*
|
|
1131
|
-
* List all actions available from this state. Please note that the order of
|
|
1132
|
-
* the actions is not guaranteed.
|
|
1133
|
-
*
|
|
1134
|
-
* ```typescript
|
|
1135
|
-
* import { sm } from 'jssm';
|
|
1136
|
-
*
|
|
1137
|
-
* const machine = sm`
|
|
1138
|
-
* red 'next' -> green 'next' -> yellow 'next' -> red;
|
|
1139
|
-
* [red yellow green] 'shutdown' ~> off 'start' -> red;
|
|
1140
|
-
* `;
|
|
1141
|
-
*
|
|
1142
|
-
* console.log( machine.state() ); // logs 'red'
|
|
1143
|
-
* console.log( machine.actions() ); // logs ['next', 'shutdown']
|
|
1144
|
-
*
|
|
1145
|
-
* machine.action('next'); // true
|
|
1146
|
-
* console.log( machine.state() ); // logs 'green'
|
|
1147
|
-
* console.log( machine.actions() ); // logs ['next', 'shutdown']
|
|
1148
|
-
*
|
|
1149
|
-
* machine.action('shutdown'); // true
|
|
1150
|
-
* console.log( machine.state() ); // logs 'off'
|
|
1151
|
-
* console.log( machine.actions() ); // logs ['start']
|
|
1152
|
-
*
|
|
1153
|
-
* machine.action('start'); // true
|
|
1154
|
-
* console.log( machine.state() ); // logs 'red'
|
|
1155
|
-
* console.log( machine.actions() ); // logs ['next', 'shutdown']
|
|
1156
|
-
* ```
|
|
1157
|
-
*
|
|
1158
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1159
|
-
*
|
|
1160
|
-
* @param whichState The state whose actions to have listed
|
|
1161
|
-
*
|
|
1162
|
-
*/
|
|
1163
|
-
actions(whichState = this.state()) {
|
|
1164
|
-
const wstate = this._reverse_actions.get(whichState);
|
|
1165
|
-
if (wstate) {
|
|
1166
|
-
return Array.from(wstate.keys());
|
|
1167
|
-
}
|
|
1168
|
-
else {
|
|
1169
|
-
if (this.has_state(whichState)) {
|
|
1170
|
-
return [];
|
|
1171
|
-
}
|
|
1172
|
-
else {
|
|
1173
|
-
throw new JssmError(this, `No such state ${JSON.stringify(whichState)}`);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
/********
|
|
1178
|
-
*
|
|
1179
|
-
* List all states that have a specific action attached. Please note that
|
|
1180
|
-
* the order of the states is not guaranteed.
|
|
1181
|
-
*
|
|
1182
|
-
* ```typescript
|
|
1183
|
-
* import { sm } from 'jssm';
|
|
1184
|
-
*
|
|
1185
|
-
* const machine = sm`
|
|
1186
|
-
* red 'next' -> green 'next' -> yellow 'next' -> red;
|
|
1187
|
-
* [red yellow green] 'shutdown' ~> off 'start' -> red;
|
|
1188
|
-
* `;
|
|
1189
|
-
*
|
|
1190
|
-
* console.log( machine.list_states_having_action('next') ); // ['red', 'green', 'yellow']
|
|
1191
|
-
* console.log( machine.list_states_having_action('start') ); // ['off']
|
|
1192
|
-
* ```
|
|
1193
|
-
*
|
|
1194
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1195
|
-
*
|
|
1196
|
-
* @param whichState The action to be checked for associated states
|
|
1197
|
-
*
|
|
1198
|
-
*/
|
|
1199
|
-
list_states_having_action(whichState) {
|
|
1200
|
-
const wstate = this._actions.get(whichState);
|
|
1201
|
-
if (wstate) {
|
|
1202
|
-
return Array.from(wstate.keys());
|
|
1203
|
-
}
|
|
1204
|
-
else {
|
|
1205
|
-
throw new JssmError(this, `No such state ${JSON.stringify(whichState)}`);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
// comeback
|
|
1209
|
-
/*
|
|
1210
|
-
list_entrance_actions(whichState: mNT = this.state() ) : Array<mNT> {
|
|
1211
|
-
return [... (this._reverse_action_targets.get(whichState) || new Map()).values()] // wasteful
|
|
1212
|
-
.map( (edgeId:any) => (this._edges[edgeId] : any)) // whargarbl burn out any
|
|
1213
|
-
.filter( (o:any) => o.to === whichState)
|
|
1214
|
-
.map( filtered => filtered.from );
|
|
1215
|
-
}
|
|
1216
|
-
*/
|
|
1217
|
-
list_exit_actions(whichState = this.state()) {
|
|
1218
|
-
const ra_base = this._reverse_actions.get(whichState);
|
|
1219
|
-
if (!(ra_base)) {
|
|
1220
|
-
throw new JssmError(this, `No such state ${JSON.stringify(whichState)}`);
|
|
1221
|
-
}
|
|
1222
|
-
return Array.from(ra_base.values())
|
|
1223
|
-
.map((edgeId) => this._edges[edgeId])
|
|
1224
|
-
.filter((o) => o.from === whichState)
|
|
1225
|
-
.map((filtered) => filtered.action);
|
|
1226
|
-
}
|
|
1227
|
-
probable_action_exits(whichState = this.state()) {
|
|
1228
|
-
const ra_base = this._reverse_actions.get(whichState);
|
|
1229
|
-
if (!(ra_base)) {
|
|
1230
|
-
throw new JssmError(this, `No such state ${JSON.stringify(whichState)}`);
|
|
1231
|
-
}
|
|
1232
|
-
return Array.from(ra_base.values())
|
|
1233
|
-
.map((edgeId) => this._edges[edgeId])
|
|
1234
|
-
.filter((o) => o.from === whichState)
|
|
1235
|
-
.map((filtered) => ({
|
|
1236
|
-
action: filtered.action,
|
|
1237
|
-
probability: filtered.probability
|
|
1238
|
-
}));
|
|
1239
|
-
}
|
|
1240
|
-
// TODO FIXME test that is_unenterable on non-state throws
|
|
1241
|
-
is_unenterable(whichState) {
|
|
1242
|
-
if (!(this.has_state(whichState))) {
|
|
1243
|
-
throw new JssmError(this, `No such state ${whichState}`);
|
|
1244
|
-
}
|
|
1245
|
-
return this.list_entrances(whichState).length === 0;
|
|
1246
|
-
}
|
|
1247
|
-
has_unenterables() {
|
|
1248
|
-
return this.states().some((x) => this.is_unenterable(x));
|
|
1249
|
-
}
|
|
1250
|
-
is_terminal() {
|
|
1251
|
-
return this.state_is_terminal(this.state());
|
|
1252
|
-
}
|
|
1253
|
-
// TODO FIXME test that state_is_terminal on non-state throws
|
|
1254
|
-
state_is_terminal(whichState) {
|
|
1255
|
-
if (!(this.has_state(whichState))) {
|
|
1256
|
-
throw new JssmError(this, `No such state ${whichState}`);
|
|
1257
|
-
}
|
|
1258
|
-
return this.list_exits(whichState).length === 0;
|
|
1259
|
-
}
|
|
1260
|
-
has_terminals() {
|
|
1261
|
-
return this.states().some((x) => this.state_is_terminal(x));
|
|
1262
|
-
}
|
|
1263
|
-
is_complete() {
|
|
1264
|
-
return this.state_is_complete(this.state());
|
|
1265
|
-
}
|
|
1266
|
-
state_is_complete(whichState) {
|
|
1267
|
-
const wstate = this._states.get(whichState);
|
|
1268
|
-
if (wstate) {
|
|
1269
|
-
return wstate.complete;
|
|
1270
|
-
}
|
|
1271
|
-
else {
|
|
1272
|
-
throw new JssmError(this, `No such state ${JSON.stringify(whichState)}`);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
has_completes() {
|
|
1276
|
-
return this.states().some((x) => this.state_is_complete(x));
|
|
1277
|
-
}
|
|
1278
|
-
// basic toolable hook call. convenience wrappers will follow, like
|
|
1279
|
-
// hook(from, to, handler) and exit_hook(from, handler) and etc
|
|
1280
|
-
set_hook(HookDesc) {
|
|
1281
|
-
switch (HookDesc.kind) {
|
|
1282
|
-
case 'hook':
|
|
1283
|
-
this._hooks.set(hook_name(HookDesc.from, HookDesc.to), HookDesc.handler);
|
|
1284
|
-
this._has_hooks = true;
|
|
1285
|
-
this._has_basic_hooks = true;
|
|
1286
|
-
break;
|
|
1287
|
-
case 'named':
|
|
1288
|
-
this._named_hooks.set(named_hook_name(HookDesc.from, HookDesc.to, HookDesc.action), HookDesc.handler);
|
|
1289
|
-
this._has_hooks = true;
|
|
1290
|
-
this._has_named_hooks = true;
|
|
1291
|
-
break;
|
|
1292
|
-
case 'global action':
|
|
1293
|
-
this._global_action_hooks.set(HookDesc.action, HookDesc.handler);
|
|
1294
|
-
this._has_hooks = true;
|
|
1295
|
-
this._has_global_action_hooks = true;
|
|
1296
|
-
break;
|
|
1297
|
-
case 'any action':
|
|
1298
|
-
this._any_action_hook = HookDesc.handler;
|
|
1299
|
-
this._has_hooks = true;
|
|
1300
|
-
break;
|
|
1301
|
-
case 'standard transition':
|
|
1302
|
-
this._standard_transition_hook = HookDesc.handler;
|
|
1303
|
-
this._has_transition_hooks = true;
|
|
1304
|
-
this._has_hooks = true;
|
|
1305
|
-
break;
|
|
1306
|
-
case 'main transition':
|
|
1307
|
-
this._main_transition_hook = HookDesc.handler;
|
|
1308
|
-
this._has_transition_hooks = true;
|
|
1309
|
-
this._has_hooks = true;
|
|
1310
|
-
break;
|
|
1311
|
-
case 'forced transition':
|
|
1312
|
-
this._forced_transition_hook = HookDesc.handler;
|
|
1313
|
-
this._has_transition_hooks = true;
|
|
1314
|
-
this._has_hooks = true;
|
|
1315
|
-
break;
|
|
1316
|
-
case 'any transition':
|
|
1317
|
-
this._any_transition_hook = HookDesc.handler;
|
|
1318
|
-
this._has_hooks = true;
|
|
1319
|
-
break;
|
|
1320
|
-
case 'entry':
|
|
1321
|
-
this._entry_hooks.set(HookDesc.to, HookDesc.handler);
|
|
1322
|
-
this._has_hooks = true;
|
|
1323
|
-
this._has_entry_hooks = true;
|
|
1324
|
-
break;
|
|
1325
|
-
case 'exit':
|
|
1326
|
-
this._exit_hooks.set(HookDesc.from, HookDesc.handler);
|
|
1327
|
-
this._has_hooks = true;
|
|
1328
|
-
this._has_exit_hooks = true;
|
|
1329
|
-
break;
|
|
1330
|
-
case 'after':
|
|
1331
|
-
this._after_hooks.set(HookDesc.from, HookDesc.handler);
|
|
1332
|
-
this._has_hooks = true;
|
|
1333
|
-
this._has_after_hooks = true;
|
|
1334
|
-
break;
|
|
1335
|
-
case 'post hook':
|
|
1336
|
-
this._post_hooks.set(hook_name(HookDesc.from, HookDesc.to), HookDesc.handler);
|
|
1337
|
-
this._has_post_hooks = true;
|
|
1338
|
-
this._has_post_basic_hooks = true;
|
|
1339
|
-
break;
|
|
1340
|
-
case 'post named':
|
|
1341
|
-
this._post_named_hooks.set(named_hook_name(HookDesc.from, HookDesc.to, HookDesc.action), HookDesc.handler);
|
|
1342
|
-
this._has_post_hooks = true;
|
|
1343
|
-
this._has_post_named_hooks = true;
|
|
1344
|
-
break;
|
|
1345
|
-
case 'post global action':
|
|
1346
|
-
this._post_global_action_hooks.set(HookDesc.action, HookDesc.handler);
|
|
1347
|
-
this._has_post_hooks = true;
|
|
1348
|
-
this._has_post_global_action_hooks = true;
|
|
1349
|
-
break;
|
|
1350
|
-
case 'post any action':
|
|
1351
|
-
this._post_any_action_hook = HookDesc.handler;
|
|
1352
|
-
this._has_post_hooks = true;
|
|
1353
|
-
break;
|
|
1354
|
-
case 'post standard transition':
|
|
1355
|
-
this._post_standard_transition_hook = HookDesc.handler;
|
|
1356
|
-
this._has_post_transition_hooks = true;
|
|
1357
|
-
this._has_post_hooks = true;
|
|
1358
|
-
break;
|
|
1359
|
-
case 'post main transition':
|
|
1360
|
-
this._post_main_transition_hook = HookDesc.handler;
|
|
1361
|
-
this._has_post_transition_hooks = true;
|
|
1362
|
-
this._has_post_hooks = true;
|
|
1363
|
-
break;
|
|
1364
|
-
case 'post forced transition':
|
|
1365
|
-
this._post_forced_transition_hook = HookDesc.handler;
|
|
1366
|
-
this._has_post_transition_hooks = true;
|
|
1367
|
-
this._has_post_hooks = true;
|
|
1368
|
-
break;
|
|
1369
|
-
case 'post any transition':
|
|
1370
|
-
this._post_any_transition_hook = HookDesc.handler;
|
|
1371
|
-
this._has_post_hooks = true;
|
|
1372
|
-
break;
|
|
1373
|
-
case 'post entry':
|
|
1374
|
-
this._post_entry_hooks.set(HookDesc.to, HookDesc.handler);
|
|
1375
|
-
this._has_post_entry_hooks = true;
|
|
1376
|
-
this._has_post_hooks = true;
|
|
1377
|
-
break;
|
|
1378
|
-
case 'post exit':
|
|
1379
|
-
this._post_exit_hooks.set(HookDesc.from, HookDesc.handler);
|
|
1380
|
-
this._has_post_exit_hooks = true;
|
|
1381
|
-
this._has_post_hooks = true;
|
|
1382
|
-
break;
|
|
1383
|
-
default:
|
|
1384
|
-
throw new JssmError(this, `Unknown hook type ${HookDesc.kind}, should be impossible`);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
hook(from, to, handler) {
|
|
1388
|
-
this.set_hook({ kind: 'hook', from, to, handler });
|
|
1389
|
-
return this;
|
|
1390
|
-
}
|
|
1391
|
-
hook_action(from, to, action, handler) {
|
|
1392
|
-
this.set_hook({ kind: 'named', from, to, action, handler });
|
|
1393
|
-
return this;
|
|
1394
|
-
}
|
|
1395
|
-
hook_global_action(action, handler) {
|
|
1396
|
-
this.set_hook({ kind: 'global action', action, handler });
|
|
1397
|
-
return this;
|
|
1398
|
-
}
|
|
1399
|
-
hook_any_action(handler) {
|
|
1400
|
-
this.set_hook({ kind: 'any action', handler });
|
|
1401
|
-
return this;
|
|
1402
|
-
}
|
|
1403
|
-
hook_standard_transition(handler) {
|
|
1404
|
-
this.set_hook({ kind: 'standard transition', handler });
|
|
1405
|
-
return this;
|
|
1406
|
-
}
|
|
1407
|
-
hook_main_transition(handler) {
|
|
1408
|
-
this.set_hook({ kind: 'main transition', handler });
|
|
1409
|
-
return this;
|
|
1410
|
-
}
|
|
1411
|
-
hook_forced_transition(handler) {
|
|
1412
|
-
this.set_hook({ kind: 'forced transition', handler });
|
|
1413
|
-
return this;
|
|
1414
|
-
}
|
|
1415
|
-
hook_any_transition(handler) {
|
|
1416
|
-
this.set_hook({ kind: 'any transition', handler });
|
|
1417
|
-
return this;
|
|
1418
|
-
}
|
|
1419
|
-
hook_entry(to, handler) {
|
|
1420
|
-
this.set_hook({ kind: 'entry', to, handler });
|
|
1421
|
-
return this;
|
|
1422
|
-
}
|
|
1423
|
-
hook_exit(from, handler) {
|
|
1424
|
-
this.set_hook({ kind: 'exit', from, handler });
|
|
1425
|
-
return this;
|
|
1426
|
-
}
|
|
1427
|
-
hook_after(from, handler) {
|
|
1428
|
-
this.set_hook({ kind: 'after', from, handler });
|
|
1429
|
-
return this;
|
|
1430
|
-
}
|
|
1431
|
-
post_hook(from, to, handler) {
|
|
1432
|
-
this.set_hook({ kind: 'post hook', from, to, handler });
|
|
1433
|
-
return this;
|
|
1434
|
-
}
|
|
1435
|
-
post_hook_action(from, to, action, handler) {
|
|
1436
|
-
this.set_hook({ kind: 'post named', from, to, action, handler });
|
|
1437
|
-
return this;
|
|
1438
|
-
}
|
|
1439
|
-
post_hook_global_action(action, handler) {
|
|
1440
|
-
this.set_hook({ kind: 'post global action', action, handler });
|
|
1441
|
-
return this;
|
|
1442
|
-
}
|
|
1443
|
-
post_hook_any_action(handler) {
|
|
1444
|
-
this.set_hook({ kind: 'post any action', handler });
|
|
1445
|
-
return this;
|
|
1446
|
-
}
|
|
1447
|
-
post_hook_standard_transition(handler) {
|
|
1448
|
-
this.set_hook({ kind: 'post standard transition', handler });
|
|
1449
|
-
return this;
|
|
1450
|
-
}
|
|
1451
|
-
post_hook_main_transition(handler) {
|
|
1452
|
-
this.set_hook({ kind: 'post main transition', handler });
|
|
1453
|
-
return this;
|
|
1454
|
-
}
|
|
1455
|
-
post_hook_forced_transition(handler) {
|
|
1456
|
-
this.set_hook({ kind: 'post forced transition', handler });
|
|
1457
|
-
return this;
|
|
1458
|
-
}
|
|
1459
|
-
post_hook_any_transition(handler) {
|
|
1460
|
-
this.set_hook({ kind: 'post any transition', handler });
|
|
1461
|
-
return this;
|
|
1462
|
-
}
|
|
1463
|
-
post_hook_entry(to, handler) {
|
|
1464
|
-
this.set_hook({ kind: 'post entry', to, handler });
|
|
1465
|
-
return this;
|
|
1466
|
-
}
|
|
1467
|
-
post_hook_exit(from, handler) {
|
|
1468
|
-
this.set_hook({ kind: 'post exit', from, handler });
|
|
1469
|
-
return this;
|
|
1470
|
-
}
|
|
1471
|
-
get rng_seed() {
|
|
1472
|
-
return this._rng_seed;
|
|
1473
|
-
}
|
|
1474
|
-
set rng_seed(to) {
|
|
1475
|
-
if (typeof to === 'undefined') {
|
|
1476
|
-
this._rng_seed = new Date().getTime();
|
|
1477
|
-
}
|
|
1478
|
-
else {
|
|
1479
|
-
this._rng_seed = to;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
// remove_hook(HookDesc: HookDescription) {
|
|
1483
|
-
// throw new JssmError(this, 'TODO: Should remove hook here');
|
|
1484
|
-
// }
|
|
1485
|
-
edges_between(from, to) {
|
|
1486
|
-
return this._edges.filter(edge => ((edge.from === from) && (edge.to === to)));
|
|
1487
|
-
}
|
|
1488
|
-
/*********
|
|
1489
|
-
*
|
|
1490
|
-
* Replace the current state and data with no regard to the graph.
|
|
1491
|
-
*
|
|
1492
|
-
* ```typescript
|
|
1493
|
-
* import { sm } from 'jssm';
|
|
1494
|
-
*
|
|
1495
|
-
* const machine = sm`a -> b -> c;`;
|
|
1496
|
-
* console.log( machine.state() ); // 'a'
|
|
1497
|
-
*
|
|
1498
|
-
* machine.go('b');
|
|
1499
|
-
* machine.go('c');
|
|
1500
|
-
* console.log( machine.state() ); // 'c'
|
|
1501
|
-
*
|
|
1502
|
-
* machine.override('a');
|
|
1503
|
-
* console.log( machine.state() ); // 'a'
|
|
1504
|
-
* ```
|
|
1505
|
-
*
|
|
1506
|
-
*/
|
|
1507
|
-
override(newState, newData) {
|
|
1508
|
-
if (this.allows_override) {
|
|
1509
|
-
if (this._states.has(newState)) {
|
|
1510
|
-
this._state = newState;
|
|
1511
|
-
this._data = newData;
|
|
1512
|
-
}
|
|
1513
|
-
else {
|
|
1514
|
-
throw new JssmError(this, `Cannot override state to "${newState}", a state that does not exist`);
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
else {
|
|
1518
|
-
throw new JssmError(this, "Code specifies no override, but config tries to permit; config may not be less strict than code");
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
transition_impl(newStateOrAction, newData, wasForced, wasAction) {
|
|
1522
|
-
// TODO the forced-ness behavior needs to be cleaned up a lot here
|
|
1523
|
-
// TODO all the callbacks are wrong on forced, action, etc
|
|
1524
|
-
let valid = false, trans_type, newState, fromAction = undefined;
|
|
1525
|
-
if (wasForced) {
|
|
1526
|
-
if (this.valid_force_transition(newStateOrAction, newData)) {
|
|
1527
|
-
valid = true;
|
|
1528
|
-
trans_type = 'forced';
|
|
1529
|
-
newState = newStateOrAction;
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
else if (wasAction) {
|
|
1533
|
-
if (this.valid_action(newStateOrAction, newData)) {
|
|
1534
|
-
const edge = this.current_action_edge_for(newStateOrAction);
|
|
1535
|
-
valid = true;
|
|
1536
|
-
trans_type = edge.kind;
|
|
1537
|
-
newState = edge.to;
|
|
1538
|
-
fromAction = newStateOrAction;
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
else {
|
|
1542
|
-
if (this.valid_transition(newStateOrAction, newData)) {
|
|
1543
|
-
if (this._has_transition_hooks) {
|
|
1544
|
-
trans_type = this.edges_between(this._state, newStateOrAction)[0].kind; // TODO this won't do the right thing if various edges have different types
|
|
1545
|
-
}
|
|
1546
|
-
valid = true;
|
|
1547
|
-
newState = newStateOrAction;
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
const hook_args = {
|
|
1551
|
-
data: this._data,
|
|
1552
|
-
action: fromAction,
|
|
1553
|
-
from: this._state,
|
|
1554
|
-
to: newState,
|
|
1555
|
-
next_data: newData,
|
|
1556
|
-
forced: wasForced,
|
|
1557
|
-
trans_type
|
|
1558
|
-
};
|
|
1559
|
-
if (valid) {
|
|
1560
|
-
if (this._has_hooks) {
|
|
1561
|
-
// once validity is known, clear old 'after' timeout clause
|
|
1562
|
-
this.clear_state_timeout();
|
|
1563
|
-
function update_fields(res) {
|
|
1564
|
-
if (res.hasOwnProperty('data')) {
|
|
1565
|
-
hook_args.data = res.data;
|
|
1566
|
-
hook_args.next_data = res.next_data;
|
|
1567
|
-
data_changed = true;
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
let data_changed = false;
|
|
1571
|
-
if (wasAction) {
|
|
1572
|
-
// 1a. any action hook
|
|
1573
|
-
const outcome = abstract_hook_step(this._any_action_hook, hook_args);
|
|
1574
|
-
if (outcome.pass === false) {
|
|
1575
|
-
return false;
|
|
1576
|
-
}
|
|
1577
|
-
update_fields(outcome);
|
|
1578
|
-
// 1b. global specific action hook
|
|
1579
|
-
const outcome2 = abstract_hook_step(this._global_action_hooks.get(newStateOrAction), hook_args);
|
|
1580
|
-
if (outcome2.pass === false) {
|
|
1581
|
-
return false;
|
|
1582
|
-
}
|
|
1583
|
-
update_fields(outcome2);
|
|
1584
|
-
}
|
|
1585
|
-
// 2. after hook
|
|
1586
|
-
if (this._has_after_hooks) {
|
|
1587
|
-
const ah = this._after_hooks.get(newStateOrAction);
|
|
1588
|
-
const outcome = abstract_hook_step(ah, hook_args);
|
|
1589
|
-
// there's no such thing as after not passing, so, omit the result pass check
|
|
1590
|
-
/* istanbul can't trace this through the timer */
|
|
1591
|
-
/* istanbul ignore next */
|
|
1592
|
-
if (ah !== undefined) {
|
|
1593
|
-
/* istanbul can't trace this through the timer */
|
|
1594
|
-
/* istanbul ignore next */
|
|
1595
|
-
ah({ data: outcome.data, next_data: outcome.next_data });
|
|
1596
|
-
}
|
|
1597
|
-
update_fields(outcome);
|
|
1598
|
-
}
|
|
1599
|
-
// 3. any transition hook
|
|
1600
|
-
if (this._any_transition_hook !== undefined) {
|
|
1601
|
-
const outcome = abstract_hook_step(this._any_transition_hook, hook_args);
|
|
1602
|
-
if (outcome.pass === false) {
|
|
1603
|
-
return false;
|
|
1604
|
-
}
|
|
1605
|
-
update_fields(outcome);
|
|
1606
|
-
}
|
|
1607
|
-
// 4. exit hook
|
|
1608
|
-
if (this._has_exit_hooks) {
|
|
1609
|
-
const outcome = abstract_hook_step(this._exit_hooks.get(this._state), hook_args);
|
|
1610
|
-
if (outcome.pass === false) {
|
|
1611
|
-
return false;
|
|
1612
|
-
}
|
|
1613
|
-
update_fields(outcome);
|
|
1614
|
-
}
|
|
1615
|
-
// 5. named transition / action hook
|
|
1616
|
-
if (this._has_named_hooks) {
|
|
1617
|
-
if (wasAction) {
|
|
1618
|
-
const nhn = named_hook_name(this._state, newState, newStateOrAction), outcome = abstract_hook_step(this._named_hooks.get(nhn), hook_args);
|
|
1619
|
-
if (outcome.pass === false) {
|
|
1620
|
-
return false;
|
|
1621
|
-
}
|
|
1622
|
-
update_fields(outcome);
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
// 6. regular hook
|
|
1626
|
-
if (this._has_basic_hooks) {
|
|
1627
|
-
const hn = hook_name(this._state, newState), outcome = abstract_hook_step(this._hooks.get(hn), hook_args);
|
|
1628
|
-
if (outcome.pass === false) {
|
|
1629
|
-
return false;
|
|
1630
|
-
}
|
|
1631
|
-
update_fields(outcome);
|
|
1632
|
-
}
|
|
1633
|
-
// 7. edge type hook
|
|
1634
|
-
// 7a. standard transition hook
|
|
1635
|
-
if (trans_type === 'legal') {
|
|
1636
|
-
const outcome = abstract_hook_step(this._standard_transition_hook, hook_args);
|
|
1637
|
-
if (outcome.pass === false) {
|
|
1638
|
-
return false;
|
|
1639
|
-
}
|
|
1640
|
-
update_fields(outcome);
|
|
1641
|
-
}
|
|
1642
|
-
// 7b. main type hook
|
|
1643
|
-
if (trans_type === 'main') {
|
|
1644
|
-
const outcome = abstract_hook_step(this._main_transition_hook, hook_args);
|
|
1645
|
-
if (outcome.pass === false) {
|
|
1646
|
-
return false;
|
|
1647
|
-
}
|
|
1648
|
-
update_fields(outcome);
|
|
1649
|
-
}
|
|
1650
|
-
// 7c. forced transition hook
|
|
1651
|
-
if (trans_type === 'forced') {
|
|
1652
|
-
const outcome = abstract_hook_step(this._forced_transition_hook, hook_args);
|
|
1653
|
-
if (outcome.pass === false) {
|
|
1654
|
-
return false;
|
|
1655
|
-
}
|
|
1656
|
-
update_fields(outcome);
|
|
1657
|
-
}
|
|
1658
|
-
// 8. entry hook
|
|
1659
|
-
if (this._has_entry_hooks) {
|
|
1660
|
-
const outcome = abstract_hook_step(this._entry_hooks.get(newState), hook_args);
|
|
1661
|
-
if (outcome.pass === false) {
|
|
1662
|
-
return false;
|
|
1663
|
-
}
|
|
1664
|
-
update_fields(outcome);
|
|
1665
|
-
}
|
|
1666
|
-
// all hooks passed! let's now establish the result
|
|
1667
|
-
if (this._history_length) {
|
|
1668
|
-
this._history.shove([this._state, this._data]);
|
|
1669
|
-
}
|
|
1670
|
-
this._state = newState;
|
|
1671
|
-
if (data_changed) {
|
|
1672
|
-
this._data = hook_args.data;
|
|
1673
|
-
}
|
|
1674
|
-
else if (newData !== undefined) {
|
|
1675
|
-
this._data = newData;
|
|
1676
|
-
}
|
|
1677
|
-
// success fallthrough to posthooks; intentionally no return here
|
|
1678
|
-
// look for "posthooks begin here"
|
|
1679
|
-
// or without hooks
|
|
1680
|
-
}
|
|
1681
|
-
else {
|
|
1682
|
-
if (this._history_length) {
|
|
1683
|
-
this._history.shove([this._state, this._data]);
|
|
1684
|
-
}
|
|
1685
|
-
this._state = newState;
|
|
1686
|
-
// TODO known bug: this gives no way to set data to undefined
|
|
1687
|
-
// see https://github.com/StoneCypher/fsl/issues/1264
|
|
1688
|
-
if (newData !== undefined) {
|
|
1689
|
-
this._data = newData;
|
|
1690
|
-
}
|
|
1691
|
-
// success fallthrough to posthooks; intentionally no return here
|
|
1692
|
-
// look for "posthooks begin here"
|
|
1693
|
-
}
|
|
1694
|
-
// not valid
|
|
1695
|
-
}
|
|
1696
|
-
else {
|
|
1697
|
-
return false;
|
|
1698
|
-
}
|
|
1699
|
-
// posthooks begin here
|
|
1700
|
-
if (this._has_post_hooks) {
|
|
1701
|
-
if (wasAction) {
|
|
1702
|
-
// 1. any action posthook
|
|
1703
|
-
if (this._post_any_action_hook !== undefined) {
|
|
1704
|
-
this._post_any_action_hook(hook_args);
|
|
1705
|
-
}
|
|
1706
|
-
// 2. global specific action hook
|
|
1707
|
-
const pgah = this._post_global_action_hooks.get(hook_args.action);
|
|
1708
|
-
if (pgah !== undefined) {
|
|
1709
|
-
pgah(hook_args);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
// 3. any transition hook
|
|
1713
|
-
if (this._post_any_transition_hook !== undefined) {
|
|
1714
|
-
this._post_any_transition_hook(hook_args);
|
|
1715
|
-
}
|
|
1716
|
-
// 4. exit hook
|
|
1717
|
-
if (this._has_post_exit_hooks) {
|
|
1718
|
-
const peh = this._post_exit_hooks.get(hook_args.from); // todo this is probably from instead
|
|
1719
|
-
if (peh !== undefined) {
|
|
1720
|
-
peh(hook_args);
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
// 5. named transition / action hook
|
|
1724
|
-
if (this._has_post_named_hooks) {
|
|
1725
|
-
if (wasAction) {
|
|
1726
|
-
const nhn = named_hook_name(hook_args.from, hook_args.to, hook_args.action), pnh = this._post_named_hooks.get(nhn);
|
|
1727
|
-
if (pnh !== undefined) {
|
|
1728
|
-
pnh(hook_args);
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
// 6. regular hook
|
|
1733
|
-
if (this._has_post_basic_hooks) {
|
|
1734
|
-
const hook = this._post_hooks.get(hook_name(hook_args.from, hook_args.to));
|
|
1735
|
-
if (hook !== undefined) {
|
|
1736
|
-
hook(hook_args);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
// 7. edge type hook
|
|
1740
|
-
// 7a. standard transition hook
|
|
1741
|
-
if (trans_type === 'legal') {
|
|
1742
|
-
if (this._post_standard_transition_hook !== undefined) {
|
|
1743
|
-
this._post_standard_transition_hook(hook_args);
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
// 7b. main type hook
|
|
1747
|
-
if (trans_type === 'main') {
|
|
1748
|
-
if (this._post_main_transition_hook !== undefined) {
|
|
1749
|
-
this._post_main_transition_hook(hook_args);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
// 7c. forced transition hook
|
|
1753
|
-
if (trans_type === 'forced') {
|
|
1754
|
-
if (this._post_forced_transition_hook !== undefined) {
|
|
1755
|
-
this._post_forced_transition_hook(hook_args);
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
// 8. entry hook
|
|
1759
|
-
if (this._has_post_entry_hooks) {
|
|
1760
|
-
const hook = this._post_entry_hooks.get(hook_args.to);
|
|
1761
|
-
if (hook !== undefined) {
|
|
1762
|
-
hook(hook_args);
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
// possibly re-establish new 'after' clause
|
|
1767
|
-
this.auto_set_state_timeout();
|
|
1768
|
-
return true;
|
|
1769
|
-
}
|
|
1770
|
-
auto_set_state_timeout() {
|
|
1771
|
-
const after_res = this._after_mapping.get(this._state);
|
|
1772
|
-
if (after_res !== undefined) {
|
|
1773
|
-
const [next_state, after_time] = after_res;
|
|
1774
|
-
this.set_state_timeout(next_state, after_time);
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
/*********
|
|
1778
|
-
*
|
|
1779
|
-
* Get a truncated history of the recent states and data of the machine.
|
|
1780
|
-
* Turned off by default; configure with `.from('...', {data: 5})` by length,
|
|
1781
|
-
* or set `.history_length` at runtime.
|
|
1782
|
-
*
|
|
1783
|
-
* History *does not contain the current state*. If you want that, call
|
|
1784
|
-
* `.history_inclusive` instead.
|
|
1785
|
-
*
|
|
1786
|
-
* ```typescript
|
|
1787
|
-
* const foo = jssm.from(
|
|
1788
|
-
* "a 'next' -> b 'next' -> c 'next' -> d 'next' -> e;",
|
|
1789
|
-
* { history: 3 }
|
|
1790
|
-
* );
|
|
1791
|
-
*
|
|
1792
|
-
* foo.action('next');
|
|
1793
|
-
* foo.action('next');
|
|
1794
|
-
* foo.action('next');
|
|
1795
|
-
* foo.action('next');
|
|
1796
|
-
*
|
|
1797
|
-
* foo.history; // [ ['b',undefined], ['c',undefined], ['d',undefined] ]
|
|
1798
|
-
* ```
|
|
1799
|
-
*
|
|
1800
|
-
* Notice that the machine's current state, `e`, is not in the returned list.
|
|
1801
|
-
*
|
|
1802
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1803
|
-
*
|
|
1804
|
-
*/
|
|
1805
|
-
get history() {
|
|
1806
|
-
return this._history.toArray();
|
|
1807
|
-
}
|
|
1808
|
-
/*********
|
|
1809
|
-
*
|
|
1810
|
-
* Get a truncated history of the recent states and data of the machine,
|
|
1811
|
-
* including the current state. Turned off by default; configure with
|
|
1812
|
-
* `.from('...', {data: 5})` by length, or set `.history_length` at runtime.
|
|
1813
|
-
*
|
|
1814
|
-
* History inclusive contains the current state. If you only want past
|
|
1815
|
-
* states, call `.history` instead.
|
|
1816
|
-
*
|
|
1817
|
-
* The list returned will be one longer than the history buffer kept, as the
|
|
1818
|
-
* history buffer kept gets the current state added to it to produce this
|
|
1819
|
-
* list.
|
|
1820
|
-
*
|
|
1821
|
-
* ```typescript
|
|
1822
|
-
* const foo = jssm.from(
|
|
1823
|
-
* "a 'next' -> b 'next' -> c 'next' -> d 'next' -> e;",
|
|
1824
|
-
* { history: 3 }
|
|
1825
|
-
* );
|
|
1826
|
-
*
|
|
1827
|
-
* foo.action('next');
|
|
1828
|
-
* foo.action('next');
|
|
1829
|
-
* foo.action('next');
|
|
1830
|
-
* foo.action('next');
|
|
1831
|
-
*
|
|
1832
|
-
* foo.history_inclusive; // [ ['b',undefined], ['c',undefined], ['d',undefined], ['e',undefined] ]
|
|
1833
|
-
* ```
|
|
1834
|
-
*
|
|
1835
|
-
* Notice that the machine's current state, `e`, is in the returned list.
|
|
1836
|
-
*
|
|
1837
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1838
|
-
*
|
|
1839
|
-
*/
|
|
1840
|
-
get history_inclusive() {
|
|
1841
|
-
const ret = this._history.toArray();
|
|
1842
|
-
ret.push([this.state(), this.data()]);
|
|
1843
|
-
return ret;
|
|
1844
|
-
}
|
|
1845
|
-
/*********
|
|
1846
|
-
*
|
|
1847
|
-
* Find out how long a history this machine is keeping. Defaults to zero.
|
|
1848
|
-
* Settable directly.
|
|
1849
|
-
*
|
|
1850
|
-
* ```typescript
|
|
1851
|
-
* const foo = jssm.from("a -> b;");
|
|
1852
|
-
* foo.history_length; // 0
|
|
1853
|
-
*
|
|
1854
|
-
* const bar = jssm.from("a -> b;", { history: 3 });
|
|
1855
|
-
* foo.history_length; // 3
|
|
1856
|
-
* foo.history_length = 5;
|
|
1857
|
-
* foo.history_length; // 5
|
|
1858
|
-
* ```
|
|
1859
|
-
*
|
|
1860
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1861
|
-
*
|
|
1862
|
-
*/
|
|
1863
|
-
get history_length() {
|
|
1864
|
-
return this._history_length;
|
|
1865
|
-
}
|
|
1866
|
-
set history_length(to) {
|
|
1867
|
-
this._history_length = to;
|
|
1868
|
-
this._history.resize(to, true);
|
|
1869
|
-
}
|
|
1870
|
-
/********
|
|
1871
|
-
*
|
|
1872
|
-
* Instruct the machine to complete an action. Synonym for {@link do}.
|
|
1873
|
-
*
|
|
1874
|
-
* ```typescript
|
|
1875
|
-
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
|
|
1876
|
-
*
|
|
1877
|
-
* light.state(); // 'red'
|
|
1878
|
-
* light.action('next'); // true
|
|
1879
|
-
* light.state(); // 'green'
|
|
1880
|
-
* ```
|
|
1881
|
-
*
|
|
1882
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1883
|
-
*
|
|
1884
|
-
* @param actionName The action to engage
|
|
1885
|
-
*
|
|
1886
|
-
* @param newData The data change to insert during the action
|
|
1887
|
-
*
|
|
1888
|
-
*/
|
|
1889
|
-
action(actionName, newData) {
|
|
1890
|
-
return this.transition_impl(actionName, newData, false, true);
|
|
1891
|
-
}
|
|
1892
|
-
/********
|
|
1893
|
-
*
|
|
1894
|
-
* Get the standard style for a single state. ***Does not*** include
|
|
1895
|
-
* composition from an applied theme, or things from the underlying base
|
|
1896
|
-
* stylesheet; only the modifications applied by this machine.
|
|
1897
|
-
*
|
|
1898
|
-
* ```typescript
|
|
1899
|
-
* const light = sm`a -> b;`;
|
|
1900
|
-
* console.log(light.standard_state_style);
|
|
1901
|
-
* // {}
|
|
1902
|
-
*
|
|
1903
|
-
* const light = sm`a -> b; state: { shape: circle; };`;
|
|
1904
|
-
* console.log(light.standard_state_style);
|
|
1905
|
-
* // { shape: 'circle' }
|
|
1906
|
-
* ```
|
|
1907
|
-
*
|
|
1908
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1909
|
-
*
|
|
1910
|
-
*/
|
|
1911
|
-
get standard_state_style() {
|
|
1912
|
-
return this._state_style;
|
|
1913
|
-
}
|
|
1914
|
-
/********
|
|
1915
|
-
*
|
|
1916
|
-
* Get the hooked state style. ***Does not*** include
|
|
1917
|
-
* composition from an applied theme, or things from the underlying base
|
|
1918
|
-
* stylesheet; only the modifications applied by this machine.
|
|
1919
|
-
*
|
|
1920
|
-
* The hooked style is only applied to nodes which have a named hook in the
|
|
1921
|
-
* graph. Open hooks set through the external API aren't graphed, because
|
|
1922
|
-
* that would be literally every node.
|
|
1923
|
-
*
|
|
1924
|
-
* ```typescript
|
|
1925
|
-
* const light = sm`a -> b;`;
|
|
1926
|
-
* console.log(light.hooked_state_style);
|
|
1927
|
-
* // {}
|
|
1928
|
-
*
|
|
1929
|
-
* const light = sm`a -> b; hooked_state: { shape: circle; };`;
|
|
1930
|
-
* console.log(light.hooked_state_style);
|
|
1931
|
-
* // { shape: 'circle' }
|
|
1932
|
-
* ```
|
|
1933
|
-
*
|
|
1934
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1935
|
-
*
|
|
1936
|
-
*/
|
|
1937
|
-
get hooked_state_style() {
|
|
1938
|
-
return this._hooked_state_style;
|
|
1939
|
-
}
|
|
1940
|
-
/********
|
|
1941
|
-
*
|
|
1942
|
-
* Get the start state style. ***Does not*** include composition from an
|
|
1943
|
-
* applied theme, or things from the underlying base stylesheet; only the
|
|
1944
|
-
* modifications applied by this machine.
|
|
1945
|
-
*
|
|
1946
|
-
* Start states are defined by the directive `start_states`, or in absentia,
|
|
1947
|
-
* are the first mentioned state.
|
|
1948
|
-
*
|
|
1949
|
-
* ```typescript
|
|
1950
|
-
* const light = sm`a -> b;`;
|
|
1951
|
-
* console.log(light.start_state_style);
|
|
1952
|
-
* // {}
|
|
1953
|
-
*
|
|
1954
|
-
* const light = sm`a -> b; start_state: { shape: circle; };`;
|
|
1955
|
-
* console.log(light.start_state_style);
|
|
1956
|
-
* // { shape: 'circle' }
|
|
1957
|
-
* ```
|
|
1958
|
-
*
|
|
1959
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1960
|
-
*
|
|
1961
|
-
*/
|
|
1962
|
-
get start_state_style() {
|
|
1963
|
-
return this._start_state_style;
|
|
1964
|
-
}
|
|
1965
|
-
/********
|
|
1966
|
-
*
|
|
1967
|
-
* Get the end state style. ***Does not*** include
|
|
1968
|
-
* composition from an applied theme, or things from the underlying base
|
|
1969
|
-
* stylesheet; only the modifications applied by this machine.
|
|
1970
|
-
*
|
|
1971
|
-
* End states are defined in the directive `end_states`, and are distinct
|
|
1972
|
-
* from terminal states. End states are voluntary successful endpoints for a
|
|
1973
|
-
* process. Terminal states are states that cannot be exited. By example,
|
|
1974
|
-
* most error states are terminal states, but not end states. Also, since
|
|
1975
|
-
* some end states can be exited and are determined by hooks, such as
|
|
1976
|
-
* recursive or iterative nodes, there is such a thing as an end state that
|
|
1977
|
-
* is not a terminal state.
|
|
1978
|
-
*
|
|
1979
|
-
* ```typescript
|
|
1980
|
-
* const light = sm`a -> b;`;
|
|
1981
|
-
* console.log(light.standard_state_style);
|
|
1982
|
-
* // {}
|
|
1983
|
-
*
|
|
1984
|
-
* const light = sm`a -> b; end_state: { shape: circle; };`;
|
|
1985
|
-
* console.log(light.standard_state_style);
|
|
1986
|
-
* // { shape: 'circle' }
|
|
1987
|
-
* ```
|
|
1988
|
-
*
|
|
1989
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
1990
|
-
*
|
|
1991
|
-
*/
|
|
1992
|
-
get end_state_style() {
|
|
1993
|
-
return this._end_state_style;
|
|
1994
|
-
}
|
|
1995
|
-
/********
|
|
1996
|
-
*
|
|
1997
|
-
* Get the terminal state style. ***Does not*** include
|
|
1998
|
-
* composition from an applied theme, or things from the underlying base
|
|
1999
|
-
* stylesheet; only the modifications applied by this machine.
|
|
2000
|
-
*
|
|
2001
|
-
* Terminal state styles are automatically determined by the machine. Any
|
|
2002
|
-
* state without a valid exit transition is terminal.
|
|
2003
|
-
*
|
|
2004
|
-
* ```typescript
|
|
2005
|
-
* const light = sm`a -> b;`;
|
|
2006
|
-
* console.log(light.terminal_state_style);
|
|
2007
|
-
* // {}
|
|
2008
|
-
*
|
|
2009
|
-
* const light = sm`a -> b; terminal_state: { shape: circle; };`;
|
|
2010
|
-
* console.log(light.terminal_state_style);
|
|
2011
|
-
* // { shape: 'circle' }
|
|
2012
|
-
* ```
|
|
2013
|
-
*
|
|
2014
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2015
|
-
*
|
|
2016
|
-
*/
|
|
2017
|
-
get terminal_state_style() {
|
|
2018
|
-
return this._terminal_state_style;
|
|
2019
|
-
}
|
|
2020
|
-
/********
|
|
2021
|
-
*
|
|
2022
|
-
* Get the style for the active state. ***Does not*** include
|
|
2023
|
-
* composition from an applied theme, or things from the underlying base
|
|
2024
|
-
* stylesheet; only the modifications applied by this machine.
|
|
2025
|
-
*
|
|
2026
|
-
* ```typescript
|
|
2027
|
-
* const light = sm`a -> b;`;
|
|
2028
|
-
* console.log(light.active_state_style);
|
|
2029
|
-
* // {}
|
|
2030
|
-
*
|
|
2031
|
-
* const light = sm`a -> b; active_state: { shape: circle; };`;
|
|
2032
|
-
* console.log(light.active_state_style);
|
|
2033
|
-
* // { shape: 'circle' }
|
|
2034
|
-
* ```
|
|
2035
|
-
*
|
|
2036
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2037
|
-
*
|
|
2038
|
-
*/
|
|
2039
|
-
get active_state_style() {
|
|
2040
|
-
return this._active_state_style;
|
|
2041
|
-
}
|
|
2042
|
-
/*
|
|
2043
|
-
*/
|
|
2044
|
-
// TODO COMEBACK IMPLEMENTME FIXME
|
|
2045
|
-
// has_hooks(state: StateType): false {
|
|
2046
|
-
// return false;
|
|
2047
|
-
// }
|
|
2048
|
-
/********
|
|
2049
|
-
*
|
|
2050
|
-
* Gets the composite style for a specific node by individually imposing the
|
|
2051
|
-
* style layers on a given object, after determining which layers are
|
|
2052
|
-
* appropriate.
|
|
2053
|
-
*
|
|
2054
|
-
* The order of composition is base, then theme, then user content. Each
|
|
2055
|
-
* item in the stack will be composited independently. First, the base state
|
|
2056
|
-
* style, then the theme state style, then the user state style.
|
|
2057
|
-
*
|
|
2058
|
-
* After the three state styles, we'll composite the hooked styles; then the
|
|
2059
|
-
* terminal styles; then the start styles; then the end styles; finally, the
|
|
2060
|
-
* active styles. Remember, last wins.
|
|
2061
|
-
*
|
|
2062
|
-
* The base state style must exist. All other styles are optional.
|
|
2063
|
-
*
|
|
2064
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2065
|
-
*
|
|
2066
|
-
*/
|
|
2067
|
-
style_for(state) {
|
|
2068
|
-
// first look up the themes
|
|
2069
|
-
const themes = [];
|
|
2070
|
-
this._themes.forEach(th => {
|
|
2071
|
-
const theme_impl = theme_mapping.get(th);
|
|
2072
|
-
if (theme_impl !== undefined) {
|
|
2073
|
-
themes.push(theme_impl);
|
|
2074
|
-
}
|
|
2075
|
-
});
|
|
2076
|
-
// basic state style
|
|
2077
|
-
const layers = [base_theme.state];
|
|
2078
|
-
themes.reverse().map(theme => {
|
|
2079
|
-
if (theme.state) {
|
|
2080
|
-
layers.push(theme.state);
|
|
2081
|
-
}
|
|
2082
|
-
});
|
|
2083
|
-
if (this._state_style) {
|
|
2084
|
-
layers.push(this._state_style);
|
|
2085
|
-
}
|
|
2086
|
-
// hooked state style
|
|
2087
|
-
// if (this.has_hooks(state)) {
|
|
2088
|
-
// layers.push(base_theme.hooked);
|
|
2089
|
-
// themes.map(theme => {
|
|
2090
|
-
// if (theme.hooked) { layers.push(theme.hooked); }
|
|
2091
|
-
// });
|
|
2092
|
-
// if (this._hooked_state_style) { layers.push(this._hooked_state_style); }
|
|
2093
|
-
// }
|
|
2094
|
-
// terminal state style
|
|
2095
|
-
if (this.state_is_terminal(state)) {
|
|
2096
|
-
layers.push(base_theme.terminal);
|
|
2097
|
-
themes.map(theme => {
|
|
2098
|
-
if (theme.terminal) {
|
|
2099
|
-
layers.push(theme.terminal);
|
|
2100
|
-
}
|
|
2101
|
-
});
|
|
2102
|
-
if (this._terminal_state_style) {
|
|
2103
|
-
layers.push(this._terminal_state_style);
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
// start state style
|
|
2107
|
-
if (this.is_start_state(state)) {
|
|
2108
|
-
layers.push(base_theme.start);
|
|
2109
|
-
themes.map(theme => {
|
|
2110
|
-
if (theme.start) {
|
|
2111
|
-
layers.push(theme.start);
|
|
2112
|
-
}
|
|
2113
|
-
});
|
|
2114
|
-
if (this._start_state_style) {
|
|
2115
|
-
layers.push(this._start_state_style);
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
// end state style
|
|
2119
|
-
if (this.is_end_state(state)) {
|
|
2120
|
-
layers.push(base_theme.end);
|
|
2121
|
-
themes.map(theme => {
|
|
2122
|
-
if (theme.end) {
|
|
2123
|
-
layers.push(theme.end);
|
|
2124
|
-
}
|
|
2125
|
-
});
|
|
2126
|
-
if (this._end_state_style) {
|
|
2127
|
-
layers.push(this._end_state_style);
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
// active state style
|
|
2131
|
-
if (this.state() === state) {
|
|
2132
|
-
layers.push(base_theme.active);
|
|
2133
|
-
themes.map(theme => {
|
|
2134
|
-
if (theme.active) {
|
|
2135
|
-
layers.push(theme.active);
|
|
2136
|
-
}
|
|
2137
|
-
});
|
|
2138
|
-
if (this._active_state_style) {
|
|
2139
|
-
layers.push(this._active_state_style);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
const individual_style = {}, decl = this._state_declarations.get(state);
|
|
2143
|
-
individual_style.color = decl === null || decl === void 0 ? void 0 : decl.color;
|
|
2144
|
-
individual_style.textColor = decl === null || decl === void 0 ? void 0 : decl.textColor;
|
|
2145
|
-
individual_style.borderColor = decl === null || decl === void 0 ? void 0 : decl.borderColor;
|
|
2146
|
-
individual_style.backgroundColor = decl === null || decl === void 0 ? void 0 : decl.backgroundColor;
|
|
2147
|
-
individual_style.lineStyle = decl === null || decl === void 0 ? void 0 : decl.lineStyle;
|
|
2148
|
-
individual_style.corners = decl === null || decl === void 0 ? void 0 : decl.corners;
|
|
2149
|
-
individual_style.shape = decl === null || decl === void 0 ? void 0 : decl.shape;
|
|
2150
|
-
layers.push(individual_style);
|
|
2151
|
-
return layers.reduce((acc, cur) => {
|
|
2152
|
-
const composite_state = acc;
|
|
2153
|
-
Object.keys(cur).forEach(key => { var _a; return composite_state[key] = (_a = cur[key]) !== null && _a !== void 0 ? _a : composite_state[key]; });
|
|
2154
|
-
return composite_state;
|
|
2155
|
-
}, {});
|
|
2156
|
-
}
|
|
2157
|
-
/********
|
|
2158
|
-
*
|
|
2159
|
-
* Instruct the machine to complete an action. Synonym for {@link action}.
|
|
2160
|
-
*
|
|
2161
|
-
* ```typescript
|
|
2162
|
-
* const light = sm`
|
|
2163
|
-
* off 'start' -> red;
|
|
2164
|
-
* red 'next' -> green 'next' -> yellow 'next' -> red;
|
|
2165
|
-
* [red yellow green] 'shutdown' ~> off;
|
|
2166
|
-
* `;
|
|
2167
|
-
*
|
|
2168
|
-
* light.state(); // 'off'
|
|
2169
|
-
* light.do('start'); // true
|
|
2170
|
-
* light.state(); // 'red'
|
|
2171
|
-
* light.do('next'); // true
|
|
2172
|
-
* light.state(); // 'green'
|
|
2173
|
-
* light.do('next'); // true
|
|
2174
|
-
* light.state(); // 'yellow'
|
|
2175
|
-
* light.do('dance'); // !! false - no such action
|
|
2176
|
-
* light.state(); // 'yellow'
|
|
2177
|
-
* light.do('start'); // !! false - yellow does not have the action start
|
|
2178
|
-
* light.state(); // 'yellow'
|
|
2179
|
-
* ```
|
|
2180
|
-
*
|
|
2181
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2182
|
-
*
|
|
2183
|
-
* @param actionName The action to engage
|
|
2184
|
-
*
|
|
2185
|
-
* @param newData The data change to insert during the action
|
|
2186
|
-
*
|
|
2187
|
-
*/
|
|
2188
|
-
do(actionName, newData) {
|
|
2189
|
-
return this.transition_impl(actionName, newData, false, true);
|
|
2190
|
-
}
|
|
2191
|
-
/********
|
|
2192
|
-
*
|
|
2193
|
-
* Instruct the machine to complete a transition. Synonym for {@link go}.
|
|
2194
|
-
*
|
|
2195
|
-
* ```typescript
|
|
2196
|
-
* const light = sm`
|
|
2197
|
-
* off 'start' -> red;
|
|
2198
|
-
* red 'next' -> green 'next' -> yellow 'next' -> red;
|
|
2199
|
-
* [red yellow green] 'shutdown' ~> off;
|
|
2200
|
-
* `;
|
|
2201
|
-
*
|
|
2202
|
-
* light.state(); // 'off'
|
|
2203
|
-
* light.go('red'); // true
|
|
2204
|
-
* light.state(); // 'red'
|
|
2205
|
-
* light.go('green'); // true
|
|
2206
|
-
* light.state(); // 'green'
|
|
2207
|
-
* light.go('blue'); // !! false - no such state
|
|
2208
|
-
* light.state(); // 'green'
|
|
2209
|
-
* light.go('red'); // !! false - green may not go directly to red, only to yellow
|
|
2210
|
-
* light.state(); // 'green'
|
|
2211
|
-
* ```
|
|
2212
|
-
*
|
|
2213
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2214
|
-
*
|
|
2215
|
-
* @param newState The state to switch to
|
|
2216
|
-
*
|
|
2217
|
-
* @param newData The data change to insert during the transition
|
|
2218
|
-
*
|
|
2219
|
-
*/
|
|
2220
|
-
transition(newState, newData) {
|
|
2221
|
-
return this.transition_impl(newState, newData, false, false);
|
|
2222
|
-
}
|
|
2223
|
-
/********
|
|
2224
|
-
*
|
|
2225
|
-
* Instruct the machine to complete a transition. Synonym for {@link transition}.
|
|
2226
|
-
*
|
|
2227
|
-
* ```typescript
|
|
2228
|
-
* const light = sm`red -> green -> yellow -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
|
|
2229
|
-
*
|
|
2230
|
-
* light.state(); // 'red'
|
|
2231
|
-
* light.go('green'); // true
|
|
2232
|
-
* light.state(); // 'green'
|
|
2233
|
-
* ```
|
|
2234
|
-
*
|
|
2235
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2236
|
-
*
|
|
2237
|
-
* @param newState The state to switch to
|
|
2238
|
-
*
|
|
2239
|
-
* @param newData The data change to insert during the transition
|
|
2240
|
-
*
|
|
2241
|
-
*/
|
|
2242
|
-
go(newState, newData) {
|
|
2243
|
-
return this.transition_impl(newState, newData, false, false);
|
|
2244
|
-
}
|
|
2245
|
-
/********
|
|
2246
|
-
*
|
|
2247
|
-
* Instruct the machine to complete a forced transition (which will reject if
|
|
2248
|
-
* called with a normal {@link transition} call.)
|
|
2249
|
-
*
|
|
2250
|
-
* ```typescript
|
|
2251
|
-
* const light = sm`red -> green -> yellow -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
|
|
2252
|
-
*
|
|
2253
|
-
* light.state(); // 'red'
|
|
2254
|
-
* light.transition('off'); // false
|
|
2255
|
-
* light.state(); // 'red'
|
|
2256
|
-
* light.force_transition('off'); // true
|
|
2257
|
-
* light.state(); // 'off'
|
|
2258
|
-
* ```
|
|
2259
|
-
*
|
|
2260
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2261
|
-
*
|
|
2262
|
-
* @param newState The state to switch to
|
|
2263
|
-
*
|
|
2264
|
-
* @param newData The data change to insert during the transition
|
|
2265
|
-
*
|
|
2266
|
-
*/
|
|
2267
|
-
force_transition(newState, newData) {
|
|
2268
|
-
return this.transition_impl(newState, newData, true, false);
|
|
2269
|
-
}
|
|
2270
|
-
current_action_for(action) {
|
|
2271
|
-
const action_base = this._actions.get(action);
|
|
2272
|
-
return action_base
|
|
2273
|
-
? action_base.get(this.state())
|
|
2274
|
-
: undefined;
|
|
2275
|
-
}
|
|
2276
|
-
current_action_edge_for(action) {
|
|
2277
|
-
const idx = this.current_action_for(action);
|
|
2278
|
-
if ((idx === undefined) || (idx === null)) {
|
|
2279
|
-
throw new JssmError(this, `No such action ${JSON.stringify(action)}`);
|
|
2280
|
-
}
|
|
2281
|
-
return this._edges[idx];
|
|
2282
|
-
}
|
|
2283
|
-
valid_action(action, _newData) {
|
|
2284
|
-
// todo whargarbl implement data stuff
|
|
2285
|
-
// todo major incomplete whargarbl comeback
|
|
2286
|
-
return this.current_action_for(action) !== undefined;
|
|
2287
|
-
}
|
|
2288
|
-
valid_transition(newState, _newData) {
|
|
2289
|
-
// todo whargarbl implement data stuff
|
|
2290
|
-
// todo major incomplete whargarbl comeback
|
|
2291
|
-
const transition_for = this.lookup_transition_for(this.state(), newState);
|
|
2292
|
-
if (!(transition_for)) {
|
|
2293
|
-
return false;
|
|
2294
|
-
}
|
|
2295
|
-
if (transition_for.forced_only) {
|
|
2296
|
-
return false;
|
|
2297
|
-
}
|
|
2298
|
-
return true;
|
|
2299
|
-
}
|
|
2300
|
-
valid_force_transition(newState, _newData) {
|
|
2301
|
-
// todo whargarbl implement data stuff
|
|
2302
|
-
// todo major incomplete whargarbl comeback
|
|
2303
|
-
return (this.lookup_transition_for(this.state(), newState) !== undefined);
|
|
2304
|
-
}
|
|
2305
|
-
instance_name() {
|
|
2306
|
-
return this._instance_name;
|
|
2307
|
-
}
|
|
2308
|
-
get creation_date() {
|
|
2309
|
-
return new Date(Math.floor(this.creation_timestamp));
|
|
2310
|
-
}
|
|
2311
|
-
get creation_timestamp() {
|
|
2312
|
-
return this._created;
|
|
2313
|
-
}
|
|
2314
|
-
get create_start_time() {
|
|
2315
|
-
return this._create_started;
|
|
2316
|
-
}
|
|
2317
|
-
set_state_timeout(next_state, after_time) {
|
|
2318
|
-
if (this._timeout_handle !== undefined) {
|
|
2319
|
-
throw new JssmError(this, `Asked to set a state timeout to ${next_state}:${after_time}, but already timing out to ${this._timeout_target}:${this._timeout_target_time}`);
|
|
2320
|
-
}
|
|
2321
|
-
this._timeout_handle = this._timeout_source(
|
|
2322
|
-
// it seems like istanbul can't see this line being followed, even though it is, actively
|
|
2323
|
-
// this is enforced by the "after mapping runs normally with very short time" tests in after_mapping.spec
|
|
2324
|
-
// we'll mark it no-check so that our coverage numbers aren't wrecked
|
|
2325
|
-
/* istanbul ignore next */
|
|
2326
|
-
() => {
|
|
2327
|
-
this.clear_state_timeout();
|
|
2328
|
-
if (this._has_after_hooks) {
|
|
2329
|
-
const ah = this._after_hooks.get(this.state());
|
|
2330
|
-
if (ah !== undefined) {
|
|
2331
|
-
ah({ data: this._data, next_data: this._data });
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
this.go(next_state);
|
|
2335
|
-
}, after_time);
|
|
2336
|
-
this._timeout_target = next_state;
|
|
2337
|
-
this._timeout_target_time = after_time;
|
|
2338
|
-
}
|
|
2339
|
-
clear_state_timeout() {
|
|
2340
|
-
if (this._timeout_handle === undefined) {
|
|
2341
|
-
return; // calling with no timeout is a no-op, means it can be called glad-handedly
|
|
2342
|
-
}
|
|
2343
|
-
this._clear_timeout_source(this._timeout_handle);
|
|
2344
|
-
this._timeout_handle = undefined;
|
|
2345
|
-
this._timeout_target = undefined;
|
|
2346
|
-
this._timeout_target_time = undefined;
|
|
2347
|
-
}
|
|
2348
|
-
state_timeout_for(which_state) {
|
|
2349
|
-
return this._after_mapping.get(which_state);
|
|
2350
|
-
}
|
|
2351
|
-
current_state_timeout() {
|
|
2352
|
-
return (this._timeout_target !== undefined)
|
|
2353
|
-
? [this._timeout_target, this._timeout_target_time]
|
|
2354
|
-
: undefined;
|
|
2355
|
-
}
|
|
2356
|
-
/* eslint-disable no-use-before-define */
|
|
2357
|
-
/* eslint-disable class-methods-use-this */
|
|
2358
|
-
sm(template_strings, ...remainder /* , arguments */) {
|
|
2359
|
-
return sm(template_strings, ...remainder);
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
/*********
|
|
2363
|
-
*
|
|
2364
|
-
* Create a state machine from a template string. This is one of the two main
|
|
2365
|
-
* paths for working with JSSM, alongside {@link from}.
|
|
2366
|
-
*
|
|
2367
|
-
* Use this method when you want to work directly and conveniently with a
|
|
2368
|
-
* constant template expression. Use `.from` when you want to pull from
|
|
2369
|
-
* dynamic strings.
|
|
2370
|
-
*
|
|
2371
|
-
*
|
|
2372
|
-
* ```typescript
|
|
2373
|
-
* import * as jssm from 'jssm';
|
|
2374
|
-
*
|
|
2375
|
-
* const lswitch = jssm.from('on <=> off;');
|
|
2376
|
-
* ```
|
|
2377
|
-
*
|
|
2378
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2379
|
-
*
|
|
2380
|
-
* @param template_strings The assembled code
|
|
2381
|
-
*
|
|
2382
|
-
* @param remainder The mechanic for template argument insertion
|
|
2383
|
-
*
|
|
2384
|
-
*/
|
|
2385
|
-
function sm(template_strings, ...remainder /* , arguments */) {
|
|
2386
|
-
// foo`a${1}b${2}c` will come in as (['a','b','c'],1,2)
|
|
2387
|
-
// this includes when a and c are empty strings
|
|
2388
|
-
// therefore template_strings will always have one more el than template_args
|
|
2389
|
-
// therefore map the smaller container and toss the last one on on the way out
|
|
2390
|
-
return new Machine(make(template_strings.reduce(
|
|
2391
|
-
// in general avoiding `arguments` is smart. however with the template
|
|
2392
|
-
// string notation, as designed, it's not really worth the hassle
|
|
2393
|
-
/* eslint-disable prefer-rest-params */
|
|
2394
|
-
(acc, val, idx) => `${acc}${remainder[idx - 1]}${val}` // arguments[0] is never loaded, so args doesn't need to be gated
|
|
2395
|
-
/* eslint-enable prefer-rest-params */
|
|
2396
|
-
)));
|
|
2397
|
-
}
|
|
2398
|
-
/*********
|
|
2399
|
-
*
|
|
2400
|
-
* Create a state machine from an implementation string. This is one of the
|
|
2401
|
-
* two main paths for working with JSSM, alongside {@link sm}.
|
|
2402
|
-
*
|
|
2403
|
-
* Use this method when you want to conveniently pull a state machine from a
|
|
2404
|
-
* string dynamically. Use operator `sm` when you just want to work with a
|
|
2405
|
-
* template expression.
|
|
2406
|
-
*
|
|
2407
|
-
* ```typescript
|
|
2408
|
-
* import * as jssm from 'jssm';
|
|
2409
|
-
*
|
|
2410
|
-
* const lswitch = jssm.from('on <=> off;');
|
|
2411
|
-
* ```
|
|
2412
|
-
*
|
|
2413
|
-
* @typeparam mDT The type of the machine data member; usually omitted
|
|
2414
|
-
*
|
|
2415
|
-
* @param MachineAsString The FSL code to evaluate
|
|
2416
|
-
*
|
|
2417
|
-
* @param ExtraConstructorFields Extra non-code configuration to pass at creation time
|
|
2418
|
-
*
|
|
2419
|
-
*/
|
|
2420
|
-
function from(MachineAsString, ExtraConstructorFields) {
|
|
2421
|
-
const to_decorate = make(MachineAsString);
|
|
2422
|
-
if (ExtraConstructorFields !== undefined) {
|
|
2423
|
-
Object.keys(ExtraConstructorFields).map(key => {
|
|
2424
|
-
if (key === 'allows_override') {
|
|
2425
|
-
to_decorate['config_allows_override'] = ExtraConstructorFields['allows_override'];
|
|
2426
|
-
}
|
|
2427
|
-
else {
|
|
2428
|
-
to_decorate[key] = ExtraConstructorFields[key];
|
|
2429
|
-
}
|
|
2430
|
-
});
|
|
2431
|
-
}
|
|
2432
|
-
return new Machine(to_decorate);
|
|
2433
|
-
}
|
|
2434
|
-
function is_hook_complex_result(hr) {
|
|
2435
|
-
if (typeof hr === 'object') {
|
|
2436
|
-
if (typeof hr.pass === 'boolean') {
|
|
2437
|
-
return true;
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
return false;
|
|
2441
|
-
}
|
|
2442
|
-
function is_hook_rejection(hr) {
|
|
2443
|
-
if (hr === true) {
|
|
2444
|
-
return false;
|
|
2445
|
-
}
|
|
2446
|
-
if (hr === undefined) {
|
|
2447
|
-
return false;
|
|
2448
|
-
}
|
|
2449
|
-
if (hr === false) {
|
|
2450
|
-
return true;
|
|
2451
|
-
}
|
|
2452
|
-
if (is_hook_complex_result(hr)) {
|
|
2453
|
-
return (!(hr.pass));
|
|
2454
|
-
}
|
|
2455
|
-
throw new TypeError('unknown hook rejection type result');
|
|
2456
|
-
}
|
|
2457
|
-
function abstract_hook_step(maybe_hook, hook_args) {
|
|
2458
|
-
if (maybe_hook !== undefined) {
|
|
2459
|
-
const result = maybe_hook(hook_args);
|
|
2460
|
-
if (result === undefined) {
|
|
2461
|
-
return { pass: true };
|
|
2462
|
-
}
|
|
2463
|
-
if (result === true) {
|
|
2464
|
-
return { pass: true };
|
|
2465
|
-
}
|
|
2466
|
-
if (result === false) {
|
|
2467
|
-
return { pass: false };
|
|
2468
|
-
}
|
|
2469
|
-
if (is_hook_complex_result(result)) {
|
|
2470
|
-
return result;
|
|
2471
|
-
}
|
|
2472
|
-
throw new TypeError(`Unknown hook result type ${result}`);
|
|
2473
|
-
}
|
|
2474
|
-
else {
|
|
2475
|
-
return { pass: true };
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
function deserialize(machine_string, ser) {
|
|
2479
|
-
const machine = from(machine_string, { data: ser.data, history: ser.history_capacity });
|
|
2480
|
-
machine._state = ser.state;
|
|
2481
|
-
ser.history.forEach(history_item => machine._history.push(history_item));
|
|
2482
|
-
return machine;
|
|
2483
|
-
}
|
|
2484
|
-
export { version, build_time, transfer_state_properties, Machine, deserialize, make, wrap_parse as parse, compile, sm, from, arrow_direction, arrow_left_kind, arrow_right_kind,
|
|
2485
|
-
// WHARGARBL TODO these should be exported to a utility library
|
|
2486
|
-
seq, unique, find_repeated, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key, sleep, constants, shapes, gviz_shapes, named_colors, is_hook_rejection, is_hook_complex_result, abstract_hook_step, state_style_condense, FslDirections
|
|
2487
|
-
// FslThemes
|
|
2488
|
-
};
|