-
Notifications
You must be signed in to change notification settings - Fork 1
/
graph-to-dot.js
80 lines (68 loc) · 1.97 KB
/
graph-to-dot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* @typedef {object} Opts
* @prop {string} pre
* @prop {string} initial
* @prop {string} terminal
* @prop {Record<string, Record<string, Record<string, Record<string, any>>>>} edges
* @prop {string} post
*/
/** @type {Opts}*/
const stdOpts = {
pre: ` graph [rankdir=LR]
node [fontname="Trebuchet MS" fontsize=14
color="/accent3/3" shape=box style="rounded,filled"]
edge [fontname="Trebuchet MS" fontsize=10 arrowsize=0.7]
`,
initial: `[color="/accent3/1"]`,
terminal: `[color="/accent3/2"]`,
edges: {},
post: '',
};
/** @arg {Record<string, any> | undefined } obj */
export function props(obj) {
const buf = [];
for (const key in obj) buf.push(`${key}="${obj[key].toString()}"`);
return buf.join(' ');
}
/**
* @arg {Graph} graph
* @arg {State} initial
* @arg {Partial<Opts>} [userOpts]
*/
export function graphToDot(graph, initial, userOpts) {
/** @type {Opts} */
const opts = Object.assign({}, stdOpts, userOpts);
const buf = ['digraph {'];
buf.push(opts.pre);
for (const from in graph) {
const gf = graph[from];
let terminal = false;
if (gf == null) terminal = true;
else if ('ENTER' in gf || 'LEAVE' in gf) {
const n = Object.keys(gf).length;
terminal = n < 2 || (n < 3 && 'ENTER' in gf && 'LEAVE' in gf);
}
if (terminal) {
buf.push(` ${from} ${opts.terminal}`);
continue;
} else if (from === initial) {
buf.push(` ${from} ${opts.initial}`);
}
for (const edge in gf) {
if (edge !== 'ENTER' && edge !== 'LEAVE') {
const gfe = /** @type {Edge}*/ (gf)[edge];
for (const to of gfe.to) {
const es = opts.edges;
let label = `label="${edge}"`;
if (es && es[from] && es[from][edge] && es[from][edge][to]) {
label += ' ' + props(es[from][edge][to]);
}
buf.push(` ${from} -> ${to} [${label}]`);
}
}
}
}
buf.push(opts.post);
buf.push('}');
return buf.join('\n');
}