jssm 5.112.3 → 5.113.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/deno/README.md +347 -0
- package/dist/deno/jssm.js +1 -0
- package/dist/{es6 → deno}/jssm_constants.d.ts +5 -0
- package/dist/{es6 → deno}/jssm_types.d.ts +298 -5
- package/dist/jssm.es5.cjs +1 -1
- package/dist/jssm.es5.iife.js +1 -1
- package/dist/jssm.es6.mjs +1 -1
- package/dist/jssm_viz.cjs +1 -1
- package/dist/jssm_viz.iife.cjs +1 -1
- package/dist/jssm_viz.mjs +1 -1
- package/jssm.es5.d.cts +241 -2
- package/jssm.es6.d.ts +241 -2
- package/jssm_viz.es5.d.cts +216 -2
- package/jssm_viz.es6.d.ts +216 -2
- package/package.json +18 -2
- package/.clocignore +0 -1
- package/.codeclimate.yml +0 -22
- package/.editorconfig +0 -12
- package/.eslintrc +0 -20
- package/.gitattributes +0 -17
- package/.log-progress.json +0 -9
- package/.nycrc +0 -6
- package/.travis.yml +0 -9
- package/CHANGELOG.md +0 -263
- package/CLAUDE.md +0 -11
- package/dist/es6/fsl_parser.js +0 -1
- package/dist/es6/jssm.js +0 -3320
- package/dist/es6/jssm_arrow.js +0 -211
- package/dist/es6/jssm_compiler.js +0 -380
- package/dist/es6/jssm_constants.js +0 -121
- package/dist/es6/jssm_error.js +0 -47
- package/dist/es6/jssm_theme.js +0 -24
- package/dist/es6/jssm_types.js +0 -3
- package/dist/es6/jssm_util.js +0 -337
- package/dist/es6/jssm_viz.js +0 -560
- package/dist/es6/jssm_viz_colors.js +0 -63
- 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.nonmin.cjs +0 -24506
- package/dist/jssm.es6.nonmin.cjs +0 -24473
- package/dist/jssm_viz.es5.iife.nonmin.cjs +0 -24679
- package/dist/jssm_viz.es5.nonmin.cjs +0 -24674
- package/dist/jssm_viz.es6.nonmin.cjs +0 -24661
- package/jest-dragon.config.cjs +0 -36
- package/jest-spec.config.cjs +0 -36
- package/jest-stoch.config.cjs +0 -36
- package/jest-unicode.config.cjs +0 -36
- package/log-progress.data.json +0 -28
- package/rollup.config.deno.js +0 -44
- package/rollup.config.es5.js +0 -52
- package/rollup.config.es6.js +0 -55
- package/rollup.config.viz.es5.js +0 -46
- package/rollup.config.viz.es6.js +0 -46
- package/rollup.config.viz.iife.js +0 -36
- package/tutorial_learn_testing.md +0 -168
- package/typedoc-options.cjs +0 -69
- /package/dist/{es6 → deno}/fsl_parser.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_arrow.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_compiler.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_error.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_theme.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_util.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_viz.d.ts +0 -0
- /package/dist/{es6 → deno}/jssm_viz_colors.d.ts +0 -0
- /package/dist/{es6 → deno}/version.d.ts +0 -0
package/dist/es6/jssm_viz.js
DELETED
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
import * as jssm from './jssm';
|
|
2
|
-
import { JssmError } from './jssm_error';
|
|
3
|
-
import { default_viz_colors } from './jssm_viz_colors';
|
|
4
|
-
import { version, build_time } from './version';
|
|
5
|
-
/**
|
|
6
|
-
* Cached resolved viz.js instance. Populated on first call to
|
|
7
|
-
* {@link get_viz}; later calls reuse it directly. Internal.
|
|
8
|
-
*/
|
|
9
|
-
let viz_instance = null;
|
|
10
|
-
/**
|
|
11
|
-
* DOM parser injected via {@link configure} for environments without a
|
|
12
|
-
* global `DOMParser` (e.g. Node). Internal.
|
|
13
|
-
*/
|
|
14
|
-
let injected_dom_parser = null;
|
|
15
|
-
/**
|
|
16
|
-
* Returns a cached @viz-js/viz instance, lazily instantiated on first call.
|
|
17
|
-
* Internal helper for the rendering functions.
|
|
18
|
-
*
|
|
19
|
-
* @internal
|
|
20
|
-
*/
|
|
21
|
-
async function get_viz() {
|
|
22
|
-
if (viz_instance === null) {
|
|
23
|
-
const mod = await import('@viz-js/viz');
|
|
24
|
-
viz_instance = await mod.instance();
|
|
25
|
-
}
|
|
26
|
-
return viz_instance;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Inject runtime configuration for jssm/viz. Currently only accepts a
|
|
30
|
-
* custom `DOMParser` constructor for use by `*_svg_element` functions in
|
|
31
|
-
* environments that do not provide one globally (e.g. Node + jsdom).
|
|
32
|
-
*
|
|
33
|
-
* Idempotent — last call wins. No-op if called with no recognized keys.
|
|
34
|
-
*
|
|
35
|
-
* ```typescript
|
|
36
|
-
* // Node, with jsdom:
|
|
37
|
-
* import { JSDOM } from 'jsdom';
|
|
38
|
-
* import { configure, fsl_to_svg_element } from 'jssm/viz';
|
|
39
|
-
*
|
|
40
|
-
* configure({ DOMParser: new JSDOM().window.DOMParser });
|
|
41
|
-
* const el = await fsl_to_svg_element('a -> b;');
|
|
42
|
-
* ```
|
|
43
|
-
*
|
|
44
|
-
* @param opts Configuration overrides.
|
|
45
|
-
* @param opts.DOMParser Constructor compatible with the WHATWG `DOMParser`
|
|
46
|
-
* interface. Used as a fallback when `globalThis.DOMParser` is undefined.
|
|
47
|
-
*
|
|
48
|
-
* @throws {JssmError} if `DOMParser` is provided and is not a constructor.
|
|
49
|
-
*/
|
|
50
|
-
function configure(opts) {
|
|
51
|
-
if (opts.DOMParser !== undefined) {
|
|
52
|
-
if (typeof opts.DOMParser !== 'function') {
|
|
53
|
-
throw new JssmError(undefined, 'jssm/viz: configure({ DOMParser }) — value must be a constructor');
|
|
54
|
-
}
|
|
55
|
-
injected_dom_parser = opts.DOMParser;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Look up a color from the default viz palette by key, returning empty
|
|
60
|
-
* string if the key is unknown (so it disappears in feature concatenation).
|
|
61
|
-
*
|
|
62
|
-
* @internal
|
|
63
|
-
*/
|
|
64
|
-
function vc(col) {
|
|
65
|
-
var _a;
|
|
66
|
-
return (_a = default_viz_colors[col]) !== null && _a !== void 0 ? _a : '';
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Build a graphviz-safe node identifier for a state, by index. Accepts
|
|
70
|
-
* either a `string[]` (used historically; O(n) per call) or a
|
|
71
|
-
* precomputed `Map<state, index>` (used by rendering hot paths; O(1)
|
|
72
|
-
* per call). The map form is used during dot generation; the array
|
|
73
|
-
* form is retained for direct test access via `_test`.
|
|
74
|
-
*
|
|
75
|
-
* @internal
|
|
76
|
-
*/
|
|
77
|
-
function node_of(state, state_index) {
|
|
78
|
-
return Array.isArray(state_index)
|
|
79
|
-
? `n${state_index.indexOf(state)}`
|
|
80
|
-
: `n${state_index.get(state)}`;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Convert an 8-channel hex color (`#RRGGBBAA`) to a 6-channel hex color
|
|
84
|
-
* (`#RRGGBB`), discarding the alpha channel. Throws if the input is not
|
|
85
|
-
* a 9-character `#`-prefixed string.
|
|
86
|
-
*
|
|
87
|
-
* Graphviz dot does not support alpha; this is a lossy projection.
|
|
88
|
-
*
|
|
89
|
-
* @internal
|
|
90
|
-
*/
|
|
91
|
-
function color8to6(color8) {
|
|
92
|
-
if ((color8.length !== 9) || (color8[0] !== '#')) {
|
|
93
|
-
throw new JssmError(undefined, `not a color8: ${color8}`);
|
|
94
|
-
}
|
|
95
|
-
return `#${color8.substring(1, 7)}`;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Variant of {@link color8to6} that passes `undefined` through.
|
|
99
|
-
*
|
|
100
|
-
* @internal
|
|
101
|
-
*/
|
|
102
|
-
function u_color8to6(color8) {
|
|
103
|
-
return color8 === undefined ? undefined : color8to6(color8);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Read the graphviz shape for a state through {@link jssm.Machine.style_for},
|
|
107
|
-
* so theme-supplied shapes are honoured along with per-state declarations.
|
|
108
|
-
* Returns `undefined` if neither a theme nor a state declaration supplies a
|
|
109
|
-
* shape.
|
|
110
|
-
*
|
|
111
|
-
* @internal
|
|
112
|
-
*/
|
|
113
|
-
function shape_for_state(u_jssm, state) {
|
|
114
|
-
return u_jssm.style_for(state).shape;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Read the image filename for a state through {@link jssm.Machine.style_for},
|
|
118
|
-
* so theme-supplied images are honoured along with per-state declarations.
|
|
119
|
-
* Returns `undefined` if neither a theme nor a state declaration supplies an
|
|
120
|
-
* image.
|
|
121
|
-
*
|
|
122
|
-
* @internal
|
|
123
|
-
*/
|
|
124
|
-
function image_for_state(u_jssm, state) {
|
|
125
|
-
return u_jssm.style_for(state).image;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Compose a graphviz `style` string from a pre-computed
|
|
129
|
-
* {@link jssm.JssmStateConfig}, combining its `corners` and `lineStyle`
|
|
130
|
-
* fields. Returns either the empty string (when neither field is set
|
|
131
|
-
* anywhere — state declaration nor theme) or a `corners,line,filled`-shape
|
|
132
|
-
* string.
|
|
133
|
-
*
|
|
134
|
-
* Production callers should pass the result of `u_jssm.style_for(state)`
|
|
135
|
-
* so theme-supplied values are honoured uniformly with the rest of the
|
|
136
|
-
* rendering pipeline.
|
|
137
|
-
*
|
|
138
|
-
* @internal
|
|
139
|
-
*/
|
|
140
|
-
function compose_style_string(style) {
|
|
141
|
-
var _a, _b;
|
|
142
|
-
if (style.corners === undefined && style.lineStyle === undefined) {
|
|
143
|
-
return '';
|
|
144
|
-
}
|
|
145
|
-
const corners_map = { rounded: 'rounded', lined: 'diagonals', regular: 'regular' };
|
|
146
|
-
const lines_map = { dashed: 'dashed', dotted: 'dotted', solid: 'solid' };
|
|
147
|
-
const corners = corners_map[(_a = style.corners) !== null && _a !== void 0 ? _a : 'regular'];
|
|
148
|
-
const lines = lines_map[(_b = style.lineStyle) !== null && _b !== void 0 ? _b : 'solid'];
|
|
149
|
-
return `${corners},${lines},filled`;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Compose a graphviz `style` string for a state by looking up its merged
|
|
153
|
-
* style via {@link jssm.Machine.style_for}, then delegating to
|
|
154
|
-
* {@link compose_style_string}. Theme-supplied `corners` and `lineStyle`
|
|
155
|
-
* are honoured along with per-state declarations.
|
|
156
|
-
*
|
|
157
|
-
* @internal
|
|
158
|
-
*/
|
|
159
|
-
function style_for_state(u_jssm, state) {
|
|
160
|
-
return compose_style_string(u_jssm.style_for(state));
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Convert an FSL flow direction (`up`/`right`/`down`/`left`) to a graphviz
|
|
164
|
-
* `rankdir=` declaration line. Throws on unknown input.
|
|
165
|
-
*
|
|
166
|
-
* @internal
|
|
167
|
-
*/
|
|
168
|
-
function flow_direction_to_rankdir(flow_direction) {
|
|
169
|
-
switch (flow_direction) {
|
|
170
|
-
case 'up': return 'rankdir=BT;';
|
|
171
|
-
case 'right': return 'rankdir=LR;';
|
|
172
|
-
case 'down': return 'rankdir=TB;';
|
|
173
|
-
case 'left': return 'rankdir=RL;';
|
|
174
|
-
default: throw new JssmError(undefined, `unknown flow direction '${flow_direction}'`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Build the graphviz `digraph G { ... }` envelope from rendered fragments.
|
|
179
|
-
*
|
|
180
|
-
* @internal
|
|
181
|
-
*/
|
|
182
|
-
function dot_template(rank_dir, graph_bg_color, nodes, edges, arranges, preamble = '') {
|
|
183
|
-
return `digraph G {
|
|
184
|
-
${preamble}
|
|
185
|
-
|
|
186
|
-
${rank_dir}
|
|
187
|
-
fontname="Open Sans";
|
|
188
|
-
style=filled;
|
|
189
|
-
bgcolor="${graph_bg_color}";
|
|
190
|
-
node [fontsize=14; shape=box; style=filled; fillcolor=white; fontname="Times New Roman"];
|
|
191
|
-
edge [fontsize=6; fontname="Open Sans"];
|
|
192
|
-
|
|
193
|
-
${nodes}
|
|
194
|
-
|
|
195
|
-
${edges}
|
|
196
|
-
|
|
197
|
-
${arranges}
|
|
198
|
-
}`;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Compute state-kind classification for every state once per render.
|
|
202
|
-
* Replaces per-edge calls to `state_is_complete` / `state_is_terminal`
|
|
203
|
-
* (the latter rebuilds a fresh `list_exits` array each call) with O(1)
|
|
204
|
-
* Map lookups. Significant win on machines with many edges.
|
|
205
|
-
*
|
|
206
|
-
* The conjunction (`final` = complete AND terminal) is computed directly
|
|
207
|
-
* rather than via `state_is_final`, which returns the disjunction
|
|
208
|
-
* (terminal OR complete) and would collapse the three named buckets
|
|
209
|
-
* into two — see the type docstring above.
|
|
210
|
-
*
|
|
211
|
-
* @internal
|
|
212
|
-
*/
|
|
213
|
-
function classify_states(u_jssm, l_states) {
|
|
214
|
-
const kinds = new Map();
|
|
215
|
-
for (const s of l_states) {
|
|
216
|
-
const is_complete = u_jssm.state_is_complete(s);
|
|
217
|
-
const is_terminal = u_jssm.state_is_terminal(s);
|
|
218
|
-
if (is_complete && is_terminal) {
|
|
219
|
-
kinds.set(s, 'final');
|
|
220
|
-
}
|
|
221
|
-
else if (is_complete) {
|
|
222
|
-
kinds.set(s, 'complete');
|
|
223
|
-
}
|
|
224
|
-
else if (is_terminal) {
|
|
225
|
-
kinds.set(s, 'terminal');
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
kinds.set(s, 'base');
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return kinds;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Pick the default fill color for a state, by state-kind precedence
|
|
235
|
-
* (final > complete > terminal > none). Returns empty string if no
|
|
236
|
-
* kind applies.
|
|
237
|
-
*
|
|
238
|
-
* @internal
|
|
239
|
-
*/
|
|
240
|
-
function default_fillcolor_for(kind) {
|
|
241
|
-
switch (kind) {
|
|
242
|
-
case 'final': return vc('fill_final');
|
|
243
|
-
case 'complete': return vc('fill_complete');
|
|
244
|
-
case 'terminal': return vc('fill_terminal');
|
|
245
|
-
default: return '';
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Render the node-feature list for one machine, one node per state.
|
|
250
|
-
* All style reads route through {@link jssm.Machine.style_for} so that
|
|
251
|
-
* theme-supplied values (corners, lineStyle, image, shape, colours) are
|
|
252
|
-
* honoured uniformly. A single `style_for` call per state — downstream
|
|
253
|
-
* helpers operate on the cached result rather than re-querying.
|
|
254
|
-
*
|
|
255
|
-
* @internal
|
|
256
|
-
*/
|
|
257
|
-
function states_to_nodes_string(u_jssm, l_states, state_index, state_kinds) {
|
|
258
|
-
return l_states.map((s) => {
|
|
259
|
-
var _a;
|
|
260
|
-
const style = u_jssm.style_for(s);
|
|
261
|
-
const fillcolor = style.backgroundColor || default_fillcolor_for((_a = state_kinds.get(s)) !== null && _a !== void 0 ? _a : 'base');
|
|
262
|
-
const features = [
|
|
263
|
-
['label', u_jssm.display_text(s)],
|
|
264
|
-
['shape', style.shape || ''],
|
|
265
|
-
['color', style.borderColor || ''],
|
|
266
|
-
['style', compose_style_string(style)],
|
|
267
|
-
['fontcolor', style.textColor || ''],
|
|
268
|
-
['image', style.image || ''],
|
|
269
|
-
['fillcolor', fillcolor]
|
|
270
|
-
]
|
|
271
|
-
.filter(r => r[1])
|
|
272
|
-
.map(r => `${r[0]}="${r[1]}"`)
|
|
273
|
-
.join(' ');
|
|
274
|
-
return `${node_of(s, state_index)} [${features}];`;
|
|
275
|
-
}).join(' ');
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Compose a multi-line `action\nprobability` label for a transition.
|
|
279
|
-
* Returns `undefined` when both fields are absent, so callers can skip
|
|
280
|
-
* emitting the attribute entirely.
|
|
281
|
-
*
|
|
282
|
-
* @internal
|
|
283
|
-
*/
|
|
284
|
-
function transition_label(tr) {
|
|
285
|
-
if (!tr) {
|
|
286
|
-
return undefined;
|
|
287
|
-
}
|
|
288
|
-
const parts = [`${tr.action || ''}`, `${tr.probability || ''}`].filter(x => x !== '');
|
|
289
|
-
return parts.length ? parts.join('\n') : undefined;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Pick the graphviz arrowhead style for a transition, by transition kind
|
|
293
|
-
* (forced > main > normal).
|
|
294
|
-
*
|
|
295
|
-
* @internal
|
|
296
|
-
*/
|
|
297
|
-
function arrow_for(tr) {
|
|
298
|
-
if (!tr) {
|
|
299
|
-
return '';
|
|
300
|
-
}
|
|
301
|
-
if (tr.forced_only) {
|
|
302
|
-
return 'ediamond';
|
|
303
|
-
}
|
|
304
|
-
if (tr.main_path) {
|
|
305
|
-
return 'normal;weight=5';
|
|
306
|
-
}
|
|
307
|
-
return 'empty';
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Pick the line color for a transition end-position from a precomputed
|
|
311
|
-
* state-kind classification (final > complete > terminal > base).
|
|
312
|
-
*
|
|
313
|
-
* @internal
|
|
314
|
-
*/
|
|
315
|
-
function line_color(kind, lkind, suffix) {
|
|
316
|
-
switch (kind) {
|
|
317
|
-
case 'final': return vc(`${lkind}_final${suffix}`);
|
|
318
|
-
case 'complete': return vc(`${lkind}_complete${suffix}`);
|
|
319
|
-
case 'terminal': return vc(`${lkind}_terminal${suffix}`);
|
|
320
|
-
default: return vc(`${lkind}${suffix}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Pick the text color for a transition label end-position from a precomputed
|
|
325
|
-
* state-kind classification (final > complete > terminal > none).
|
|
326
|
-
*
|
|
327
|
-
* @internal
|
|
328
|
-
*/
|
|
329
|
-
function text_color(kind, suffix) {
|
|
330
|
-
switch (kind) {
|
|
331
|
-
case 'final': return vc(`text_final${suffix}`);
|
|
332
|
-
case 'complete': return vc(`text_complete${suffix}`);
|
|
333
|
-
case 'terminal': return vc(`text_terminal${suffix}`);
|
|
334
|
-
default: return '';
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Build the colored-HTML `headlabel=` / `taillabel=` fragment for a
|
|
339
|
-
* transition end, concatenating `name`, `probability`, and `action`
|
|
340
|
-
* fields when present. Returns the empty string when nothing should
|
|
341
|
-
* be emitted.
|
|
342
|
-
*
|
|
343
|
-
* Historical note: the original jssm-viz code passed `JssmTransitionList`
|
|
344
|
-
* wrappers to this builder by mistake, so this colored-HTML label path
|
|
345
|
-
* silently produced empty strings for years; only the simpler
|
|
346
|
-
* `taillabel="..."` form below kept user-visible labels rendering.
|
|
347
|
-
* Fixed during the merge.
|
|
348
|
-
*
|
|
349
|
-
* @internal
|
|
350
|
-
*/
|
|
351
|
-
function colored_label(tr, which, color) {
|
|
352
|
-
if (!tr) {
|
|
353
|
-
return '';
|
|
354
|
-
}
|
|
355
|
-
const text = [tr.name, tr.probability, tr.action].filter(q => q).join('<br/>');
|
|
356
|
-
if (!text) {
|
|
357
|
-
return '';
|
|
358
|
-
}
|
|
359
|
-
const body = color ? `<<font color="${color}">${text}</font>>` : `"${text}"`;
|
|
360
|
-
return `${which}=${body};`;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Render the edge-feature list, including bidirectional fold-up and
|
|
364
|
-
* per-direction action / probability labels. Suppressed-edge tracking
|
|
365
|
-
* uses an internally-owned `Set<string>` keyed by `"from|to"` for O(1)
|
|
366
|
-
* duplicate detection, replacing the previous `[string, string][]`
|
|
367
|
-
* accumulator and O(n) `find` probe.
|
|
368
|
-
*
|
|
369
|
-
* @internal
|
|
370
|
-
*/
|
|
371
|
-
function states_to_edges_string(u_jssm, l_states, state_index, state_kinds) {
|
|
372
|
-
const doublequote = (txt) => txt.replace(/"/g, '\\"');
|
|
373
|
-
const strike = new Set();
|
|
374
|
-
const kind_of = (s) => { var _a; return (_a = state_kinds.get(s)) !== null && _a !== void 0 ? _a : 'base'; };
|
|
375
|
-
return l_states.map((s) => u_jssm.list_exits(s).map((ex) => {
|
|
376
|
-
if (strike.has(`${s}|${ex}`)) {
|
|
377
|
-
return '';
|
|
378
|
-
}
|
|
379
|
-
const edge_tr = u_jssm.lookup_transition_for(s, ex);
|
|
380
|
-
if (!edge_tr) {
|
|
381
|
-
return '';
|
|
382
|
-
} // belt-and-suspenders; list_exits should always have a corresponding transition
|
|
383
|
-
const pair_tr = u_jssm.lookup_transition_for(ex, s);
|
|
384
|
-
const double = (pair_tr !== undefined) && (s !== ex);
|
|
385
|
-
const s_kind = kind_of(s);
|
|
386
|
-
const ex_kind = kind_of(ex);
|
|
387
|
-
// colored-HTML labels (per-direction, with text colors)
|
|
388
|
-
const headColor = text_color(s_kind, double ? '_1' : '_solo');
|
|
389
|
-
const tailColor = text_color(ex_kind, double ? '_2' : '_solo');
|
|
390
|
-
const labelInline = colored_label(double ? pair_tr : undefined, 'headlabel', headColor) +
|
|
391
|
-
colored_label(edge_tr, 'taillabel', tailColor);
|
|
392
|
-
// plain `headlabel="..."` / `taillabel="..."` fallback
|
|
393
|
-
const label = transition_label(edge_tr);
|
|
394
|
-
const rlabel = transition_label(pair_tr);
|
|
395
|
-
const maybeLabel = label ? `taillabel="${doublequote(label)}";` : '';
|
|
396
|
-
const maybeRLabel = rlabel ? `headlabel="${doublequote(rlabel)}";` : '';
|
|
397
|
-
const arrowHead = arrow_for(edge_tr);
|
|
398
|
-
const arrowTail = arrow_for(pair_tr);
|
|
399
|
-
const edgeInline = double
|
|
400
|
-
? `${maybeLabel}${maybeRLabel}arrowhead=${arrowHead};arrowtail=${arrowTail};dir=both;color="${line_color(ex_kind, edge_tr.kind, '_1')}:${line_color(s_kind, (pair_tr !== null && pair_tr !== void 0 ? pair_tr : { kind: 'legal' }).kind, '_2')}"`
|
|
401
|
-
: `${maybeLabel}arrowhead=${arrowHead};color="${line_color(ex_kind, edge_tr.kind, '_solo')}"`;
|
|
402
|
-
if (pair_tr) {
|
|
403
|
-
strike.add(`${ex}|${s}`);
|
|
404
|
-
}
|
|
405
|
-
return `${node_of(s, state_index)}->${node_of(ex, state_index)} [${labelInline}${edgeInline}];`;
|
|
406
|
-
}).join(' ')).join(' ');
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Render `arrange`, `arrange_start`, and `arrange_end` declarations to
|
|
410
|
-
* rank-grouped subgraphs (`rank=same`/`rank=min`/`rank=max`).
|
|
411
|
-
*
|
|
412
|
-
* @internal
|
|
413
|
-
*/
|
|
414
|
-
function arranges_for(u_jssm, state_index) {
|
|
415
|
-
const group = (decls, rank) => decls
|
|
416
|
-
? decls.map(d => `{rank=${rank}; ${d.map(di => node_of(di, state_index)).join('; ')};};`).join('\n')
|
|
417
|
-
: '';
|
|
418
|
-
return group(u_jssm._arrange_declaration, 'same')
|
|
419
|
-
+ group(u_jssm._arrange_start_declaration, 'min')
|
|
420
|
-
+ group(u_jssm._arrange_end_declaration, 'max');
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Render a {@link jssm.Machine} as a graphviz dot string.
|
|
424
|
-
*
|
|
425
|
-
* ```typescript
|
|
426
|
-
* import { sm } from 'jssm';
|
|
427
|
-
* import { machine_to_dot } from 'jssm/viz';
|
|
428
|
-
*
|
|
429
|
-
* const dot = machine_to_dot(sm`a -> b;`);
|
|
430
|
-
* // 'digraph G { ... }'
|
|
431
|
-
* ```
|
|
432
|
-
*
|
|
433
|
-
* @param u_jssm The machine to render.
|
|
434
|
-
* @returns A complete graphviz dot source string.
|
|
435
|
-
*/
|
|
436
|
-
function machine_to_dot(u_jssm) {
|
|
437
|
-
const l_states = u_jssm.states();
|
|
438
|
-
const state_index = new Map(l_states.map((s, i) => [s, i]));
|
|
439
|
-
const state_kinds = classify_states(u_jssm, l_states);
|
|
440
|
-
const nodes = states_to_nodes_string(u_jssm, l_states, state_index, state_kinds);
|
|
441
|
-
const edges = states_to_edges_string(u_jssm, l_states, state_index, state_kinds);
|
|
442
|
-
const arranges = arranges_for(u_jssm, state_index);
|
|
443
|
-
const rank_dir = flow_direction_to_rankdir(u_jssm.flow() || 'down');
|
|
444
|
-
const preamble = u_jssm.dot_preamble() || '';
|
|
445
|
-
return dot_template(rank_dir, vc('graph_bg_color'), nodes, edges, arranges, preamble);
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Render an FSL string directly to graphviz dot source.
|
|
449
|
-
*
|
|
450
|
-
* ```typescript
|
|
451
|
-
* import { fsl_to_dot } from 'jssm/viz';
|
|
452
|
-
* const dot = fsl_to_dot('a -> b;');
|
|
453
|
-
* ```
|
|
454
|
-
*
|
|
455
|
-
* @param fsl The FSL source.
|
|
456
|
-
* @returns A complete graphviz dot source string.
|
|
457
|
-
*/
|
|
458
|
-
function fsl_to_dot(fsl) {
|
|
459
|
-
return machine_to_dot(jssm.sm `${fsl}`);
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Render a graphviz dot source string to SVG using `@viz-js/viz`. The
|
|
463
|
-
* underlying viz instance is lazy-initialized on first call and cached for
|
|
464
|
-
* the lifetime of the module.
|
|
465
|
-
*
|
|
466
|
-
* ```typescript
|
|
467
|
-
* const svg = await dot_to_svg('digraph G { a -> b }');
|
|
468
|
-
* ```
|
|
469
|
-
*
|
|
470
|
-
* @param dot Graphviz dot source.
|
|
471
|
-
* @returns A promise resolving to an SVG XML string.
|
|
472
|
-
*/
|
|
473
|
-
async function dot_to_svg(dot) {
|
|
474
|
-
const viz = await get_viz();
|
|
475
|
-
return viz.renderString(dot, { format: 'svg' });
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Render an FSL string directly to SVG.
|
|
479
|
-
*
|
|
480
|
-
* @param fsl The FSL source.
|
|
481
|
-
* @returns A promise resolving to an SVG XML string.
|
|
482
|
-
*/
|
|
483
|
-
async function fsl_to_svg_string(fsl) {
|
|
484
|
-
return dot_to_svg(fsl_to_dot(fsl));
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Render a {@link jssm.Machine} to SVG.
|
|
488
|
-
*
|
|
489
|
-
* @param u_jssm The machine to render.
|
|
490
|
-
* @returns A promise resolving to an SVG XML string.
|
|
491
|
-
*/
|
|
492
|
-
async function machine_to_svg_string(u_jssm) {
|
|
493
|
-
return dot_to_svg(machine_to_dot(u_jssm));
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Resolve a `DOMParser` constructor: prefer `globalThis.DOMParser` (browsers,
|
|
497
|
-
* jsdom test environment), fall back to the value passed to {@link configure},
|
|
498
|
-
* throw `JssmError` if neither is available.
|
|
499
|
-
*
|
|
500
|
-
* @internal
|
|
501
|
-
*/
|
|
502
|
-
function get_dom_parser() {
|
|
503
|
-
if (typeof globalThis.DOMParser === 'function') {
|
|
504
|
-
return globalThis.DOMParser;
|
|
505
|
-
}
|
|
506
|
-
if (injected_dom_parser !== null) {
|
|
507
|
-
return injected_dom_parser;
|
|
508
|
-
}
|
|
509
|
-
throw new JssmError(undefined, 'jssm/viz: *_svg_element requires a browser DOM. Use *_svg_string in Node, or call configure({ DOMParser }) with a parser from jsdom or @xmldom/xmldom.');
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Render dot source to a parsed `SVGSVGElement`. Browser-by-default; in
|
|
513
|
-
* Node, requires a `DOMParser` to have been injected via {@link configure}.
|
|
514
|
-
*
|
|
515
|
-
* @param dot Graphviz dot source.
|
|
516
|
-
* @returns A promise resolving to a parsed `SVGSVGElement`.
|
|
517
|
-
* @throws {JssmError} if no `DOMParser` is available.
|
|
518
|
-
*/
|
|
519
|
-
async function dot_to_svg_element(dot) {
|
|
520
|
-
const ParserCtor = get_dom_parser();
|
|
521
|
-
const svg_string = await dot_to_svg(dot);
|
|
522
|
-
const parser = new ParserCtor();
|
|
523
|
-
const doc = parser.parseFromString(svg_string, 'image/svg+xml');
|
|
524
|
-
return doc.documentElement;
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Render an FSL string directly to a parsed `SVGSVGElement`.
|
|
528
|
-
*
|
|
529
|
-
* @param fsl The FSL source.
|
|
530
|
-
* @returns A promise resolving to a parsed `SVGSVGElement`.
|
|
531
|
-
* @throws {JssmError} if no `DOMParser` is available (Node without `configure`).
|
|
532
|
-
*/
|
|
533
|
-
async function fsl_to_svg_element(fsl) {
|
|
534
|
-
return dot_to_svg_element(fsl_to_dot(fsl));
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Render a {@link jssm.Machine} to a parsed `SVGSVGElement`.
|
|
538
|
-
*
|
|
539
|
-
* @param u_jssm The machine to render.
|
|
540
|
-
* @returns A promise resolving to a parsed `SVGSVGElement`.
|
|
541
|
-
* @throws {JssmError} if no `DOMParser` is available (Node without `configure`).
|
|
542
|
-
*/
|
|
543
|
-
async function machine_to_svg_element(u_jssm) {
|
|
544
|
-
return dot_to_svg_element(machine_to_dot(u_jssm));
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Compatibility wrapper for {@link machine_to_dot}, retained from
|
|
548
|
-
* jssm-viz. Will be removed in the next major.
|
|
549
|
-
*
|
|
550
|
-
* @deprecated Use {@link machine_to_dot} instead.
|
|
551
|
-
*/
|
|
552
|
-
function dot(machine) {
|
|
553
|
-
return machine_to_dot(machine);
|
|
554
|
-
}
|
|
555
|
-
export { configure, dot, dot_to_svg, fsl_to_dot, fsl_to_svg_string, fsl_to_svg_element, machine_to_dot, machine_to_svg_string, machine_to_svg_element, version, build_time };
|
|
556
|
-
/** @internal — test-only access to private helpers. */
|
|
557
|
-
export const _test = {
|
|
558
|
-
color8to6, u_color8to6, vc, node_of,
|
|
559
|
-
shape_for_state, image_for_state, style_for_state
|
|
560
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default color palette for jssm/viz dot/svg output. Used by the graphviz
|
|
3
|
-
* rendering helpers in jssm_viz.ts to colorize nodes and edges based on
|
|
4
|
-
* state type (terminal/final/complete/normal) and arrow kind
|
|
5
|
-
* (legal/main/forced).
|
|
6
|
-
*
|
|
7
|
-
* Keys are flat strings of the form `<arrow_kind>_<modifier>_<position>`
|
|
8
|
-
* (e.g. `legal_final_solo`, `main_terminal_2`); also the special node fill
|
|
9
|
-
* colors (`fill_final`, `fill_terminal`, `fill_complete`) and the graph
|
|
10
|
-
* background.
|
|
11
|
-
*/
|
|
12
|
-
const default_viz_colors = {
|
|
13
|
-
'graph_bg_color': '#eeeeee',
|
|
14
|
-
'fill_final': '#ddddff',
|
|
15
|
-
'fill_terminal': '#ffdddd',
|
|
16
|
-
'fill_complete': '#ddffdd',
|
|
17
|
-
'legal_1': '#888888',
|
|
18
|
-
'legal_2': '#777777',
|
|
19
|
-
'legal_solo': '#777777',
|
|
20
|
-
'legal_final_1': '#7777aa',
|
|
21
|
-
'legal_final_2': '#666699',
|
|
22
|
-
'legal_final_solo': '#666699',
|
|
23
|
-
'legal_terminal_1': '#aa7777',
|
|
24
|
-
'legal_terminal_2': '#996666',
|
|
25
|
-
'legal_terminal_solo': '#996666',
|
|
26
|
-
'legal_complete_1': '#77aa77',
|
|
27
|
-
'legal_complete_2': '#669966',
|
|
28
|
-
'legal_complete_solo': '#669966',
|
|
29
|
-
'main_1': '#444444',
|
|
30
|
-
'main_2': '#333333',
|
|
31
|
-
'main_solo': '#333333',
|
|
32
|
-
'main_final_1': '#333366',
|
|
33
|
-
'main_final_2': '#222255',
|
|
34
|
-
'main_final_solo': '#222255',
|
|
35
|
-
'main_terminal_1': '#663333',
|
|
36
|
-
'main_terminal_2': '#552222',
|
|
37
|
-
'main_terminal_solo': '#552222',
|
|
38
|
-
'main_complete_1': '#336633',
|
|
39
|
-
'main_complete_2': '#225522',
|
|
40
|
-
'main_complete_solo': '#225522',
|
|
41
|
-
'forced_1': '#cccccc',
|
|
42
|
-
'forced_2': '#bbbbbb',
|
|
43
|
-
'forced_solo': '#bbbbbb',
|
|
44
|
-
'forced_final_1': '#bbbbee',
|
|
45
|
-
'forced_final_2': '#aaaadd',
|
|
46
|
-
'forced_final_solo': '#aaaadd',
|
|
47
|
-
'forced_terminal_1': '#eebbbb',
|
|
48
|
-
'forced_terminal_2': '#ddaaaa',
|
|
49
|
-
'forced_terminal_solo': '#ddaaaa',
|
|
50
|
-
'forced_complete_1': '#bbeebb',
|
|
51
|
-
'forced_complete_2': '#aaddaa',
|
|
52
|
-
'forced_complete_solo': '#aaddaa',
|
|
53
|
-
'text_final_1': '#000088',
|
|
54
|
-
'text_final_2': '#000088',
|
|
55
|
-
'text_final_solo': '#000088',
|
|
56
|
-
'text_terminal_1': '#880000',
|
|
57
|
-
'text_terminal_2': '#880000',
|
|
58
|
-
'text_terminal_solo': '#880000',
|
|
59
|
-
'text_complete_1': '#007700',
|
|
60
|
-
'text_complete_2': '#007700',
|
|
61
|
-
'text_complete_solo': '#007700'
|
|
62
|
-
};
|
|
63
|
-
export { default_viz_colors };
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { JssmStateConfig, JssmBaseTheme } from '../jssm_types';
|
|
2
|
-
declare const base_state_style: JssmStateConfig;
|
|
3
|
-
declare const base_active_state_style: JssmStateConfig;
|
|
4
|
-
declare const base_terminal_state_style: JssmStateConfig;
|
|
5
|
-
declare const base_active_terminal_state_style: JssmStateConfig;
|
|
6
|
-
declare const base_start_state_style: JssmStateConfig;
|
|
7
|
-
declare const base_active_start_state_style: JssmStateConfig;
|
|
8
|
-
declare const base_end_state_style: JssmStateConfig;
|
|
9
|
-
declare const base_active_end_state_style: JssmStateConfig;
|
|
10
|
-
declare const base_theme: JssmBaseTheme;
|
|
11
|
-
export { base_state_style, base_active_state_style, base_terminal_state_style, base_active_terminal_state_style, base_start_state_style, base_active_start_state_style, base_end_state_style, base_active_end_state_style, base_theme, base_theme as theme };
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
const base_state_style = {
|
|
2
|
-
shape: 'rectangle',
|
|
3
|
-
backgroundColor: 'white',
|
|
4
|
-
textColor: 'black',
|
|
5
|
-
borderColor: 'black'
|
|
6
|
-
};
|
|
7
|
-
const base_active_state_style = {
|
|
8
|
-
textColor: 'white',
|
|
9
|
-
backgroundColor: 'dodgerblue4'
|
|
10
|
-
};
|
|
11
|
-
const base_hooked_state_style = {
|
|
12
|
-
shape: 'component'
|
|
13
|
-
};
|
|
14
|
-
const base_terminal_state_style = {
|
|
15
|
-
textColor: 'white',
|
|
16
|
-
backgroundColor: 'crimson'
|
|
17
|
-
};
|
|
18
|
-
const base_active_terminal_state_style = {
|
|
19
|
-
textColor: 'white',
|
|
20
|
-
backgroundColor: 'indigo'
|
|
21
|
-
};
|
|
22
|
-
const base_start_state_style = {
|
|
23
|
-
backgroundColor: 'yellow'
|
|
24
|
-
};
|
|
25
|
-
const base_active_start_state_style = {
|
|
26
|
-
backgroundColor: 'yellowgreen'
|
|
27
|
-
};
|
|
28
|
-
const base_active_hooked_state_style = {
|
|
29
|
-
backgroundColor: 'yellowgreen'
|
|
30
|
-
};
|
|
31
|
-
const base_end_state_style = {
|
|
32
|
-
textColor: 'white',
|
|
33
|
-
backgroundColor: 'darkolivegreen'
|
|
34
|
-
};
|
|
35
|
-
const base_active_end_state_style = {
|
|
36
|
-
textColor: 'white',
|
|
37
|
-
backgroundColor: 'darkgreen'
|
|
38
|
-
};
|
|
39
|
-
const base_theme = {
|
|
40
|
-
name: 'base',
|
|
41
|
-
state: base_state_style,
|
|
42
|
-
start: base_start_state_style,
|
|
43
|
-
end: base_end_state_style,
|
|
44
|
-
terminal: base_terminal_state_style,
|
|
45
|
-
hooked: base_hooked_state_style,
|
|
46
|
-
active: base_active_state_style,
|
|
47
|
-
active_start: base_active_start_state_style,
|
|
48
|
-
active_end: base_active_end_state_style,
|
|
49
|
-
active_terminal: base_active_terminal_state_style,
|
|
50
|
-
active_hooked: base_active_hooked_state_style,
|
|
51
|
-
legal: undefined,
|
|
52
|
-
main: undefined,
|
|
53
|
-
forced: undefined,
|
|
54
|
-
action: undefined,
|
|
55
|
-
graph: undefined,
|
|
56
|
-
title: undefined // TODO FIXME
|
|
57
|
-
};
|
|
58
|
-
export { base_state_style, base_active_state_style, base_terminal_state_style, base_active_terminal_state_style, base_start_state_style, base_active_start_state_style, base_end_state_style, base_active_end_state_style, base_theme, base_theme as theme };
|