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.
Files changed (41) hide show
  1. package/README.md +47 -0
  2. package/dist/{chunk-B2D5DMTO.js → chunk-4L6JVKH4.js} +165 -8
  3. package/dist/chunk-4L6JVKH4.js.map +1 -0
  4. package/dist/chunk-H62Z76FY.js +1346 -0
  5. package/dist/chunk-H62Z76FY.js.map +1 -0
  6. package/dist/chunk-SXK2Z45Z.js +50 -0
  7. package/dist/chunk-SXK2Z45Z.js.map +1 -0
  8. package/dist/debug/index.d.ts +50 -3
  9. package/dist/debug/index.js +64 -6
  10. package/dist/debug/index.js.map +1 -1
  11. package/dist/doclet/index.d.ts +152 -31
  12. package/dist/doclet/index.js +458 -57
  13. package/dist/doclet/index.js.map +1 -1
  14. package/dist/doclet/resources/petrinet-diagrams.css +384 -7
  15. package/dist/doclet/resources/petrinet-diagrams.js +7214 -106
  16. package/dist/dot-exporter-PMHOQHZ3.js +8 -0
  17. package/dist/{event-store-BnyHh3TF.d.ts → event-store-2zkXeQkd.d.ts} +1 -1
  18. package/dist/export/index.d.ts +16 -2
  19. package/dist/export/index.js +1 -1
  20. package/dist/index.d.ts +41 -5
  21. package/dist/index.js +1221 -35
  22. package/dist/index.js.map +1 -1
  23. package/dist/petri-net-BDrj4XZE.d.ts +1461 -0
  24. package/dist/render-P6GROU7J.js +16 -0
  25. package/dist/render-P6GROU7J.js.map +1 -0
  26. package/dist/verification/index.d.ts +3 -144
  27. package/dist/verification/index.js +30 -1214
  28. package/dist/verification/index.js.map +1 -1
  29. package/dist/viewer/index.d.ts +196 -0
  30. package/dist/viewer/index.js +442 -0
  31. package/dist/viewer/index.js.map +1 -0
  32. package/dist/viewer/viewer-static.iife.js +3 -0
  33. package/dist/viewer/viewer.css +503 -0
  34. package/dist/viewer/viewer.iife.js +7219 -0
  35. package/package.json +17 -8
  36. package/dist/chunk-B2D5DMTO.js.map +0 -1
  37. package/dist/chunk-VQ4XMJTD.js +0 -107
  38. package/dist/chunk-VQ4XMJTD.js.map +0 -1
  39. package/dist/dot-exporter-3CVCH6J4.js +0 -8
  40. package/dist/petri-net-D-GN9g_D.d.ts +0 -570
  41. /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: "p_" + sanitize(name),
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: "t_" + sanitize(t.name),
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-B2D5DMTO.js.map
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":";;;;;;;AAoDA,IAAM,cAAgD;AAAA,EACpD,OAAiB,EAAE,OAAO,UAAW,MAAM,WAAW,QAAQ,WAAW,UAAU,KAAK,OAAO,KAAK;AAAA,EACpG,OAAiB,EAAE,OAAO,UAAW,MAAM,WAAW,QAAQ,WAAW,UAAU,GAAK,OAAO,KAAK;AAAA,EACpG,KAAiB,EAAE,OAAO,gBAAiB,MAAM,WAAW,QAAQ,WAAW,UAAU,GAAK,OAAO,KAAK;AAAA,EAC1G,aAAiB,EAAE,OAAO,UAAW,MAAM,WAAW,QAAQ,WAAW,UAAU,GAAK,OAAO,UAAU,OAAO,KAAK;AAAA,EACrH,YAAiB,EAAE,OAAO,OAAQ,MAAM,WAAW,QAAQ,WAAW,UAAU,GAAK,QAAQ,KAAK,OAAO,IAAI;AAAA,EAC7G,gBAAiB,EAAE,OAAO,WAAY,MAAM,WAAW,QAAQ,WAAW,UAAU,GAAK,QAAQ,KAAK,OAAO,IAAI;AAAA,EACjH,gBAAiB,EAAE,OAAO,WAAY,MAAM,WAAW,QAAQ,WAAW,UAAU,GAAK,QAAQ,KAAK,OAAO,IAAI;AAAA,EACjH,kBAAiB,EAAE,OAAO,UAAW,MAAM,WAAW,QAAQ,WAAW,UAAU,GAAK,OAAO,UAAU,OAAO,KAAK;AAAA,EACrH,gBAAiB,EAAE,OAAO,OAAQ,MAAM,WAAW,QAAQ,WAAW,UAAU,KAAK,QAAQ,KAAK,OAAO,IAAI;AAC/G;AAEA,IAAM,cAAgD;AAAA,EACpD,OAAe,EAAE,OAAO,WAAW,OAAO,SAAU,WAAW,SAAS;AAAA,EACxE,QAAe,EAAE,OAAO,WAAW,OAAO,SAAU,WAAW,SAAS;AAAA,EACxE,WAAe,EAAE,OAAO,WAAW,OAAO,SAAU,WAAW,OAAO;AAAA,EACtE,MAAe,EAAE,OAAO,WAAW,OAAO,UAAW,WAAW,SAAS;AAAA,EACzE,OAAe,EAAE,OAAO,WAAW,OAAO,QAAS,WAAW,UAAW,UAAU,EAAI;AAAA,EACvF,gBAAgB,EAAE,OAAO,WAAW,OAAO,QAAS,WAAW,UAAW,UAAU,EAAI;AAC1F;AAEO,IAAM,OAAkB,EAAE,QAAQ,8BAA8B,UAAU,IAAI,UAAU,GAAG;AAE3F,IAAM,QAAoB,EAAE,SAAS,KAAK,SAAS,MAAM,aAAa,MAAM,SAAS,OAAO,aAAa,aAAa;AAQtH,SAAS,UAAU,UAAoC;AAC5D,SAAO,YAAY,QAAQ;AAC7B;AAGO,SAAS,UAAU,SAAmC;AAC3D,SAAO,YAAY,OAAO;AAC5B;;;AC1DO,SAAS,iBAAiB,UAAyD;AACxF,MAAI,YAAY,KAAM,QAAO;AAC7B,QAAM,MAAM,SAAS,YAAY,GAAG;AACpC,MAAI,OAAO,EAAG,QAAO;AACrB,SAAO,SAAS,UAAU,GAAG,GAAG;AAClC;AAYO,SAAS,SAAS,QAAuD;AAC9E,MAAI,UAAU,QAAQ,OAAO,WAAW,EAAG,QAAO;AAClD,QAAM,MAAM,OAAO,YAAY,GAAG;AAClC,MAAI,OAAO,EAAG,QAAO;AACrB,SAAO,OAAO,UAAU,GAAG,GAAG;AAChC;;;ACRO,IAAM,wBAA0D;AAAA,EACrE,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AACZ;AAmBO,SAAS,UACd,OACA,OACA,gBACW;AAGX,MAAI,eAAe,SAAS,GAAG;AAC7B,WAAO;AAAA,MACL,eAAe,CAAC,GAAG,KAAK;AAAA,MACxB,eAAe,CAAC,GAAG,KAAK;AAAA,MACxB,mBAAmB,CAAC;AAAA,IACtB;AAAA,EACF;AAQA,QAAM,cAAc,oBAAI,IAAkB;AAC1C,aAAW,UAAU,eAAe,OAAO,GAAG;AAC5C,wBAAoB,QAAQ,WAAW;AAAA,EACzC;AAGA,QAAM,cAAc,oBAAI,IAA4B;AACpD,aAAW,UAAU,YAAY,KAAK,GAAG;AACvC,gBAAY,IAAI,QAAQ,EAAE,QAAQ,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,eAAe,CAAC,EAAE,CAAC;AAAA,EAC7E;AAEA,aAAW,UAAU,YAAY,KAAK,GAAG;AACvC,UAAM,SAAS,SAAS,MAAM;AAC9B,QAAI,WAAW,QAAW;AACxB,kBAAY,IAAI,MAAM,EAAG,cAAc,KAAK,MAAM;AAAA,IACpD;AAAA,EACF;AAGA,QAAM,gBAA6B,CAAC;AACpC,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,eAAe,IAAI,KAAK,EAAE;AACzC,QAAI,WAAW,QAAW;AACxB,oBAAc,KAAK,IAAI;AAAA,IACzB,OAAO;AACL,kBAAY,IAAI,MAAM,EAAG,MAAM,KAAK,IAAI;AAAA,IAC1C;AAAA,EACF;AAKA,QAAM,gBAA6B,CAAC;AACpC,aAAW,QAAQ,OAAO;AACxB,UAAM,aAAa,eAAe,IAAI,KAAK,IAAI;AAC/C,UAAM,WAAW,eAAe,IAAI,KAAK,EAAE;AAC3C,UAAM,SAAS,oBAAoB,YAAY,QAAQ;AACvD,QAAI,WAAW,QAAW;AACxB,oBAAc,KAAK,IAAI;AAAA,IACzB,OAAO;AACL,kBAAY,IAAI,MAAM,EAAG,MAAM,KAAK,IAAI;AAAA,IAC1C;AAAA,EACF;AAIA,QAAM,QAAQ,oBAAI,IAAsB;AAGxC,QAAM,iBAAiB,CAAC,GAAG,YAAY,KAAK,CAAC,EAAE,QAAQ;AACvD,aAAW,UAAU,gBAAgB;AACnC,UAAM,QAAQ,YAAY,IAAI,MAAM;AACpC,UAAM,WAAuB,CAAC;AAC9B,eAAW,eAAe,MAAM,eAAe;AAC7C,YAAM,QAAQ,MAAM,IAAI,WAAW;AACnC,UAAI,UAAU,QAAW;AACvB,iBAAS,KAAK,KAAK;AAAA,MACrB;AAAA,IACF;AAGA,aAAS,QAAQ;AAEjB,UAAM,WAAqB;AAAA,MACzB,IAAI,SAAS,MAAM;AAAA,MACnB,OAAO;AAAA,MACP,OAAO,CAAC,GAAG,MAAM,KAAK;AAAA,MACtB,OAAO,CAAC,GAAG,MAAM,KAAK;AAAA,MACtB,WAAW;AAAA,MACX,OAAO;AAAA,IACT;AACA,UAAM,IAAI,QAAQ,QAAQ;AAAA,EAC5B;AAGA,QAAM,oBAAgC,CAAC;AACvC,aAAW,UAAU,YAAY,KAAK,GAAG;AACvC,QAAI,SAAS,MAAM,MAAM,QAAW;AAClC,wBAAkB,KAAK,MAAM,IAAI,MAAM,CAAE;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAOA,SAAS,oBAAoB,QAA4B,aAAsC;AAC7F,MAAI,UAAU,QAAQ,OAAO,WAAW,EAAG;AAC3C,QAAM,WAAW,OAAO,MAAM,GAAG;AACjC,MAAI,QAAQ;AACZ,aAAW,OAAO,UAAU;AAC1B,QAAI,MAAM,SAAS,EAAG,UAAS;AAC/B,aAAS;AACT,QAAI,CAAC,YAAY,IAAI,KAAK,GAAG;AAC3B,kBAAY,IAAI,OAAO,IAAI;AAAA,IAC7B;AAAA,EACF;AACF;AAMA,SAAS,oBAAoB,GAAuB,GAA2C;AAC7F,MAAI,MAAM,UAAa,MAAM,OAAW,QAAO;AAC/C,MAAI,MAAM,EAAG,QAAO;AAEpB,QAAM,QAAQ,EAAE,MAAM,GAAG;AACzB,QAAM,QAAQ,EAAE,MAAM,GAAG;AACzB,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,QAAM,MAAM,KAAK,IAAI,MAAM,QAAQ,MAAM,MAAM;AAC/C,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,QAAI,MAAM,CAAC,MAAM,MAAM,CAAC,EAAG;AAC3B,QAAI,UAAU,EAAG,UAAS;AAC1B,aAAS,MAAM,CAAC;AAChB;AAAA,EACF;AACA,SAAO,YAAY,IAAI,SAAY;AACrC;;;ACrLO,IAAM,qBAAgC;AAAA,EAC3C,WAAW;AAAA,EACX,WAAW;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAChB;AAKO,SAAS,SAAS,MAAsB;AAC7C,SAAO,KAAK,QAAQ,kBAAkB,GAAG;AAC3C;AAGO,SAAS,WAAW,KAAe,SAAoB,oBAA2B;AACvF,QAAM,SAAS,cAAc,GAAG;AAChC,QAAM,WAAW,OAAO,qBAAqB,oBAAI,IAAY;AAE7D,QAAM,QAAqB,CAAC;AAC5B,QAAM,QAAqB,CAAC;AAM5B,QAAM,iBAAiB,oBAAI,IAAoB;AAG/C,aAAW,CAAC,MAAM,IAAI,KAAK,QAAQ;AACjC,UAAM,WAAW,cAAc,MAAM,SAAS,IAAI,IAAI,CAAC;AACvD,UAAM,QAAQ,UAAU,QAAQ;AAChC,UAAM,SAAS,OAAO,SAAS,IAAI;AACnC,UAAM,KAAK;AAAA,MACT,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO,MAAM;AAAA,MACb,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,UAAU,MAAM;AAAA,MAChB,YAAY;AAAA,MACZ,OAAO,MAAM;AAAA,MACb,OAAO,MAAM;AAAA,MACb,OAAO,EAAE,QAAQ,MAAM,WAAW,OAAO;AAAA,IAC3C,CAAC;AACD,UAAM,SAAS,iBAAiB,IAAI;AACpC,QAAI,WAAW,QAAW;AACxB,qBAAe,IAAI,QAAQ,MAAM;AAAA,IACnC;AAAA,EACF;AAGA,aAAW,KAAK,IAAI,aAAa;AAC/B,UAAM,QAAQ,UAAU,YAAY;AACpC,UAAM,MAAM,OAAO,SAAS,EAAE,IAAI;AAClC,UAAM,KAAK;AAAA,MACT,IAAI;AAAA,MACJ,OAAO,gBAAgB,GAAG,MAAM;AAAA,MAChC,OAAO,MAAM;AAAA,MACb,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,UAAU,MAAM;AAAA,MAChB,YAAY,EAAE;AAAA,MACd,QAAQ,MAAM;AAAA,MACd,OAAO,MAAM;AAAA,IACf,CAAC;AACD,UAAM,SAAS,iBAAiB,EAAE,IAAI;AACtC,QAAI,WAAW,QAAW;AACxB,qBAAe,IAAI,KAAK,MAAM;AAAA,IAChC;AAAA,EACF;AAGA,aAAW,KAAK,IAAI,aAAa;AAC/B,UAAM,MAAM,OAAO,SAAS,EAAE,IAAI;AAClC,UAAM,aAAa,SAAS,EAAE,IAAI;AAGlC,UAAM,UAAU,iBAAiB,EAAE,IAAI;AACvC,UAAM,cAAc,IAAI,IAAI,EAAE,OAAO,IAAI,OAAK,EAAE,MAAM,IAAI,CAAC;AAC3D,UAAM,WAAW,oBAAI,IAAY;AAGjC,eAAW,QAAQ,EAAE,YAAY;AAC/B,YAAM,MAAM,OAAO,SAAS,KAAK,MAAM,IAAI;AAC3C,YAAM,aAAa,UAAU,OAAO;AACpC,UAAI;AACJ,cAAQ,KAAK,MAAM;AAAA,QACjB,KAAK;AACH,kBAAQ,OAAI,KAAK,KAAK;AACtB;AAAA,QACF,KAAK;AACH,kBAAQ;AACR;AAAA,QACF,KAAK;AACH,kBAAQ,SAAI,KAAK,OAAO;AACxB;AAAA,MACJ;AACA,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,IAAI;AAAA,QACJ;AAAA,QACA,OAAO,WAAW;AAAA,QAClB,OAAO,WAAW;AAAA,QAClB,WAAW,WAAW;AAAA,QACtB,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAGA,QAAI,EAAE,eAAe,MAAM;AACzB,YAAM,MAAe;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,kBAAkB;AAAA,QAClB;AAAA,MACF;AACA,iBAAW,EAAE,YAAY,KAAK,MAAM,GAAG;AAAA,IACzC;AAGA,eAAW,OAAO,EAAE,YAAY;AAC9B,YAAM,MAAM,OAAO,SAAS,IAAI,MAAM,IAAI;AAC1C,YAAM,WAAW,UAAU,WAAW;AACtC,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,OAAO,SAAS;AAAA,QAChB,OAAO,SAAS;AAAA,QAChB,WAAW,SAAS;AAAA,QACpB,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAGA,eAAW,KAAK,EAAE,OAAO;AACvB,YAAM,MAAM,OAAO,SAAS,EAAE,MAAM,IAAI;AACxC,YAAM,YAAY,UAAU,MAAM;AAClC,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,OAAO,UAAU;AAAA,QACjB,OAAO,UAAU;AAAA,QACjB,WAAW,UAAU;AAAA,QACrB,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAGA,eAAW,OAAO,EAAE,QAAQ;AAC1B,UAAI,CAAC,SAAS,IAAI,IAAI,MAAM,IAAI,GAAG;AACjC,cAAM,MAAM,OAAO,SAAS,IAAI,MAAM,IAAI;AAC1C,cAAM,aAAa,UAAU,OAAO;AACpC,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,OAAO,WAAW;AAAA,UAClB,OAAO,WAAW;AAAA,UAClB,WAAW,WAAW;AAAA,UACtB,UAAU,WAAW;AAAA,UACrB,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAKA,QAAM,cAAc,UAAU,OAAO,OAAO,cAAc;AAE1D,SAAO;AAAA,IACL,IAAI,SAAS,IAAI,IAAI;AAAA,IACrB,SAAS,OAAO;AAAA,IAChB,OAAO,YAAY;AAAA,IACnB,OAAO,YAAY;AAAA,IACnB,WAAW,YAAY;AAAA,IACvB,YAAY;AAAA,MACV,SAAS,OAAO,MAAM,OAAO;AAAA,MAC7B,SAAS,OAAO,MAAM,OAAO;AAAA,MAC7B,aAAa,OAAO,MAAM,WAAW;AAAA,MACrC,SAAS,OAAO,MAAM,OAAO;AAAA,MAC7B,UAAU,KAAK;AAAA,MACf,aAAa,MAAM;AAAA,IACrB;AAAA,IACA,cAAc;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,UAAU,OAAO,KAAK,QAAQ;AAAA,IAChC;AAAA,IACA,cAAc;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,UAAU,OAAO,KAAK,QAAQ;AAAA,IAChC;AAAA,EACF;AACF;AASA,SAAS,cAAc,KAAuC;AAC5D,QAAM,MAAM,oBAAI,IAAuB;AAEvC,WAAS,OAAO,MAAyB;AACvC,QAAI,OAAO,IAAI,IAAI,IAAI;AACvB,QAAI,CAAC,MAAM;AACT,aAAO,EAAE,aAAa,OAAO,aAAa,MAAM;AAChD,UAAI,IAAI,MAAM,IAAI;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AAEA,aAAW,KAAK,IAAI,aAAa;AAC/B,eAAW,QAAQ,EAAE,YAAY;AAC/B,aAAO,KAAK,MAAM,IAAI,EAAE,cAAc;AAAA,IACxC;AACA,QAAI,EAAE,eAAe,MAAM;AACzB,iBAAW,KAAK,EAAE,aAAa,GAAG;AAChC,eAAO,EAAE,IAAI,EAAE,cAAc;AAAA,MAC/B;AAAA,IACF;AACA,eAAW,OAAO,EAAE,YAAY;AAC9B,aAAO,IAAI,MAAM,IAAI;AAAA,IACvB;AACA,eAAW,KAAK,EAAE,OAAO;AACvB,aAAO,EAAE,MAAM,IAAI,EAAE,cAAc;AAAA,IACrC;AACA,eAAW,OAAO,EAAE,QAAQ;AAC1B,aAAO,IAAI,MAAM,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,cAAc,MAAiB,eAAsC;AAC5E,MAAI,cAAe,QAAO;AAC1B,MAAI,CAAC,KAAK,YAAa,QAAO;AAC9B,MAAI,CAAC,KAAK,YAAa,QAAO;AAC9B,SAAO;AACT;AAIA,SAAS,gBAAgB,GAAe,QAA2B;AACjE,QAAM,QAAQ,CAAC,EAAE,IAAI;AAErB,MAAI,OAAO,eAAe;AACxB,UAAM,IAAI,SAAS,EAAE,MAAM;AAC3B,UAAM,IAAI,OAAO,EAAE,MAAM;AACzB,UAAM,MAAM,YAAY,EAAE,MAAM,IAAI,OAAO,CAAC,IAAI;AAChD,UAAM,KAAK,IAAI,CAAC,KAAK,GAAG,KAAK;AAAA,EAC/B;AAEA,MAAI,OAAO,gBAAgB,EAAE,aAAa,GAAG;AAC3C,UAAM,KAAK,QAAQ,EAAE,QAAQ,EAAE;AAAA,EACjC;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;AAwCA,SAAS,WAAW,KAAU,UAAkB,aAA4B,KAAoB;AAC9F,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK,SAAS;AACZ,YAAM,MAAM,OAAO,SAAS,IAAI,MAAM,IAAI;AAC1C,mBAAa,UAAU,KAAK,IAAI,MAAM,MAAM,aAAa,KAAK,KAAK;AACnE;AAAA,IACF;AAAA,IAEA,KAAK,iBAAiB;AACpB,YAAM,MAAM,OAAO,SAAS,IAAI,GAAG,IAAI;AACvC,YAAM,YAAY,cAAc,cAAc,MAAM,MAAM,WAAM,IAAI,KAAK;AAEzE,mBAAa,UAAU,KAAK,IAAI,GAAG,MAAM,UAAU,KAAK,IAAI;AAC5D;AAAA,IACF;AAAA,IAEA,KAAK;AAAA,IACL,KAAK,OAAO;AAEV,UAAI,IAAI,SAAS,SAAS,GAAG;AAC3B,YAAI,IAAI,SAAS,WAAW,GAAG;AAC7B,qBAAW,IAAI,SAAS,CAAC,GAAI,UAAU,aAAa,GAAG;AAAA,QACzD;AACA;AAAA,MACF;AAGA,YAAM,OAAO,IAAI;AACjB,YAAM,MAAM,IAAI;AAChB,YAAM,aAAa,KAAK,IAAI,UAAU,KAAK,IAAI,IAAI,GAAG;AACtD,YAAM,WAAyB,SAAS,QAAQ,iBAAiB;AACjE,YAAM,SAAS,UAAU,QAAQ;AACjC,UAAI,MAAM,KAAK;AAAA,QACb,IAAI;AAAA,QACJ,OAAO,SAAS,QAAQ,WAAM;AAAA,QAC9B,OAAO,OAAO;AAAA,QACd,MAAM,OAAO;AAAA,QACb,QAAQ,OAAO;AAAA,QACf,UAAU,OAAO;AAAA,QACjB,YAAY;AAAA,QACZ,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,QACd,OAAO,EAAE,WAAW,QAAQ,UAAU,KAAK;AAAA,MAC7C,CAAC;AAED,UAAI,IAAI,qBAAqB,QAAW;AACtC,YAAI,eAAe,IAAI,YAAY,IAAI,gBAAgB;AAAA,MACzD;AAGA,YAAM,WAAW,UAAU,QAAQ;AACnC,UAAI,MAAM,KAAK;AAAA,QACb,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,OAAO,eAAe;AAAA,QACtB,OAAO,SAAS;AAAA,QAChB,OAAO,SAAS;AAAA,QAChB,WAAW,SAAS;AAAA,QACpB,SAAS;AAAA,MACX,CAAC;AAGD,iBAAW,SAAS,IAAI,UAAU;AAChC,cAAM,aAAa,SAAS,QAAQ,iBAAiB,KAAK,IAAI;AAC9D,mBAAW,OAAO,YAAY,YAAY,GAAG;AAAA,MAC/C;AACA;AAAA,IACF;AAAA,IAEA,KAAK,WAAW;AAGd,YAAM,eAAe,SAAI,IAAI,OAAO;AACpC,iBAAW,IAAI,OAAO,UAAU,cAAc,GAAG;AACjD;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,aACP,QACA,MACA,WACA,aACA,KACA,gBACM;AACN,MAAI,IAAI,YAAY,IAAI,SAAS,GAAG;AAClC,QAAI,SAAS,IAAI,SAAS;AAC1B,UAAM,KAAK,UAAU,cAAc;AACnC,QAAI,MAAM,KAAK;AAAA,MACb,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO,GAAG;AAAA,MACV,OAAO,GAAG;AAAA,MACV,WAAW,GAAG;AAAA,MACd,UAAU,GAAG;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AACD;AAAA,EACF;AAEA,QAAM,MAAM,UAAU,QAAQ;AAC9B,MAAI,MAAM,KAAK;AAAA,IACb,MAAM;AAAA,IACN,IAAI;AAAA,IACJ,OAAO,eAAe;AAAA,IACtB,OAAO,IAAI;AAAA,IACX,OAAO,iBAAiB,WAAW,IAAI;AAAA,IACvC,WAAW,IAAI;AAAA,IACf,SAAS;AAAA,EACX,CAAC;AACH;AAEA,SAAS,iBAAiB,KAAyB;AACjD,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AAAS,aAAO,IAAI,MAAM;AAAA,IAC/B,KAAK;AAAW,aAAO,SAAI,IAAI,OAAO;AAAA,IACtC,KAAK;AAAiB,aAAO,IAAI,GAAG;AAAA,IACpC,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,EACX;AACF;;;ACtcO,SAAS,UAAU,OAAsB;AAC9C,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,WAAW,QAAQ,MAAM,EAAE,CAAC,IAAI;AAG3C,QAAM,KAAK,eAAe,MAAM,OAAO,GAAG;AAC1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,UAAU,GAAG;AAC3D,UAAM,KAAK,OAAO,GAAG,IAAI,UAAU,KAAK,CAAC,GAAG;AAAA,EAC9C;AAGA,MAAI,OAAO,KAAK,MAAM,YAAY,EAAE,SAAS,GAAG;AAC9C,UAAM,KAAK,aAAa,YAAY,MAAM,YAAY,CAAC,IAAI;AAAA,EAC7D;AAGA,MAAI,OAAO,KAAK,MAAM,YAAY,EAAE,SAAS,GAAG;AAC9C,UAAM,KAAK,aAAa,YAAY,MAAM,YAAY,CAAC,IAAI;AAAA,EAC7D;AAEA,QAAM,KAAK,EAAE;AAGb,aAAW,MAAM,MAAM,WAAW;AAChC,UAAM,KAAK,GAAG,eAAe,IAAI,MAAM,CAAC;AACxC,UAAM,KAAK,EAAE;AAAA,EACf;AAGA,aAAW,QAAQ,MAAM,OAAO;AAC9B,UAAM,KAAK,OAAO,WAAW,IAAI,CAAC,EAAE;AAAA,EACtC;AAEA,MAAI,MAAM,MAAM,SAAS,GAAG;AAC1B,UAAM,KAAK,EAAE;AAAA,EACf;AAGA,aAAW,QAAQ,MAAM,OAAO;AAC9B,UAAM,KAAK,OAAO,WAAW,IAAI,CAAC,EAAE;AAAA,EACtC;AAEA,QAAM,KAAK,GAAG;AAEd,SAAO,MAAM,KAAK,IAAI;AACxB;AAIA,SAAS,WAAW,MAAyB;AAC3C,QAAM,QAAgC;AAAA,IACpC,OAAO,KAAK;AAAA,IACZ,OAAO,KAAK;AAAA,IACZ,OAAO,KAAK,QAAQ,WAAW,KAAK,KAAK,MAAM;AAAA,IAC/C,WAAW,KAAK;AAAA,IAChB,OAAO,KAAK;AAAA,IACZ,UAAU,OAAO,KAAK,QAAQ;AAAA,EAChC;AAEA,MAAI,KAAK,WAAW,QAAW;AAC7B,UAAM,QAAQ,IAAI,OAAO,KAAK,MAAM;AAAA,EACtC;AACA,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,OAAO,IAAI,OAAO,KAAK,KAAK;AAAA,EACpC;AAGA,MAAI,KAAK,OAAO;AACd,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AACrD,YAAM,GAAG,IAAI;AAAA,IACf;AAAA,EACF;AAEA,SAAO,GAAG,QAAQ,KAAK,EAAE,CAAC,KAAK,gBAAgB,KAAK,CAAC;AACvD;AAEA,SAAS,WAAW,MAAyB;AAC3C,QAAM,QAAgC;AAAA,IACpC,OAAO,KAAK;AAAA,IACZ,OAAO,KAAK;AAAA,IACZ,WAAW,KAAK;AAAA,EAClB;AAEA,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,OAAO,IAAI,KAAK;AAAA,EACxB;AACA,MAAI,KAAK,aAAa,QAAW;AAC/B,UAAM,UAAU,IAAI,OAAO,KAAK,QAAQ;AAAA,EAC1C;AAGA,MAAI,KAAK,OAAO;AACd,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AACrD,YAAM,GAAG,IAAI;AAAA,IACf;AAAA,EACF;AAEA,SAAO,GAAG,QAAQ,KAAK,IAAI,CAAC,OAAO,QAAQ,KAAK,EAAE,CAAC,KAAK,gBAAgB,KAAK,CAAC;AAChF;AAEA,SAAS,eAAe,IAAc,QAA0B;AAC9D,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,GAAG,MAAM,YAAY,QAAQ,aAAa,GAAG,EAAE,CAAC,IAAI;AAE/D,MAAI,GAAG,UAAU,QAAW;AAC1B,UAAM,KAAK,GAAG,MAAM,aAAa,UAAU,GAAG,KAAK,CAAC,GAAG;AAAA,EACzD;AAEA,MAAI,GAAG,OAAO;AACZ,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,KAAK,GAAG;AACnD,YAAM,KAAK,GAAG,MAAM,OAAO,GAAG,IAAI,UAAU,KAAK,CAAC,GAAG;AAAA,IACvD;AAAA,EACF;AAEA,aAAW,QAAQ,GAAG,OAAO;AAC3B,UAAM,KAAK,GAAG,MAAM,OAAO,WAAW,IAAI,CAAC,EAAE;AAAA,EAC/C;AAGA,MAAI,GAAG,WAAW;AAChB,eAAW,UAAU,GAAG,WAAW;AACjC,YAAM,KAAK,GAAG,eAAe,QAAQ,SAAS,MAAM,CAAC;AAAA,IACvD;AAAA,EACF;AAKA,MAAI,GAAG,OAAO;AACZ,eAAW,KAAK,GAAG,OAAO;AACxB,YAAM,KAAK,GAAG,MAAM,OAAO,WAAW,CAAC,CAAC,EAAE;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,KAAK,GAAG,MAAM,GAAG;AACvB,SAAO;AACT;AAKA,SAAS,QAAQ,IAAoB;AAEnC,MAAI,2BAA2B,KAAK,EAAE,KAAK,CAAC,aAAa,EAAE,GAAG;AAC5D,WAAO;AAAA,EACT;AACA,SAAO,IAAI,UAAU,EAAE,CAAC;AAC1B;AAGA,SAAS,UAAU,OAAuB;AAExC,MAAI,kBAAkB,KAAK,KAAK,GAAG;AACjC,WAAO;AAAA,EACT;AACA,SAAO,IAAI,UAAU,KAAK,CAAC;AAC7B;AAGA,SAAS,UAAU,GAAmB;AACpC,SAAO,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AACrD;AAMA,SAAS,gBAAgB,OAAuC;AAC9D,SAAO,OAAO,QAAQ,KAAK,EACxB,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAErB,QAAI,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAAG;AAChD,aAAO,GAAG,GAAG,IAAI,KAAK;AAAA,IACxB;AACA,WAAO,GAAG,GAAG,IAAI,UAAU,KAAK,CAAC;AAAA,EACnC,CAAC,EACA,KAAK,IAAI;AACd;AAGA,SAAS,YAAY,OAAiD;AACpE,SAAO,OAAO,QAAQ,KAAK,EACxB,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,IAAI,UAAU,KAAK,CAAC,EAAE,EAClD,KAAK,IAAI;AACd;AAGA,SAAS,aAAa,IAAqB;AACzC,QAAM,QAAQ,GAAG,YAAY;AAC7B,SAAO,UAAU,WAAW,UAAU,aAAa,UAAU,cACxD,UAAU,UAAU,UAAU,UAAU,UAAU;AACzD;;;AC5LO,SAAS,UAAU,KAAe,QAA4B;AACnE,SAAO,UAAU,WAAW,KAAK,MAAM,CAAC;AAC1C;","names":[]}