libpetri 1.8.4 → 2.0.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 +47 -0
- package/dist/{chunk-B2D5DMTO.js → chunk-4L6JVKH4.js} +165 -8
- package/dist/chunk-4L6JVKH4.js.map +1 -0
- package/dist/chunk-H62Z76FY.js +1346 -0
- package/dist/chunk-H62Z76FY.js.map +1 -0
- package/dist/chunk-SXK2Z45Z.js +50 -0
- package/dist/chunk-SXK2Z45Z.js.map +1 -0
- package/dist/debug/index.d.ts +50 -3
- package/dist/debug/index.js +64 -6
- package/dist/debug/index.js.map +1 -1
- package/dist/doclet/index.d.ts +152 -31
- package/dist/doclet/index.js +458 -57
- package/dist/doclet/index.js.map +1 -1
- package/dist/doclet/resources/petrinet-diagrams.css +384 -7
- package/dist/doclet/resources/petrinet-diagrams.js +7214 -106
- package/dist/dot-exporter-PMHOQHZ3.js +8 -0
- package/dist/{event-store-BnyHh3TF.d.ts → event-store-2zkXeQkd.d.ts} +1 -1
- package/dist/export/index.d.ts +16 -2
- package/dist/export/index.js +1 -1
- package/dist/index.d.ts +41 -5
- package/dist/index.js +1221 -35
- package/dist/index.js.map +1 -1
- package/dist/petri-net-BDrj4XZE.d.ts +1461 -0
- package/dist/render-P6GROU7J.js +16 -0
- package/dist/render-P6GROU7J.js.map +1 -0
- package/dist/verification/index.d.ts +3 -144
- package/dist/verification/index.js +30 -1214
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.d.ts +196 -0
- package/dist/viewer/index.js +442 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/viewer/viewer-static.iife.js +3 -0
- package/dist/viewer/viewer.css +503 -0
- package/dist/viewer/viewer.iife.js +7219 -0
- package/package.json +17 -8
- package/dist/chunk-B2D5DMTO.js.map +0 -1
- package/dist/chunk-VQ4XMJTD.js +0 -107
- package/dist/chunk-VQ4XMJTD.js.map +0 -1
- package/dist/dot-exporter-3CVCH6J4.js +0 -8
- package/dist/petri-net-D-GN9g_D.d.ts +0 -570
- /package/dist/{dot-exporter-3CVCH6J4.js.map → dot-exporter-PMHOQHZ3.js.map} +0 -0
package/README.md
CHANGED
|
@@ -99,6 +99,53 @@ const marking = await executor.run();
|
|
|
99
99
|
console.log(marking.peekTokens(output)); // [Token { value: 'HELLO' }]
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
## Modular composition
|
|
103
|
+
|
|
104
|
+
Build large nets by reusing open-net fragments. A `SubnetDef` is a structurally
|
|
105
|
+
complete `PetriNet` plus a typed `Interface` of **ports** (typed places) and
|
|
106
|
+
**channels** (transitions). Instantiating renames every internal element with a
|
|
107
|
+
`prefix/name`; composing into a host substitutes port places and merges
|
|
108
|
+
channel transitions.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { PetriNet, SubnetDef, FusionSet, place } from 'libpetri';
|
|
112
|
+
|
|
113
|
+
const items = place<string>('items');
|
|
114
|
+
const slots = place<string>('slots');
|
|
115
|
+
const put = place<string>('put');
|
|
116
|
+
const get = place<string>('get');
|
|
117
|
+
|
|
118
|
+
const buffer = SubnetDef.builder<void>('Buffer')
|
|
119
|
+
.place(items).place(slots)
|
|
120
|
+
.transition(enqueue).transition(dequeue)
|
|
121
|
+
.inputPort('put', put)
|
|
122
|
+
.outputPort('get', get)
|
|
123
|
+
.build();
|
|
124
|
+
|
|
125
|
+
const b1 = buffer.instantiate('b1').bindActions({ enqueue: enqueueImpl, dequeue: fork() });
|
|
126
|
+
const b2 = buffer.instantiate('b2').bindActions({ enqueue: enqueueImpl, dequeue: fork() });
|
|
127
|
+
|
|
128
|
+
const producerToB1 = place<string>('p1_to_b1');
|
|
129
|
+
const b1ToB2 = place<string>('b1_to_b2');
|
|
130
|
+
const b2ToConsumer = place<string>('b2_to_c');
|
|
131
|
+
|
|
132
|
+
const net = PetriNet.builder('Pipeline')
|
|
133
|
+
.compose(b1, (bind) =>
|
|
134
|
+
bind.bindPort('put', producerToB1).bindPort('get', b1ToB2))
|
|
135
|
+
.compose(b2, (bind) =>
|
|
136
|
+
bind.bindPort('put', b1ToB2).bindPort('get', b2ToConsumer))
|
|
137
|
+
.fuse(FusionSet.of('sharedSlots', b1.port<string>('slots'), b2.port<string>('slots')))
|
|
138
|
+
.build();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Composition produces a flat `PetriNet`. Ports merge places by structural
|
|
142
|
+
rewrite; channels (`bindChannel`) merge transitions with arc union, timing
|
|
143
|
+
intersection, and caller-wins identity. `FusionSet` declares N-ary place
|
|
144
|
+
equivalence applied **after** all `compose(...)` calls, regardless of
|
|
145
|
+
registration order. Per-instance action overrides are supplied via
|
|
146
|
+
`Instance.bindActions({ originalName: action })`. See
|
|
147
|
+
[`spec/11-modular-composition.md`](../spec/11-modular-composition.md).
|
|
148
|
+
|
|
102
149
|
## Verification Example
|
|
103
150
|
|
|
104
151
|
```typescript
|
|
@@ -12,7 +12,9 @@ var NODE_STYLES = {
|
|
|
12
12
|
environment: { shape: "circle", fill: "#f8d7da", stroke: "#721c24", penwidth: 2, style: "dashed", width: 0.35 },
|
|
13
13
|
transition: { shape: "box", fill: "#fff3cd", stroke: "#856404", penwidth: 1, height: 0.4, width: 0.8 },
|
|
14
14
|
"xor-junction": { shape: "diamond", fill: "#FFFFFF", stroke: "#333333", penwidth: 1, height: 0.3, width: 0.3 },
|
|
15
|
-
"and-junction": { shape: "diamond", fill: "#FFFFFF", stroke: "#333333", penwidth: 1, height: 0.3, width: 0.3 }
|
|
15
|
+
"and-junction": { shape: "diamond", fill: "#FFFFFF", stroke: "#333333", penwidth: 1, height: 0.3, width: 0.3 },
|
|
16
|
+
"interface-port": { shape: "circle", fill: "#e7f1ff", stroke: "#1d4ed8", penwidth: 2, style: "dashed", width: 0.35 },
|
|
17
|
+
"sync-channel": { shape: "box", fill: "#f3e8ff", stroke: "#6d28d9", penwidth: 1.5, height: 0.4, width: 0.8 }
|
|
16
18
|
};
|
|
17
19
|
var EDGE_STYLES = {
|
|
18
20
|
input: { color: "#333333", style: "solid", arrowhead: "normal" },
|
|
@@ -31,6 +33,131 @@ function edgeStyle(arcType) {
|
|
|
31
33
|
return EDGE_STYLES[arcType];
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
// src/export/subnet-prefixes.ts
|
|
37
|
+
function instancePrefixOf(nodeName) {
|
|
38
|
+
if (nodeName == null) return void 0;
|
|
39
|
+
const idx = nodeName.lastIndexOf("/");
|
|
40
|
+
if (idx <= 0) return void 0;
|
|
41
|
+
return nodeName.substring(0, idx);
|
|
42
|
+
}
|
|
43
|
+
function parentOf(prefix) {
|
|
44
|
+
if (prefix == null || prefix.length === 0) return void 0;
|
|
45
|
+
const idx = prefix.lastIndexOf("/");
|
|
46
|
+
if (idx <= 0) return void 0;
|
|
47
|
+
return prefix.substring(0, idx);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/export/cluster-builder.ts
|
|
51
|
+
var DEFAULT_CLUSTER_ATTRS = {
|
|
52
|
+
style: "rounded,dashed",
|
|
53
|
+
bgcolor: "#FAFAFA",
|
|
54
|
+
penwidth: "1.5"
|
|
55
|
+
};
|
|
56
|
+
function partition(nodes, edges, nodeIdToPrefix) {
|
|
57
|
+
if (nodeIdToPrefix.size === 0) {
|
|
58
|
+
return {
|
|
59
|
+
topLevelNodes: [...nodes],
|
|
60
|
+
topLevelEdges: [...edges],
|
|
61
|
+
topLevelSubgraphs: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const prefixOrder = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const prefix of nodeIdToPrefix.values()) {
|
|
66
|
+
registerPrefixChain(prefix, prefixOrder);
|
|
67
|
+
}
|
|
68
|
+
const prefixState = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const prefix of prefixOrder.keys()) {
|
|
70
|
+
prefixState.set(prefix, { prefix, nodes: [], edges: [], childPrefixes: [] });
|
|
71
|
+
}
|
|
72
|
+
for (const prefix of prefixState.keys()) {
|
|
73
|
+
const parent = parentOf(prefix);
|
|
74
|
+
if (parent !== void 0) {
|
|
75
|
+
prefixState.get(parent).childPrefixes.push(prefix);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const topLevelNodes = [];
|
|
79
|
+
for (const node of nodes) {
|
|
80
|
+
const prefix = nodeIdToPrefix.get(node.id);
|
|
81
|
+
if (prefix === void 0) {
|
|
82
|
+
topLevelNodes.push(node);
|
|
83
|
+
} else {
|
|
84
|
+
prefixState.get(prefix).nodes.push(node);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const topLevelEdges = [];
|
|
88
|
+
for (const edge of edges) {
|
|
89
|
+
const fromPrefix = nodeIdToPrefix.get(edge.from);
|
|
90
|
+
const toPrefix = nodeIdToPrefix.get(edge.to);
|
|
91
|
+
const common = deepestCommonPrefix(fromPrefix, toPrefix);
|
|
92
|
+
if (common === void 0) {
|
|
93
|
+
topLevelEdges.push(edge);
|
|
94
|
+
} else {
|
|
95
|
+
prefixState.get(common).edges.push(edge);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const built = /* @__PURE__ */ new Map();
|
|
99
|
+
const iterationOrder = [...prefixState.keys()].reverse();
|
|
100
|
+
for (const prefix of iterationOrder) {
|
|
101
|
+
const state = prefixState.get(prefix);
|
|
102
|
+
const children = [];
|
|
103
|
+
for (const childPrefix of state.childPrefixes) {
|
|
104
|
+
const child = built.get(childPrefix);
|
|
105
|
+
if (child !== void 0) {
|
|
106
|
+
children.push(child);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
children.reverse();
|
|
110
|
+
const subgraph = {
|
|
111
|
+
id: sanitize(prefix),
|
|
112
|
+
label: prefix,
|
|
113
|
+
nodes: [...state.nodes],
|
|
114
|
+
edges: [...state.edges],
|
|
115
|
+
subgraphs: children,
|
|
116
|
+
attrs: DEFAULT_CLUSTER_ATTRS
|
|
117
|
+
};
|
|
118
|
+
built.set(prefix, subgraph);
|
|
119
|
+
}
|
|
120
|
+
const topLevelSubgraphs = [];
|
|
121
|
+
for (const prefix of prefixState.keys()) {
|
|
122
|
+
if (parentOf(prefix) === void 0) {
|
|
123
|
+
topLevelSubgraphs.push(built.get(prefix));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
topLevelNodes,
|
|
128
|
+
topLevelEdges,
|
|
129
|
+
topLevelSubgraphs
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function registerPrefixChain(prefix, accumulator) {
|
|
133
|
+
if (prefix == null || prefix.length === 0) return;
|
|
134
|
+
const segments = prefix.split("/");
|
|
135
|
+
let built = "";
|
|
136
|
+
for (const seg of segments) {
|
|
137
|
+
if (built.length > 0) built += "/";
|
|
138
|
+
built += seg;
|
|
139
|
+
if (!accumulator.has(built)) {
|
|
140
|
+
accumulator.set(built, true);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function deepestCommonPrefix(a, b) {
|
|
145
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
146
|
+
if (a === b) return a;
|
|
147
|
+
const aSegs = a.split("/");
|
|
148
|
+
const bSegs = b.split("/");
|
|
149
|
+
let built = "";
|
|
150
|
+
let matched = 0;
|
|
151
|
+
const min = Math.min(aSegs.length, bSegs.length);
|
|
152
|
+
for (let i = 0; i < min; i++) {
|
|
153
|
+
if (aSegs[i] !== bSegs[i]) break;
|
|
154
|
+
if (matched > 0) built += "/";
|
|
155
|
+
built += aSegs[i];
|
|
156
|
+
matched++;
|
|
157
|
+
}
|
|
158
|
+
return matched === 0 ? void 0 : built;
|
|
159
|
+
}
|
|
160
|
+
|
|
34
161
|
// src/export/petri-net-mapper.ts
|
|
35
162
|
var DEFAULT_DOT_CONFIG = {
|
|
36
163
|
direction: "TB",
|
|
@@ -46,11 +173,13 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
|
|
|
46
173
|
const envNames = config.environmentPlaces ?? /* @__PURE__ */ new Set();
|
|
47
174
|
const nodes = [];
|
|
48
175
|
const edges = [];
|
|
176
|
+
const nodeIdToPrefix = /* @__PURE__ */ new Map();
|
|
49
177
|
for (const [name, info] of places) {
|
|
50
178
|
const category = placeCategory(info, envNames.has(name));
|
|
51
179
|
const style = nodeStyle(category);
|
|
180
|
+
const nodeId = "p_" + sanitize(name);
|
|
52
181
|
nodes.push({
|
|
53
|
-
id:
|
|
182
|
+
id: nodeId,
|
|
54
183
|
label: "",
|
|
55
184
|
shape: style.shape,
|
|
56
185
|
fill: style.fill,
|
|
@@ -61,11 +190,16 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
|
|
|
61
190
|
width: style.width,
|
|
62
191
|
attrs: { xlabel: name, fixedsize: "true" }
|
|
63
192
|
});
|
|
193
|
+
const prefix = instancePrefixOf(name);
|
|
194
|
+
if (prefix !== void 0) {
|
|
195
|
+
nodeIdToPrefix.set(nodeId, prefix);
|
|
196
|
+
}
|
|
64
197
|
}
|
|
65
198
|
for (const t of net.transitions) {
|
|
66
199
|
const style = nodeStyle("transition");
|
|
200
|
+
const tid = "t_" + sanitize(t.name);
|
|
67
201
|
nodes.push({
|
|
68
|
-
id:
|
|
202
|
+
id: tid,
|
|
69
203
|
label: transitionLabel(t, config),
|
|
70
204
|
shape: style.shape,
|
|
71
205
|
fill: style.fill,
|
|
@@ -75,10 +209,15 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
|
|
|
75
209
|
height: style.height,
|
|
76
210
|
width: style.width
|
|
77
211
|
});
|
|
212
|
+
const prefix = instancePrefixOf(t.name);
|
|
213
|
+
if (prefix !== void 0) {
|
|
214
|
+
nodeIdToPrefix.set(tid, prefix);
|
|
215
|
+
}
|
|
78
216
|
}
|
|
79
217
|
for (const t of net.transitions) {
|
|
80
218
|
const tid = "t_" + sanitize(t.name);
|
|
81
219
|
const tSanitized = sanitize(t.name);
|
|
220
|
+
const tPrefix = instancePrefixOf(t.name);
|
|
82
221
|
const resetPlaces = new Set(t.resets.map((r) => r.place.name));
|
|
83
222
|
const combined = /* @__PURE__ */ new Set();
|
|
84
223
|
for (const spec of t.inputSpecs) {
|
|
@@ -113,7 +252,9 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
|
|
|
113
252
|
combined,
|
|
114
253
|
nodes,
|
|
115
254
|
edges,
|
|
116
|
-
counter: 0
|
|
255
|
+
counter: 0,
|
|
256
|
+
transitionPrefix: tPrefix,
|
|
257
|
+
nodeIdToPrefix
|
|
117
258
|
};
|
|
118
259
|
emitOutput(t.outputSpec, tid, null, ctx);
|
|
119
260
|
}
|
|
@@ -159,12 +300,13 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
|
|
|
159
300
|
}
|
|
160
301
|
}
|
|
161
302
|
}
|
|
303
|
+
const partitioned = partition(nodes, edges, nodeIdToPrefix);
|
|
162
304
|
return {
|
|
163
305
|
id: sanitize(net.name),
|
|
164
306
|
rankdir: config.direction,
|
|
165
|
-
nodes,
|
|
166
|
-
edges,
|
|
167
|
-
subgraphs:
|
|
307
|
+
nodes: partitioned.topLevelNodes,
|
|
308
|
+
edges: partitioned.topLevelEdges,
|
|
309
|
+
subgraphs: partitioned.topLevelSubgraphs,
|
|
168
310
|
graphAttrs: {
|
|
169
311
|
nodesep: String(GRAPH.nodesep),
|
|
170
312
|
ranksep: String(GRAPH.ranksep),
|
|
@@ -271,6 +413,9 @@ function emitOutput(out, parentId, branchLabel, ctx) {
|
|
|
271
413
|
width: jStyle.width,
|
|
272
414
|
attrs: { fixedsize: "true", fontsize: "14" }
|
|
273
415
|
});
|
|
416
|
+
if (ctx.transitionPrefix !== void 0) {
|
|
417
|
+
ctx.nodeIdToPrefix.set(junctionId, ctx.transitionPrefix);
|
|
418
|
+
}
|
|
274
419
|
const outStyle = edgeStyle("output");
|
|
275
420
|
ctx.edges.push({
|
|
276
421
|
from: parentId,
|
|
@@ -421,6 +566,16 @@ function renderSubgraph(sg, indent) {
|
|
|
421
566
|
for (const node of sg.nodes) {
|
|
422
567
|
lines.push(`${indent} ${renderNode(node)}`);
|
|
423
568
|
}
|
|
569
|
+
if (sg.subgraphs) {
|
|
570
|
+
for (const nested of sg.subgraphs) {
|
|
571
|
+
lines.push(...renderSubgraph(nested, indent + " "));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (sg.edges) {
|
|
575
|
+
for (const e of sg.edges) {
|
|
576
|
+
lines.push(`${indent} ${renderEdge(e)}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
424
579
|
lines.push(`${indent}}`);
|
|
425
580
|
return lines;
|
|
426
581
|
}
|
|
@@ -465,10 +620,12 @@ export {
|
|
|
465
620
|
GRAPH,
|
|
466
621
|
nodeStyle,
|
|
467
622
|
edgeStyle,
|
|
623
|
+
instancePrefixOf,
|
|
624
|
+
parentOf,
|
|
468
625
|
DEFAULT_DOT_CONFIG,
|
|
469
626
|
sanitize,
|
|
470
627
|
mapToGraph,
|
|
471
628
|
renderDot,
|
|
472
629
|
dotExport
|
|
473
630
|
};
|
|
474
|
-
//# sourceMappingURL=chunk-
|
|
631
|
+
//# sourceMappingURL=chunk-4L6JVKH4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/export/styles.ts","../src/export/subnet-prefixes.ts","../src/export/cluster-builder.ts","../src/export/petri-net-mapper.ts","../src/export/dot-renderer.ts","../src/export/dot-exporter.ts"],"sourcesContent":["// GENERATED from spec/petri-net-styles.json — do not edit manually.\n// Regenerate with: scripts/generate-styles.sh\n\n/**\n * Style loader for Petri net visualization.\n *\n * Reads the shared style definition from `spec/petri-net-styles.json` and\n * exposes typed accessors for node and edge visual properties.\n *\n * @module export/styles\n */\n\nimport type { NodeShape, EdgeLineStyle, ArrowHead } from './graph.js';\n\n// ======================== Style Types ========================\n\nexport interface NodeVisual {\n readonly shape: NodeShape;\n readonly fill: string;\n readonly stroke: string;\n readonly penwidth: number;\n readonly style?: string;\n readonly height?: number;\n readonly width?: number;\n}\n\nexport interface EdgeVisual {\n readonly color: string;\n readonly style: EdgeLineStyle;\n readonly arrowhead: ArrowHead;\n readonly penwidth?: number;\n}\n\nexport interface FontStyle {\n readonly family: string;\n readonly nodeSize: number;\n readonly edgeSize: number;\n}\n\nexport interface GraphStyle {\n readonly nodesep: number;\n readonly ranksep: number;\n readonly forcelabels: boolean;\n readonly overlap: boolean;\n readonly outputorder: string;\n}\n\n// ======================== Inline Style Data ========================\n\n// Inlined from spec/petri-net-styles.json to avoid runtime JSON import issues.\n// Keep in sync with the spec file.\n\nconst NODE_STYLES: Record<NodeCategory, NodeVisual> = {\n place: { shape: 'circle', fill: '#FFFFFF', stroke: '#333333', penwidth: 1.5, width: 0.35 },\n start: { shape: 'circle', fill: '#d4edda', stroke: '#28a745', penwidth: 2.0, width: 0.35 },\n end: { shape: 'doublecircle', fill: '#cce5ff', stroke: '#004085', penwidth: 2.0, width: 0.35 },\n environment: { shape: 'circle', fill: '#f8d7da', stroke: '#721c24', penwidth: 2.0, style: 'dashed', width: 0.35 },\n transition: { shape: 'box', fill: '#fff3cd', stroke: '#856404', penwidth: 1.0, height: 0.4, width: 0.8 },\n 'xor-junction': { shape: 'diamond', fill: '#FFFFFF', stroke: '#333333', penwidth: 1.0, height: 0.3, width: 0.3 },\n 'and-junction': { shape: 'diamond', fill: '#FFFFFF', stroke: '#333333', penwidth: 1.0, height: 0.3, width: 0.3 },\n 'interface-port':{ shape: 'circle', fill: '#e7f1ff', stroke: '#1d4ed8', penwidth: 2.0, style: 'dashed', width: 0.35 },\n 'sync-channel': { shape: 'box', fill: '#f3e8ff', stroke: '#6d28d9', penwidth: 1.5, height: 0.4, width: 0.8 },\n};\n\nconst EDGE_STYLES: Record<EdgeCategory, EdgeVisual> = {\n input: { color: '#333333', style: 'solid', arrowhead: 'normal' },\n output: { color: '#333333', style: 'solid', arrowhead: 'normal' },\n inhibitor: { color: '#dc3545', style: 'solid', arrowhead: 'odot' },\n read: { color: '#6c757d', style: 'dashed', arrowhead: 'normal' },\n reset: { color: '#fd7e14', style: 'bold', arrowhead: 'normal', penwidth: 2.0 },\n 'reset-output': { color: '#fd7e14', style: 'bold', arrowhead: 'normal', penwidth: 2.0 },\n};\n\nexport const FONT: FontStyle = { family: 'Helvetica,Arial,sans-serif', nodeSize: 12, edgeSize: 10 };\n\nexport const GRAPH: GraphStyle = { nodesep: 0.5, ranksep: 0.75, forcelabels: true, overlap: false, outputorder: 'edgesfirst' };\n\n// ======================== Public API ========================\n\nexport type NodeCategory = 'place' | 'start' | 'end' | 'environment' | 'transition' | 'xor-junction' | 'and-junction' | 'interface-port' | 'sync-channel';\nexport type EdgeCategory = 'input' | 'output' | 'inhibitor' | 'read' | 'reset' | 'reset-output';\n\n/** Returns the visual style for the given node category. */\nexport function nodeStyle(category: NodeCategory): NodeVisual {\n return NODE_STYLES[category];\n}\n\n/** Returns the visual style for the given edge/arc type. */\nexport function edgeStyle(arcType: EdgeCategory): EdgeVisual {\n return EDGE_STYLES[arcType];\n}\n","/**\n * Prefix-derivation helpers for subnet-instance-aware export and debug\n * tooling, per `spec/11-modular-composition.md` **MOD-040** (export\n * grouping) and **MOD-041** (debug protocol subnet instances).\n *\n * The `\"/\"` character is the prefix separator established by **MOD-010**:\n * a node named `\"outer/inner/leaf\"` belongs to the cluster tree\n * `outer -> outer/inner`, with `\"leaf\"` as the un-prefixed final segment.\n *\n * This module is observability-only — it never inspects {@link\n * import('../core/subnet-def').SubnetDef} or {@link\n * import('../core/instance').Instance} objects, only the resulting\n * prefixed names that survived composition. That keeps consumers (DOT\n * exporter, debug protocol handler) decoupled from the modular-composition\n * layer per **MOD-023**.\n *\n * @module export/subnet-prefixes\n */\n\n/**\n * Returns the instance prefix carried by the given node name, or `undefined`\n * when the name has no `/` (i.e. is not part of any composed subnet\n * instance).\n *\n * The prefix is the substring up to (but not including) the **last** `/`\n * — so `\"producer1/internal\"` returns `\"producer1\"` and\n * `\"outer/inner/leaf\"` returns `\"outer/inner\"`. The bare segment after the\n * last `/` is the original (un-prefixed) place or transition name.\n *\n * @param nodeName the prefixed-or-flat node name\n * @returns the prefix, or `undefined` for flat names\n */\nexport function instancePrefixOf(nodeName: string | null | undefined): string | undefined {\n if (nodeName == null) return undefined;\n const idx = nodeName.lastIndexOf('/');\n if (idx <= 0) return undefined;\n return nodeName.substring(0, idx);\n}\n\n/**\n * Returns the parent prefix of a given prefix, or `undefined` if the prefix\n * is top-level (single segment, no `/`).\n *\n * Used by the debug protocol's {@code SubnetInstance.parentPrefix} field\n * per **MOD-041** for nested instances.\n *\n * @param prefix an instance prefix string (e.g. `\"outer/inner\"`)\n * @returns the parent prefix (e.g. `\"outer\"`), or `undefined` for top-level\n */\nexport function parentOf(prefix: string | null | undefined): string | undefined {\n if (prefix == null || prefix.length === 0) return undefined;\n const idx = prefix.lastIndexOf('/');\n if (idx <= 0) return undefined;\n return prefix.substring(0, idx);\n}\n\n/**\n * Returns the last (leaf) segment of a prefix, used as the cluster label per\n * **MOD-040**. For top-level prefixes the input itself is returned unchanged.\n *\n * @param prefix an instance prefix string\n * @returns the trailing segment after the last `/` (or the input when there\n * is no `/`)\n */\nexport function leafSegment(prefix: string | null | undefined): string | null | undefined {\n if (prefix == null || prefix.length === 0) return prefix;\n const idx = prefix.lastIndexOf('/');\n return idx < 0 ? prefix : prefix.substring(idx + 1);\n}\n","/**\n * Builds nested `subgraph cluster_*` blocks for DOT export per\n * `spec/11-modular-composition.md` **MOD-040** and `spec/09-export.md`\n * **EXP-016**.\n *\n * Given a flat list of nodes and edges plus a `nodeId -> prefix` map\n * produced during mapping (see {@link import('./petri-net-mapper.js').mapToGraph}),\n * this partitioner:\n *\n * 1. Walks every distinct prefix to produce the cluster tree (nested\n * {@link Subgraph} entries).\n * 2. Routes each node into the deepest cluster whose prefix equals the\n * node's prefix; nodes without a prefix stay at the top level.\n * 3. Routes each edge into the deepest cluster that contains **both**\n * endpoints; edges crossing cluster boundaries (or with at least one\n * top-level endpoint) stay at the top level so Graphviz routes them\n * correctly without truncating cluster boxes.\n * 4. Applies a default cluster style (rounded, dashed, light fill) when\n * none is provided — readable defaults sufficient until task #9 layers\n * the doclet styling on top.\n *\n * This partitioner is structural-only: it does not consult any\n * {@link import('../core/subnet-def').SubnetDef} or {@link\n * import('../core/instance').Instance} — it operates exclusively on the\n * flattened post-composition node names per **MOD-023**.\n *\n * @module export/cluster-builder\n */\n\nimport type { GraphEdge, GraphNode, Subgraph } from './graph.js';\nimport { sanitize } from './petri-net-mapper.js';\nimport { parentOf } from './subnet-prefixes.js';\n\n/** Result of partitioning nodes/edges into a clustered hierarchy. */\nexport interface Partition {\n readonly topLevelNodes: readonly GraphNode[];\n readonly topLevelEdges: readonly GraphEdge[];\n readonly topLevelSubgraphs: readonly Subgraph[];\n}\n\n/**\n * Default cluster styling (per [EXP-016] visual contract). These values\n * mirror the `cluster.subnet-cluster` entry in\n * `spec/petri-net-styles.json` so cross-language output stays\n * byte-equivalent.\n */\nexport const DEFAULT_CLUSTER_ATTRS: Readonly<Record<string, string>> = {\n style: 'rounded,dashed',\n bgcolor: '#FAFAFA',\n penwidth: '1.5',\n};\n\ninterface MutableCluster {\n readonly prefix: string;\n readonly nodes: GraphNode[];\n readonly edges: GraphEdge[];\n readonly childPrefixes: string[];\n}\n\n/**\n * Partitions nodes and edges into a cluster hierarchy. Mutates neither input\n * collection.\n *\n * @param nodes all nodes (places, transitions, junctions)\n * @param edges all edges\n * @param nodeIdToPrefix per-node instance prefix; absent entries stay\n * top-level\n * @returns the partitioned cluster hierarchy\n */\nexport function partition(\n nodes: readonly GraphNode[],\n edges: readonly GraphEdge[],\n nodeIdToPrefix: ReadonlyMap<string, string>,\n): Partition {\n // Fast path: no prefixes => no clusters. Preserves byte-equal output for\n // flat (non-composed) nets.\n if (nodeIdToPrefix.size === 0) {\n return {\n topLevelNodes: [...nodes],\n topLevelEdges: [...edges],\n topLevelSubgraphs: [],\n };\n }\n\n // Step 1: walk every prefix to produce the cluster tree shape. We need\n // every internal prefix node, not just leaf prefixes — e.g. a node\n // \"outer/inner/leaf\" requires both \"outer\" and \"outer/inner\" to exist as\n // cluster blocks per [EXP-016] / [MOD-013].\n //\n // We use a Map for insertion-order iteration (deterministic per [EXP-014]).\n const prefixOrder = new Map<string, true>();\n for (const prefix of nodeIdToPrefix.values()) {\n registerPrefixChain(prefix, prefixOrder);\n }\n\n // prefix -> (mutable nodes, mutable edges, mutable child prefixes)\n const prefixState = new Map<string, MutableCluster>();\n for (const prefix of prefixOrder.keys()) {\n prefixState.set(prefix, { prefix, nodes: [], edges: [], childPrefixes: [] });\n }\n // Wire parent -> child relationships.\n for (const prefix of prefixState.keys()) {\n const parent = parentOf(prefix);\n if (parent !== undefined) {\n prefixState.get(parent)!.childPrefixes.push(prefix);\n }\n }\n\n // Step 2: route nodes into their deepest cluster.\n const topLevelNodes: GraphNode[] = [];\n for (const node of nodes) {\n const prefix = nodeIdToPrefix.get(node.id);\n if (prefix === undefined) {\n topLevelNodes.push(node);\n } else {\n prefixState.get(prefix)!.nodes.push(node);\n }\n }\n\n // Step 3: route edges into the deepest cluster containing both endpoints.\n // Cross-cluster edges stay at top-level so Graphviz draws them between\n // cluster boxes rather than inside one.\n const topLevelEdges: GraphEdge[] = [];\n for (const edge of edges) {\n const fromPrefix = nodeIdToPrefix.get(edge.from);\n const toPrefix = nodeIdToPrefix.get(edge.to);\n const common = deepestCommonPrefix(fromPrefix, toPrefix);\n if (common === undefined) {\n topLevelEdges.push(edge);\n } else {\n prefixState.get(common)!.edges.push(edge);\n }\n }\n\n // Step 4: assemble Subgraph records bottom-up so children are built before\n // they're consumed by parents.\n const built = new Map<string, Subgraph>();\n // Reverse the insertion order: parents were inserted before children in\n // registerPrefixChain so iterating reversed produces a deepest-first order.\n const iterationOrder = [...prefixState.keys()].reverse();\n for (const prefix of iterationOrder) {\n const state = prefixState.get(prefix)!;\n const children: Subgraph[] = [];\n for (const childPrefix of state.childPrefixes) {\n const child = built.get(childPrefix);\n if (child !== undefined) {\n children.push(child);\n }\n }\n // Children were built deepest-first, so they're reversed relative to\n // their original insertion order. Restore the original order.\n children.reverse();\n\n const subgraph: Subgraph = {\n id: sanitize(prefix),\n label: prefix,\n nodes: [...state.nodes],\n edges: [...state.edges],\n subgraphs: children,\n attrs: DEFAULT_CLUSTER_ATTRS,\n };\n built.set(prefix, subgraph);\n }\n\n // Top-level subgraphs are those whose prefix has no parent.\n const topLevelSubgraphs: Subgraph[] = [];\n for (const prefix of prefixState.keys()) {\n if (parentOf(prefix) === undefined) {\n topLevelSubgraphs.push(built.get(prefix)!);\n }\n }\n\n return {\n topLevelNodes,\n topLevelEdges,\n topLevelSubgraphs,\n };\n}\n\n/**\n * Walks a prefix root-to-leaf, registering every intermediate prefix in\n * `accumulator` (no-op when already present). Ensures parents are inserted\n * before children.\n */\nfunction registerPrefixChain(prefix: string | undefined, accumulator: Map<string, true>): void {\n if (prefix == null || prefix.length === 0) return;\n const segments = prefix.split('/');\n let built = '';\n for (const seg of segments) {\n if (built.length > 0) built += '/';\n built += seg;\n if (!accumulator.has(built)) {\n accumulator.set(built, true);\n }\n }\n}\n\n/**\n * Returns the deepest prefix that is a (non-strict) ancestor of both sides,\n * or `undefined` when there is no shared cluster (top-level routing).\n */\nfunction deepestCommonPrefix(a: string | undefined, b: string | undefined): string | undefined {\n if (a === undefined || b === undefined) return undefined;\n if (a === b) return a;\n\n const aSegs = a.split('/');\n const bSegs = b.split('/');\n let built = '';\n let matched = 0;\n const min = Math.min(aSegs.length, bSegs.length);\n for (let i = 0; i < min; i++) {\n if (aSegs[i] !== bSegs[i]) break;\n if (matched > 0) built += '/';\n built += aSegs[i];\n matched++;\n }\n return matched === 0 ? undefined : built;\n}\n","/**\n * Maps a PetriNet definition to a format-agnostic Graph.\n *\n * Petri net semantics live here. The mapper applies the visualization rules\n * specified in spec/09-export.md (EXP-012, EXP-013, EXP-014):\n *\n * - XOR / AND output groups with ≥2 children become synthetic junction nodes\n * (diamond for XOR, square for AND).\n * - Output + reset arcs to the same place collapse into a single edge styled\n * as the reset-output category and labelled \"reset+out\".\n * - Junction IDs use the form j_<transition>__<kind>_<idx>, where idx is a\n * depth-first pre-order counter starting at 0.\n *\n * @module export/petri-net-mapper\n */\n\nimport type { PetriNet } from '../core/petri-net.js';\nimport type { Transition } from '../core/transition.js';\nimport type { Out } from '../core/out.js';\nimport { earliest, latest, hasDeadline } from '../core/timing.js';\nimport type { Graph, GraphNode, GraphEdge, RankDir } from './graph.js';\nimport { nodeStyle, edgeStyle, FONT, GRAPH } from './styles.js';\nimport type { NodeCategory } from './styles.js';\nimport { partition } from './cluster-builder.js';\nimport { instancePrefixOf } from './subnet-prefixes.js';\n\n// ======================== Configuration ========================\n\nexport interface DotConfig {\n readonly direction: RankDir;\n readonly showTypes: boolean;\n readonly showIntervals: boolean;\n readonly showPriority: boolean;\n readonly environmentPlaces?: ReadonlySet<string>;\n}\n\nexport const DEFAULT_DOT_CONFIG: DotConfig = {\n direction: 'TB',\n showTypes: true,\n showIntervals: true,\n showPriority: true,\n};\n\n// ======================== Public API ========================\n\n/** Sanitizes a name for use as a graph node ID. */\nexport function sanitize(name: string): string {\n return name.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\n/** Maps a PetriNet to a format-agnostic Graph. */\nexport function mapToGraph(net: PetriNet, config: DotConfig = DEFAULT_DOT_CONFIG): Graph {\n const places = analyzePlaces(net);\n const envNames = config.environmentPlaces ?? new Set<string>();\n\n const nodes: GraphNode[] = [];\n const edges: GraphEdge[] = [];\n\n // Track each emitted node's instance prefix (per [MOD-040]) so we can\n // partition into subgraph clusters at the end. Nodes without a prefix (no\n // '/' in their semantic name) are absent from this map and stay at the top\n // level.\n const nodeIdToPrefix = new Map<string, string>();\n\n // Place nodes\n for (const [name, info] of places) {\n const category = placeCategory(info, envNames.has(name));\n const style = nodeStyle(category);\n const nodeId = 'p_' + sanitize(name);\n nodes.push({\n id: nodeId,\n label: '',\n shape: style.shape,\n fill: style.fill,\n stroke: style.stroke,\n penwidth: style.penwidth,\n semanticId: name,\n style: style.style,\n width: style.width,\n attrs: { xlabel: name, fixedsize: 'true' },\n });\n const prefix = instancePrefixOf(name);\n if (prefix !== undefined) {\n nodeIdToPrefix.set(nodeId, prefix);\n }\n }\n\n // Transition nodes\n for (const t of net.transitions) {\n const style = nodeStyle('transition');\n const tid = 't_' + sanitize(t.name);\n nodes.push({\n id: tid,\n label: transitionLabel(t, config),\n shape: style.shape,\n fill: style.fill,\n stroke: style.stroke,\n penwidth: style.penwidth,\n semanticId: t.name,\n height: style.height,\n width: style.width,\n });\n const prefix = instancePrefixOf(t.name);\n if (prefix !== undefined) {\n nodeIdToPrefix.set(tid, prefix);\n }\n }\n\n // Edges (and junction nodes)\n for (const t of net.transitions) {\n const tid = 't_' + sanitize(t.name);\n const tSanitized = sanitize(t.name);\n // Junctions inherit their parent transition's instance prefix — tracked\n // here so the partition step routes them correctly.\n const tPrefix = instancePrefixOf(t.name);\n const resetPlaces = new Set(t.resets.map(r => r.place.name));\n const combined = new Set<string>();\n\n // Input arcs from inputSpecs\n for (const spec of t.inputSpecs) {\n const pid = 'p_' + sanitize(spec.place.name);\n const inputStyle = edgeStyle('input');\n let label: string | undefined;\n switch (spec.type) {\n case 'exactly':\n label = `×${spec.count}`;\n break;\n case 'all':\n label = '*';\n break;\n case 'at-least':\n label = `≥${spec.minimum}`;\n break;\n }\n edges.push({\n from: pid,\n to: tid,\n label,\n color: inputStyle.color,\n style: inputStyle.style,\n arrowhead: inputStyle.arrowhead,\n arcType: 'input',\n });\n }\n\n // Output arcs from outputSpec — emits junction nodes + edges, marks combined places.\n if (t.outputSpec !== null) {\n const ctx: EmitCtx = {\n tSanitized,\n resetPlaces,\n combined,\n nodes,\n edges,\n counter: 0,\n transitionPrefix: tPrefix,\n nodeIdToPrefix,\n };\n emitOutput(t.outputSpec, tid, null, ctx);\n }\n\n // Inhibitor arcs\n for (const inh of t.inhibitors) {\n const pid = 'p_' + sanitize(inh.place.name);\n const inhStyle = edgeStyle('inhibitor');\n edges.push({\n from: pid,\n to: tid,\n color: inhStyle.color,\n style: inhStyle.style,\n arrowhead: inhStyle.arrowhead,\n arcType: 'inhibitor',\n });\n }\n\n // Read arcs\n for (const r of t.reads) {\n const pid = 'p_' + sanitize(r.place.name);\n const readStyle = edgeStyle('read');\n edges.push({\n from: pid,\n to: tid,\n label: 'read',\n color: readStyle.color,\n style: readStyle.style,\n arrowhead: readStyle.arrowhead,\n arcType: 'read',\n });\n }\n\n // Standalone reset arcs (only those not already combined with an output)\n for (const rst of t.resets) {\n if (!combined.has(rst.place.name)) {\n const pid = 'p_' + sanitize(rst.place.name);\n const resetStyle = edgeStyle('reset');\n edges.push({\n from: tid,\n to: pid,\n label: 'reset',\n color: resetStyle.color,\n style: resetStyle.style,\n arrowhead: resetStyle.arrowhead,\n penwidth: resetStyle.penwidth,\n arcType: 'reset',\n });\n }\n }\n }\n\n // Partition nodes/edges into subgraph clusters per [MOD-040] / [EXP-016].\n // When there are no prefixed names this is a structural no-op and the\n // resulting Graph is byte-identical to the pre-cluster output.\n const partitioned = partition(nodes, edges, nodeIdToPrefix);\n\n return {\n id: sanitize(net.name),\n rankdir: config.direction,\n nodes: partitioned.topLevelNodes,\n edges: partitioned.topLevelEdges,\n subgraphs: partitioned.topLevelSubgraphs,\n graphAttrs: {\n nodesep: String(GRAPH.nodesep),\n ranksep: String(GRAPH.ranksep),\n forcelabels: String(GRAPH.forcelabels),\n overlap: String(GRAPH.overlap),\n fontname: FONT.family,\n outputorder: GRAPH.outputorder,\n },\n nodeDefaults: {\n fontname: FONT.family,\n fontsize: String(FONT.nodeSize),\n },\n edgeDefaults: {\n fontname: FONT.family,\n fontsize: String(FONT.edgeSize),\n },\n };\n}\n\n// ======================== Place Analysis ========================\n\ninterface PlaceInfo {\n hasIncoming: boolean;\n hasOutgoing: boolean;\n}\n\nfunction analyzePlaces(net: PetriNet): Map<string, PlaceInfo> {\n const map = new Map<string, PlaceInfo>();\n\n function ensure(name: string): PlaceInfo {\n let info = map.get(name);\n if (!info) {\n info = { hasIncoming: false, hasOutgoing: false };\n map.set(name, info);\n }\n return info;\n }\n\n for (const t of net.transitions) {\n for (const spec of t.inputSpecs) {\n ensure(spec.place.name).hasOutgoing = true;\n }\n if (t.outputSpec !== null) {\n for (const p of t.outputPlaces()) {\n ensure(p.name).hasIncoming = true;\n }\n }\n for (const inh of t.inhibitors) {\n ensure(inh.place.name);\n }\n for (const r of t.reads) {\n ensure(r.place.name).hasOutgoing = true;\n }\n for (const rst of t.resets) {\n ensure(rst.place.name);\n }\n }\n\n return map;\n}\n\nfunction placeCategory(info: PlaceInfo, isEnvironment: boolean): NodeCategory {\n if (isEnvironment) return 'environment';\n if (!info.hasIncoming) return 'start';\n if (!info.hasOutgoing) return 'end';\n return 'place';\n}\n\n// ======================== Helpers ========================\n\nfunction transitionLabel(t: Transition, config: DotConfig): string {\n const parts = [t.name];\n\n if (config.showIntervals) {\n const e = earliest(t.timing);\n const l = latest(t.timing);\n const max = hasDeadline(t.timing) ? String(l) : '∞';\n parts.push(`[${e}, ${max}]ms`);\n }\n\n if (config.showPriority && t.priority !== 0) {\n parts.push(`prio=${t.priority}`);\n }\n\n return parts.join(' ');\n}\n\n/**\n * Mutable per-transition state threaded through the recursive Out-tree walk.\n *\n * `counter` starts at 0 and increments once per emitted junction (depth-first\n * pre-order). `combined` accumulates place names where a reset+output combination\n * short-circuited the standalone reset edge.\n */\ninterface EmitCtx {\n readonly tSanitized: string;\n readonly resetPlaces: ReadonlySet<string>;\n readonly combined: Set<string>;\n readonly nodes: GraphNode[];\n readonly edges: GraphEdge[];\n counter: number;\n /**\n * Instance prefix (e.g. \"b1\" or \"outer/inner\") of the parent transition —\n * junction nodes inherit this so they live inside the right cluster per\n * [MOD-040]. Undefined for transitions that are not part of any composed\n * instance.\n */\n readonly transitionPrefix: string | undefined;\n /**\n * Shared map populated during mapping; junction emitters add their own\n * entries under transitionPrefix.\n */\n readonly nodeIdToPrefix: Map<string, string>;\n}\n\n/**\n * Emits output edges for an Out tree, inserting junction nodes for XOR/AND\n * groups with ≥2 children. Combined reset+output edges replace plain output\n * edges when a leaf place is also in `ctx.resetPlaces`.\n *\n * @param out current Out subtree\n * @param parentId id of the parent node (transition or junction)\n * @param branchLabel label to apply to the edge entering this Out (timeout/XOR-branch label)\n * @param ctx per-transition junction context (counter, reset set, accumulators)\n */\nfunction emitOutput(out: Out, parentId: string, branchLabel: string | null, ctx: EmitCtx): void {\n switch (out.type) {\n case 'place': {\n const pid = 'p_' + sanitize(out.place.name);\n pushLeafEdge(parentId, pid, out.place.name, branchLabel, ctx, false);\n return;\n }\n\n case 'forward-input': {\n const pid = 'p_' + sanitize(out.to.name);\n const fwdLabel = (branchLabel ? branchLabel + ' ' : '') + '⟵' + out.from.name;\n // ForwardInput is dashed; reset+out combination overrides if applicable.\n pushLeafEdge(parentId, pid, out.to.name, fwdLabel, ctx, true);\n return;\n }\n\n case 'and':\n case 'xor': {\n // Single-child groups collapse: pass through.\n if (out.children.length < 2) {\n if (out.children.length === 1) {\n emitOutput(out.children[0]!, parentId, branchLabel, ctx);\n }\n return;\n }\n\n // Insert junction node — diamond gateway with heavy ✕ / ✚ glyph as discriminator.\n const kind = out.type;\n const idx = ctx.counter++;\n const junctionId = `j_${ctx.tSanitized}__${kind}_${idx}`;\n const category: NodeCategory = kind === 'xor' ? 'xor-junction' : 'and-junction';\n const jStyle = nodeStyle(category);\n ctx.nodes.push({\n id: junctionId,\n label: kind === 'xor' ? '✕' : '✚',\n shape: jStyle.shape,\n fill: jStyle.fill,\n stroke: jStyle.stroke,\n penwidth: jStyle.penwidth,\n semanticId: junctionId,\n height: jStyle.height,\n width: jStyle.width,\n attrs: { fixedsize: 'true', fontsize: '14' },\n });\n // Junctions belong to their parent transition's cluster per [MOD-040].\n if (ctx.transitionPrefix !== undefined) {\n ctx.nodeIdToPrefix.set(junctionId, ctx.transitionPrefix);\n }\n\n // Edge parent → junction (carries any inherited branch/timeout label).\n const outStyle = edgeStyle('output');\n ctx.edges.push({\n from: parentId,\n to: junctionId,\n label: branchLabel ?? undefined,\n color: outStyle.color,\n style: outStyle.style,\n arrowhead: outStyle.arrowhead,\n arcType: 'output',\n });\n\n // Recurse children: XOR junction propagates per-branch labels; AND junction does not.\n for (const child of out.children) {\n const childLabel = kind === 'xor' ? inferBranchLabel(child) : null;\n emitOutput(child, junctionId, childLabel, ctx);\n }\n return;\n }\n\n case 'timeout': {\n // Override any inherited branchLabel: the timeout label fully describes\n // this branch (the XOR pre-inference resolves to the same string).\n const timeoutLabel = `⏱${out.afterMs}ms`;\n emitOutput(out.child, parentId, timeoutLabel, ctx);\n return;\n }\n }\n}\n\nfunction pushLeafEdge(\n fromId: string,\n toId: string,\n placeName: string,\n branchLabel: string | null,\n ctx: EmitCtx,\n isForwardInput: boolean,\n): void {\n if (ctx.resetPlaces.has(placeName)) {\n ctx.combined.add(placeName);\n const ro = edgeStyle('reset-output');\n ctx.edges.push({\n from: fromId,\n to: toId,\n label: 'reset+out',\n color: ro.color,\n style: ro.style,\n arrowhead: ro.arrowhead,\n penwidth: ro.penwidth,\n arcType: 'reset-output',\n });\n return;\n }\n\n const out = edgeStyle('output');\n ctx.edges.push({\n from: fromId,\n to: toId,\n label: branchLabel ?? undefined,\n color: out.color,\n style: isForwardInput ? 'dashed' : out.style,\n arrowhead: out.arrowhead,\n arcType: 'output',\n });\n}\n\nfunction inferBranchLabel(out: Out): string | null {\n switch (out.type) {\n case 'place': return out.place.name;\n case 'timeout': return `⏱${out.afterMs}ms`;\n case 'forward-input': return out.to.name;\n case 'and':\n case 'xor':\n return null;\n }\n}\n","/**\n * Renders a Graph to DOT (Graphviz) string.\n *\n * Pure function with zero Petri net knowledge. Operates solely on the\n * format-agnostic Graph model.\n *\n * @module export/dot-renderer\n */\n\nimport type { Graph, GraphNode, GraphEdge, Subgraph } from './graph.js';\n\n// ======================== Public API ========================\n\n/** Renders a Graph to a DOT string suitable for Graphviz. */\nexport function renderDot(graph: Graph): string {\n const lines: string[] = [];\n\n lines.push(`digraph ${quoteId(graph.id)} {`);\n\n // Graph attributes\n lines.push(` rankdir=${graph.rankdir};`);\n for (const [key, value] of Object.entries(graph.graphAttrs)) {\n lines.push(` ${key}=${quoteAttr(value)};`);\n }\n\n // Node defaults\n if (Object.keys(graph.nodeDefaults).length > 0) {\n lines.push(` node [${formatAttrs(graph.nodeDefaults)}];`);\n }\n\n // Edge defaults\n if (Object.keys(graph.edgeDefaults).length > 0) {\n lines.push(` edge [${formatAttrs(graph.edgeDefaults)}];`);\n }\n\n lines.push('');\n\n // Subgraphs\n for (const sg of graph.subgraphs) {\n lines.push(...renderSubgraph(sg, ' '));\n lines.push('');\n }\n\n // Nodes\n for (const node of graph.nodes) {\n lines.push(` ${renderNode(node)}`);\n }\n\n if (graph.nodes.length > 0) {\n lines.push('');\n }\n\n // Edges\n for (const edge of graph.edges) {\n lines.push(` ${renderEdge(edge)}`);\n }\n\n lines.push('}');\n\n return lines.join('\\n');\n}\n\n// ======================== Internal Rendering ========================\n\nfunction renderNode(node: GraphNode): string {\n const attrs: Record<string, string> = {\n label: node.label,\n shape: node.shape,\n style: node.style ? `\"filled,${node.style}\"` : 'filled',\n fillcolor: node.fill,\n color: node.stroke,\n penwidth: String(node.penwidth),\n };\n\n if (node.height !== undefined) {\n attrs['height'] = String(node.height);\n }\n if (node.width !== undefined) {\n attrs['width'] = String(node.width);\n }\n\n // Merge extra attrs\n if (node.attrs) {\n for (const [key, value] of Object.entries(node.attrs)) {\n attrs[key] = value;\n }\n }\n\n return `${quoteId(node.id)} [${formatNodeAttrs(attrs)}];`;\n}\n\nfunction renderEdge(edge: GraphEdge): string {\n const attrs: Record<string, string> = {\n color: edge.color,\n style: edge.style,\n arrowhead: edge.arrowhead,\n };\n\n if (edge.label !== undefined) {\n attrs['label'] = edge.label;\n }\n if (edge.penwidth !== undefined) {\n attrs['penwidth'] = String(edge.penwidth);\n }\n\n // Merge extra attrs\n if (edge.attrs) {\n for (const [key, value] of Object.entries(edge.attrs)) {\n attrs[key] = value;\n }\n }\n\n return `${quoteId(edge.from)} -> ${quoteId(edge.to)} [${formatNodeAttrs(attrs)}];`;\n}\n\nfunction renderSubgraph(sg: Subgraph, indent: string): string[] {\n const lines: string[] = [];\n lines.push(`${indent}subgraph ${quoteId('cluster_' + sg.id)} {`);\n\n if (sg.label !== undefined) {\n lines.push(`${indent} label=${quoteAttr(sg.label)};`);\n }\n\n if (sg.attrs) {\n for (const [key, value] of Object.entries(sg.attrs)) {\n lines.push(`${indent} ${key}=${quoteAttr(value)};`);\n }\n }\n\n for (const node of sg.nodes) {\n lines.push(`${indent} ${renderNode(node)}`);\n }\n\n // Nested clusters per EXP-016 / MOD-040.\n if (sg.subgraphs) {\n for (const nested of sg.subgraphs) {\n lines.push(...renderSubgraph(nested, indent + ' '));\n }\n }\n\n // Intra-cluster edges (both endpoints inside this cluster) — placed inside\n // the cluster so Graphviz routes them correctly. Cross-cluster edges live\n // on the top-level Graph.edges list.\n if (sg.edges) {\n for (const e of sg.edges) {\n lines.push(`${indent} ${renderEdge(e)}`);\n }\n }\n\n lines.push(`${indent}}`);\n return lines;\n}\n\n// ======================== DOT Quoting ========================\n\n/** Quotes a DOT identifier. Always quotes to be safe with special chars. */\nfunction quoteId(id: string): string {\n // DOT keywords that must be quoted\n if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(id) && !isDotKeyword(id)) {\n return id;\n }\n return `\"${escapeDot(id)}\"`;\n}\n\n/** Quotes a DOT attribute value. */\nfunction quoteAttr(value: string): string {\n // Numbers don't need quoting\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return value;\n }\n return `\"${escapeDot(value)}\"`;\n}\n\n/** Escapes special characters for DOT strings. */\nfunction escapeDot(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Formats node/edge attributes where certain values (like style with commas)\n * need special handling.\n */\nfunction formatNodeAttrs(attrs: Record<string, string>): string {\n return Object.entries(attrs)\n .map(([key, value]) => {\n // Style values that are already pre-quoted (contain the quote char)\n if (value.startsWith('\"') && value.endsWith('\"')) {\n return `${key}=${value}`;\n }\n return `${key}=${quoteAttr(value)}`;\n })\n .join(', ');\n}\n\n/** Formats simple key=value attributes. */\nfunction formatAttrs(attrs: Readonly<Record<string, string>>): string {\n return Object.entries(attrs)\n .map(([key, value]) => `${key}=${quoteAttr(value)}`)\n .join(', ');\n}\n\n/** DOT language keywords that must be quoted when used as identifiers. */\nfunction isDotKeyword(id: string): boolean {\n const lower = id.toLowerCase();\n return lower === 'graph' || lower === 'digraph' || lower === 'subgraph'\n || lower === 'node' || lower === 'edge' || lower === 'strict';\n}\n","/**\n * Convenience function for exporting a PetriNet to DOT format.\n *\n * @module export/dot-exporter\n */\n\nimport type { PetriNet } from '../core/petri-net.js';\nimport type { DotConfig } from './petri-net-mapper.js';\nimport { mapToGraph } from './petri-net-mapper.js';\nimport { renderDot } from './dot-renderer.js';\n\n/**\n * Exports a PetriNet to DOT (Graphviz) format.\n *\n * @param net the Petri net to export\n * @param config optional export configuration\n * @returns DOT string suitable for rendering with Graphviz\n */\nexport function dotExport(net: PetriNet, config?: DotConfig): string {\n return renderDot(mapToGraph(net, config));\n}\n"],"mappings":"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","names":[]}
|