libpetri 2.0.0 → 2.1.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/dist/{chunk-4L6JVKH4.js → chunk-B2CV5M3L.js} +64 -2
- package/dist/chunk-B2CV5M3L.js.map +1 -0
- package/dist/chunk-RNWTYK5B.js +419 -0
- package/dist/chunk-RNWTYK5B.js.map +1 -0
- package/dist/debug/index.js +1 -1
- package/dist/doclet/index.js +2 -2
- package/dist/doclet/resources/petrinet-diagrams.css +256 -0
- package/dist/doclet/resources/petrinet-diagrams.js +30 -6
- package/dist/dot-exporter-WJMCJEFK.js +8 -0
- package/dist/export/index.d.ts +2 -2
- package/dist/export/index.js +1 -1
- package/dist/render-QK57X4TP.js +73 -0
- package/dist/render-QK57X4TP.js.map +1 -0
- package/dist/viewer/index.d.ts +32 -1
- package/dist/viewer/index.js +510 -75
- package/dist/viewer/index.js.map +1 -1
- package/dist/viewer/layout/index.d.ts +200 -0
- package/dist/viewer/layout/index.js +15 -0
- package/dist/viewer/layout/index.js.map +1 -0
- package/dist/viewer/viewer-static.iife.js +2 -2
- package/dist/viewer/viewer.css +256 -0
- package/dist/viewer/viewer.iife.js +30 -6
- package/package.json +12 -1
- package/dist/chunk-4L6JVKH4.js.map +0 -1
- package/dist/dot-exporter-PMHOQHZ3.js +0 -8
- package/dist/render-P6GROU7J.js +0 -16
- package/dist/render-P6GROU7J.js.map +0 -1
- /package/dist/{dot-exporter-PMHOQHZ3.js.map → dot-exporter-WJMCJEFK.js.map} +0 -0
|
@@ -95,6 +95,9 @@ function partition(nodes, edges, nodeIdToPrefix) {
|
|
|
95
95
|
prefixState.get(common).edges.push(edge);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
for (const ghost of synthesizeGhostEdges(nodes, edges, nodeIdToPrefix)) {
|
|
99
|
+
topLevelEdges.push(ghost);
|
|
100
|
+
}
|
|
98
101
|
const built = /* @__PURE__ */ new Map();
|
|
99
102
|
const iterationOrder = [...prefixState.keys()].reverse();
|
|
100
103
|
for (const prefix of iterationOrder) {
|
|
@@ -141,6 +144,64 @@ function registerPrefixChain(prefix, accumulator) {
|
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
146
|
}
|
|
147
|
+
function synthesizeGhostEdges(nodes, edges, nodeIdToPrefix) {
|
|
148
|
+
const incomingByOrphan = /* @__PURE__ */ new Map();
|
|
149
|
+
const outgoingByOrphan = /* @__PURE__ */ new Map();
|
|
150
|
+
for (const edge of edges) {
|
|
151
|
+
const fromHasPrefix = nodeIdToPrefix.has(edge.from);
|
|
152
|
+
const toHasPrefix = nodeIdToPrefix.has(edge.to);
|
|
153
|
+
if (fromHasPrefix && !toHasPrefix) {
|
|
154
|
+
let list = incomingByOrphan.get(edge.to);
|
|
155
|
+
if (list === void 0) {
|
|
156
|
+
list = [];
|
|
157
|
+
incomingByOrphan.set(edge.to, list);
|
|
158
|
+
}
|
|
159
|
+
list.push(edge);
|
|
160
|
+
}
|
|
161
|
+
if (!fromHasPrefix && toHasPrefix) {
|
|
162
|
+
let list = outgoingByOrphan.get(edge.from);
|
|
163
|
+
if (list === void 0) {
|
|
164
|
+
list = [];
|
|
165
|
+
outgoingByOrphan.set(edge.from, list);
|
|
166
|
+
}
|
|
167
|
+
list.push(edge);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const ghosts = /* @__PURE__ */ new Map();
|
|
171
|
+
for (const node of nodes) {
|
|
172
|
+
if (nodeIdToPrefix.has(node.id)) continue;
|
|
173
|
+
const incoming = incomingByOrphan.get(node.id);
|
|
174
|
+
const outgoing = outgoingByOrphan.get(node.id);
|
|
175
|
+
if (incoming === void 0 || outgoing === void 0) continue;
|
|
176
|
+
for (const eIn of incoming) {
|
|
177
|
+
const x = nodeIdToPrefix.get(eIn.from);
|
|
178
|
+
for (const eOut of outgoing) {
|
|
179
|
+
const y = nodeIdToPrefix.get(eOut.to);
|
|
180
|
+
if (x === y) continue;
|
|
181
|
+
const key = `${x}\0${y}`;
|
|
182
|
+
if (!ghosts.has(key)) {
|
|
183
|
+
ghosts.set(key, { from: eIn.from, to: eOut.to, x, y });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const result = [];
|
|
189
|
+
for (const { from, to, x, y } of ghosts.values()) {
|
|
190
|
+
result.push({
|
|
191
|
+
from,
|
|
192
|
+
to,
|
|
193
|
+
color: "#000000",
|
|
194
|
+
style: "invis",
|
|
195
|
+
arrowhead: "none",
|
|
196
|
+
arcType: "ghost",
|
|
197
|
+
attrs: {
|
|
198
|
+
ltail: "cluster_" + sanitize(x),
|
|
199
|
+
lhead: "cluster_" + sanitize(y)
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
144
205
|
function deepestCommonPrefix(a, b) {
|
|
145
206
|
if (a === void 0 || b === void 0) return void 0;
|
|
146
207
|
if (a === b) return a;
|
|
@@ -313,7 +374,8 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
|
|
|
313
374
|
forcelabels: String(GRAPH.forcelabels),
|
|
314
375
|
overlap: String(GRAPH.overlap),
|
|
315
376
|
fontname: FONT.family,
|
|
316
|
-
outputorder: GRAPH.outputorder
|
|
377
|
+
outputorder: GRAPH.outputorder,
|
|
378
|
+
compound: "true"
|
|
317
379
|
},
|
|
318
380
|
nodeDefaults: {
|
|
319
381
|
fontname: FONT.family,
|
|
@@ -628,4 +690,4 @@ export {
|
|
|
628
690
|
renderDot,
|
|
629
691
|
dotExport
|
|
630
692
|
};
|
|
631
|
-
//# sourceMappingURL=chunk-
|
|
693
|
+
//# sourceMappingURL=chunk-B2CV5M3L.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 3.5: synthesize ghost edges for 1-hop cluster_X → orphan → cluster_Y\n // paths so Graphviz (with compound=true) can constrain the two clusters'\n // relative layout. Ghost edges are style=invis — they carry layout weight\n // only; the visible flow still goes through the orphan via the real edges.\n // Per spec EXP-017.\n for (const ghost of synthesizeGhostEdges(nodes, edges, nodeIdToPrefix)) {\n topLevelEdges.push(ghost);\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 * Synthesizes one invisible ghost edge per ordered (cluster_X, cluster_Y)\n * pair that is bridged by at least one top-level orphan node via a 1-hop\n * path (i.e. some edge X-node → orphan and some edge orphan → Y-node). The\n * ghost edge carries `style=invis, ltail=cluster_<sanitizedX>,\n * lhead=cluster_<sanitizedY>` so Graphviz (with `compound=true`) treats it\n * as a real cluster-to-cluster layout constraint without producing any\n * visible artifact. Per spec EXP-017.\n *\n * Determinism: walks orphans in `nodes` order, walks each orphan's\n * incoming/outgoing edges in `edges` order. First-witness wins for anchor\n * node selection. This matches the iteration discipline in the Java/Rust\n * mirrors so cross-language byte-parity holds.\n */\nfunction synthesizeGhostEdges(\n nodes: readonly GraphNode[],\n edges: readonly GraphEdge[],\n nodeIdToPrefix: ReadonlyMap<string, string>,\n): GraphEdge[] {\n // Index edges by their orphan endpoint. An orphan is any node whose id is\n // not in nodeIdToPrefix.\n const incomingByOrphan = new Map<string, GraphEdge[]>();\n const outgoingByOrphan = new Map<string, GraphEdge[]>();\n for (const edge of edges) {\n const fromHasPrefix = nodeIdToPrefix.has(edge.from);\n const toHasPrefix = nodeIdToPrefix.has(edge.to);\n if (fromHasPrefix && !toHasPrefix) {\n let list = incomingByOrphan.get(edge.to);\n if (list === undefined) {\n list = [];\n incomingByOrphan.set(edge.to, list);\n }\n list.push(edge);\n }\n if (!fromHasPrefix && toHasPrefix) {\n let list = outgoingByOrphan.get(edge.from);\n if (list === undefined) {\n list = [];\n outgoingByOrphan.set(edge.from, list);\n }\n list.push(edge);\n }\n }\n\n // Walk orphans in nodes order. For each (clusterX_prefix, clusterY_prefix)\n // ordered pair with X !== Y, record the first witness anchor pair.\n // Map<\"X\\0Y\", { from, to, x, y }> — using an insertion-ordered Map for\n // deterministic emission order.\n const ghosts = new Map<string, { from: string; to: string; x: string; y: string }>();\n for (const node of nodes) {\n if (nodeIdToPrefix.has(node.id)) continue;\n const incoming = incomingByOrphan.get(node.id);\n const outgoing = outgoingByOrphan.get(node.id);\n if (incoming === undefined || outgoing === undefined) continue;\n for (const eIn of incoming) {\n const x = nodeIdToPrefix.get(eIn.from)!;\n for (const eOut of outgoing) {\n const y = nodeIdToPrefix.get(eOut.to)!;\n if (x === y) continue;\n const key = `${x}\\0${y}`;\n if (!ghosts.has(key)) {\n ghosts.set(key, { from: eIn.from, to: eOut.to, x, y });\n }\n }\n }\n }\n\n const result: GraphEdge[] = [];\n for (const { from, to, x, y } of ghosts.values()) {\n result.push({\n from,\n to,\n color: '#000000',\n style: 'invis',\n arrowhead: 'none',\n arcType: 'ghost',\n attrs: {\n ltail: 'cluster_' + sanitize(x),\n lhead: 'cluster_' + sanitize(y),\n },\n });\n }\n return result;\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 compound: 'true',\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":[]}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
// src/viewer/layout/preprocess.ts
|
|
2
|
+
function classifyKind(id) {
|
|
3
|
+
if (id.startsWith("p_")) return "place";
|
|
4
|
+
if (id.startsWith("t_")) return "transition";
|
|
5
|
+
return "junction";
|
|
6
|
+
}
|
|
7
|
+
function parseAttrs(body) {
|
|
8
|
+
const attrs = {};
|
|
9
|
+
for (const m of body.matchAll(/(\w+)=("(?:[^"]|\\.)*"|\S+?)(?=[\s,]|$)/g)) {
|
|
10
|
+
const [, key, raw] = m;
|
|
11
|
+
const v = raw.startsWith('"') && raw.endsWith('"') ? raw.slice(1, -1) : raw;
|
|
12
|
+
attrs[key] = v;
|
|
13
|
+
}
|
|
14
|
+
return attrs;
|
|
15
|
+
}
|
|
16
|
+
function parseLibpetriDot(dot) {
|
|
17
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const m of dot.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[(.*)\];\s*$/gm)) {
|
|
19
|
+
const [, id, body] = m;
|
|
20
|
+
nodes.set(id, { id, kind: classifyKind(id), attrs: parseAttrs(body) });
|
|
21
|
+
}
|
|
22
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
23
|
+
const nodeToCluster = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const cm of dot.matchAll(/subgraph (cluster_\w+)\s*\{([^]*?)^\s*\}/gm)) {
|
|
25
|
+
const [, clusterId, body] = cm;
|
|
26
|
+
const memberIds = [...body.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[/gm)].map((m) => m[1]);
|
|
27
|
+
clusters.set(clusterId, {
|
|
28
|
+
id: clusterId,
|
|
29
|
+
shortName: clusterId.replace(/^cluster_/, ""),
|
|
30
|
+
nodes: memberIds
|
|
31
|
+
});
|
|
32
|
+
for (const n of memberIds) nodeToCluster.set(n, clusterId);
|
|
33
|
+
}
|
|
34
|
+
const stripped = dot.replace(/subgraph cluster_\w+\s*\{[^]*?^\s*\}/gm, "");
|
|
35
|
+
for (const m of stripped.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[/gm)) {
|
|
36
|
+
const id = m[1];
|
|
37
|
+
if (!nodes.has(id)) {
|
|
38
|
+
nodes.set(id, { id, kind: classifyKind(id), attrs: {} });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const orphans = [...nodes.keys()].filter((n) => !nodeToCluster.has(n));
|
|
42
|
+
const edges = [];
|
|
43
|
+
for (const m of dot.matchAll(/([pjt]_[A-Za-z0-9_]+)\s*->\s*([pjt]_[A-Za-z0-9_]+)\s*(\[[^\]]*\])?/g)) {
|
|
44
|
+
const [, src, dst, rawAttrs] = m;
|
|
45
|
+
const attrs = rawAttrs ?? "";
|
|
46
|
+
if (attrs.includes("lhead=") || attrs.includes("ltail=")) continue;
|
|
47
|
+
let arc = "normal";
|
|
48
|
+
if (attrs.includes('label="reset"') || attrs.includes("#fd7e14")) arc = "reset";
|
|
49
|
+
else if (attrs.includes('arrowhead="odot"') || attrs.includes("#dc3545")) arc = "inhibitor";
|
|
50
|
+
else if (attrs.includes('label="read"')) arc = "read";
|
|
51
|
+
const stripped2 = attrs.startsWith("[") && attrs.endsWith("]") ? attrs.slice(1, -1) : "";
|
|
52
|
+
edges.push({ src, dst, arc, rawAttrs: stripped2 });
|
|
53
|
+
}
|
|
54
|
+
return { nodes, clusters, nodeToCluster, orphans, edges };
|
|
55
|
+
}
|
|
56
|
+
function foldOrphans(graph, threshold = 0.7) {
|
|
57
|
+
if (threshold <= 0) return graph;
|
|
58
|
+
const nodeToCluster = new Map(graph.nodeToCluster);
|
|
59
|
+
const clusterMembers = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);
|
|
61
|
+
const tally = /* @__PURE__ */ new Map();
|
|
62
|
+
const bump = (orphan, other) => {
|
|
63
|
+
const oc = nodeToCluster.get(other);
|
|
64
|
+
if (!oc) return;
|
|
65
|
+
let t = tally.get(orphan);
|
|
66
|
+
if (!t) {
|
|
67
|
+
t = /* @__PURE__ */ new Map();
|
|
68
|
+
tally.set(orphan, t);
|
|
69
|
+
}
|
|
70
|
+
t.set(oc, (t.get(oc) ?? 0) + 1);
|
|
71
|
+
};
|
|
72
|
+
for (const e of graph.edges) {
|
|
73
|
+
const sIsOrphan = !nodeToCluster.has(e.src);
|
|
74
|
+
const dIsOrphan = !nodeToCluster.has(e.dst);
|
|
75
|
+
if (sIsOrphan && !dIsOrphan) bump(e.src, e.dst);
|
|
76
|
+
if (dIsOrphan && !sIsOrphan) bump(e.dst, e.src);
|
|
77
|
+
}
|
|
78
|
+
for (const id of graph.orphans) {
|
|
79
|
+
const t = tally.get(id);
|
|
80
|
+
if (!t) continue;
|
|
81
|
+
let total = 0;
|
|
82
|
+
let bestCluster = null;
|
|
83
|
+
let bestCount = 0;
|
|
84
|
+
for (const [c, n] of t) {
|
|
85
|
+
total += n;
|
|
86
|
+
if (n > bestCount) {
|
|
87
|
+
bestCount = n;
|
|
88
|
+
bestCluster = c;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (bestCluster && total > 0 && bestCount / total >= threshold) {
|
|
92
|
+
clusterMembers.get(bestCluster).push(id);
|
|
93
|
+
nodeToCluster.set(id, bestCluster);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const [id, c] of graph.clusters) {
|
|
98
|
+
clusters.set(id, { ...c, nodes: clusterMembers.get(id) });
|
|
99
|
+
}
|
|
100
|
+
const orphans = [...graph.nodes.keys()].filter((n) => !nodeToCluster.has(n));
|
|
101
|
+
return { ...graph, clusters, nodeToCluster, orphans };
|
|
102
|
+
}
|
|
103
|
+
function replicateShared(graph, opts = {}) {
|
|
104
|
+
const max = opts.max ?? Infinity;
|
|
105
|
+
const replicateTransitions = opts.replicateTransitions ?? false;
|
|
106
|
+
const nodes = new Map(graph.nodes);
|
|
107
|
+
const nodeToCluster = new Map(graph.nodeToCluster);
|
|
108
|
+
const clusterMembers = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);
|
|
110
|
+
const edges = graph.edges.map((e) => ({ ...e }));
|
|
111
|
+
const foreignByNode = /* @__PURE__ */ new Map();
|
|
112
|
+
const noteForeign = (nodeId, otherId) => {
|
|
113
|
+
if (!nodeId.startsWith("p_") && !(replicateTransitions && nodeId.startsWith("t_"))) return;
|
|
114
|
+
const home = nodeToCluster.get(nodeId);
|
|
115
|
+
const otherCluster = nodeToCluster.get(otherId);
|
|
116
|
+
if (!otherCluster) return;
|
|
117
|
+
if (otherCluster === home) return;
|
|
118
|
+
let s = foreignByNode.get(nodeId);
|
|
119
|
+
if (!s) {
|
|
120
|
+
s = /* @__PURE__ */ new Set();
|
|
121
|
+
foreignByNode.set(nodeId, s);
|
|
122
|
+
}
|
|
123
|
+
s.add(otherCluster);
|
|
124
|
+
};
|
|
125
|
+
for (const e of edges) {
|
|
126
|
+
noteForeign(e.src, e.dst);
|
|
127
|
+
noteForeign(e.dst, e.src);
|
|
128
|
+
}
|
|
129
|
+
const replicaMap = /* @__PURE__ */ new Map();
|
|
130
|
+
let replicatedPlaces = 0;
|
|
131
|
+
let totalCopies = 0;
|
|
132
|
+
for (const [placeId, foreigns] of foreignByNode) {
|
|
133
|
+
if (foreigns.size === 0) continue;
|
|
134
|
+
if (foreigns.size > max) continue;
|
|
135
|
+
const perCluster = /* @__PURE__ */ new Map();
|
|
136
|
+
const orig = nodes.get(placeId);
|
|
137
|
+
if (!orig) continue;
|
|
138
|
+
for (const clusterId of foreigns) {
|
|
139
|
+
const shortName = clusterId.replace(/^cluster_/, "");
|
|
140
|
+
const repId = `${placeId}__rep__${shortName}`;
|
|
141
|
+
perCluster.set(clusterId, repId);
|
|
142
|
+
clusterMembers.get(clusterId).push(repId);
|
|
143
|
+
nodeToCluster.set(repId, clusterId);
|
|
144
|
+
nodes.set(repId, {
|
|
145
|
+
id: repId,
|
|
146
|
+
kind: orig.kind,
|
|
147
|
+
attrs: { ...orig.attrs },
|
|
148
|
+
replica: true,
|
|
149
|
+
replicaOf: placeId
|
|
150
|
+
});
|
|
151
|
+
totalCopies++;
|
|
152
|
+
}
|
|
153
|
+
replicaMap.set(placeId, perCluster);
|
|
154
|
+
replicatedPlaces++;
|
|
155
|
+
nodes.set(placeId, { ...orig, replica: true, replicaOf: placeId });
|
|
156
|
+
}
|
|
157
|
+
for (const e of edges) {
|
|
158
|
+
const sMap = replicaMap.get(e.src);
|
|
159
|
+
const dMap = replicaMap.get(e.dst);
|
|
160
|
+
if (sMap) {
|
|
161
|
+
const dCl = nodeToCluster.get(e.dst);
|
|
162
|
+
if (dCl && sMap.has(dCl)) e.src = sMap.get(dCl);
|
|
163
|
+
}
|
|
164
|
+
if (dMap) {
|
|
165
|
+
const sCl = nodeToCluster.get(e.src);
|
|
166
|
+
if (sCl && dMap.has(sCl)) e.dst = dMap.get(sCl);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const [origId] of replicaMap) {
|
|
170
|
+
if (nodeToCluster.has(origId)) continue;
|
|
171
|
+
const stillHasEdge = edges.some((e) => e.src === origId || e.dst === origId);
|
|
172
|
+
if (!stillHasEdge) nodes.delete(origId);
|
|
173
|
+
}
|
|
174
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const [id, c] of graph.clusters) {
|
|
176
|
+
clusters.set(id, { ...c, nodes: clusterMembers.get(id) });
|
|
177
|
+
}
|
|
178
|
+
const orphans = [...nodes.keys()].filter((n) => !nodeToCluster.has(n));
|
|
179
|
+
return {
|
|
180
|
+
nodes,
|
|
181
|
+
clusters,
|
|
182
|
+
nodeToCluster,
|
|
183
|
+
orphans,
|
|
184
|
+
edges,
|
|
185
|
+
replicateStats: { replicatedPlaces, totalCopies }
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/viewer/layout/elk-place.ts
|
|
190
|
+
import ELK from "elkjs/lib/elk.bundled.js";
|
|
191
|
+
var ORCHESTRATOR_CLUSTER_ID = "cluster_orchestrator";
|
|
192
|
+
var PLACE_RADIUS = 22;
|
|
193
|
+
var PLACE_CHARW = 7.4;
|
|
194
|
+
var PLACE_PAD_BELOW = 16;
|
|
195
|
+
var FONT_PLACE = 13;
|
|
196
|
+
var TRANSITION_MIN_W = 180;
|
|
197
|
+
var TRANSITION_H = 40;
|
|
198
|
+
var TRANSITION_CHARW = 8.5;
|
|
199
|
+
var JUNCTION_SIZE = 28;
|
|
200
|
+
var FONT_CLUSTER = 17;
|
|
201
|
+
var ROOT_SPACING = 90;
|
|
202
|
+
var CLUSTER_SPACING = 16;
|
|
203
|
+
function nodeDims(node) {
|
|
204
|
+
if (node.kind === "place") {
|
|
205
|
+
const label2 = (node.attrs.xlabel ?? node.attrs.label ?? node.id).replace(/\\n/g, " ");
|
|
206
|
+
const labelWidth = label2.length * PLACE_CHARW;
|
|
207
|
+
const circleD = PLACE_RADIUS * 2 + 4;
|
|
208
|
+
return {
|
|
209
|
+
width: Math.max(circleD, labelWidth),
|
|
210
|
+
height: circleD + PLACE_PAD_BELOW + FONT_PLACE
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (node.kind === "junction") {
|
|
214
|
+
return { width: JUNCTION_SIZE, height: JUNCTION_SIZE };
|
|
215
|
+
}
|
|
216
|
+
const label = (node.attrs.label ?? node.id).replace(/\\n/g, " ");
|
|
217
|
+
return {
|
|
218
|
+
width: Math.max(TRANSITION_MIN_W, label.length * TRANSITION_CHARW),
|
|
219
|
+
height: TRANSITION_H
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
var CLUSTER_OPTIONS = {
|
|
223
|
+
"elk.padding": `[top=${FONT_CLUSTER + 16},left=16,bottom=16,right=16]`,
|
|
224
|
+
"elk.algorithm": "layered",
|
|
225
|
+
"elk.direction": "DOWN",
|
|
226
|
+
"elk.spacing.nodeNode": String(CLUSTER_SPACING),
|
|
227
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": String(CLUSTER_SPACING + 6),
|
|
228
|
+
"elk.edgeRouting": "ORTHOGONAL"
|
|
229
|
+
};
|
|
230
|
+
var ROOT_OPTIONS = {
|
|
231
|
+
"elk.algorithm": "org.eclipse.elk.rectpacking",
|
|
232
|
+
"elk.aspectRatio": "1.4",
|
|
233
|
+
"elk.padding": "[top=24,left=24,bottom=24,right=24]",
|
|
234
|
+
"elk.spacing.nodeNode": String(ROOT_SPACING)
|
|
235
|
+
};
|
|
236
|
+
var ORCHESTRATOR_OPTIONS = {
|
|
237
|
+
...CLUSTER_OPTIONS,
|
|
238
|
+
"elk.aspectRatio": "2.4",
|
|
239
|
+
"elk.spacing.nodeNode": "10"
|
|
240
|
+
};
|
|
241
|
+
function partitionEdges(graph) {
|
|
242
|
+
const byCluster = /* @__PURE__ */ new Map();
|
|
243
|
+
const cross = [];
|
|
244
|
+
const edgeIdToKey = /* @__PURE__ */ new Map();
|
|
245
|
+
const ownerOf = (id) => graph.nodeToCluster.get(id) ?? ORCHESTRATOR_CLUSTER_ID;
|
|
246
|
+
graph.edges.forEach((e, i) => {
|
|
247
|
+
const so = ownerOf(e.src);
|
|
248
|
+
const dt = ownerOf(e.dst);
|
|
249
|
+
const id = `e${i}`;
|
|
250
|
+
edgeIdToKey.set(id, `${e.src}->${e.dst}`);
|
|
251
|
+
const elkEdge = {
|
|
252
|
+
id,
|
|
253
|
+
sources: [e.src],
|
|
254
|
+
targets: [e.dst]
|
|
255
|
+
};
|
|
256
|
+
if (so === dt) {
|
|
257
|
+
let list = byCluster.get(so);
|
|
258
|
+
if (!list) {
|
|
259
|
+
list = [];
|
|
260
|
+
byCluster.set(so, list);
|
|
261
|
+
}
|
|
262
|
+
list.push(elkEdge);
|
|
263
|
+
} else {
|
|
264
|
+
cross.push(elkEdge);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
return { byCluster, cross, edgeIdToKey };
|
|
268
|
+
}
|
|
269
|
+
async function elkLayout(graph) {
|
|
270
|
+
const { byCluster, cross, edgeIdToKey } = partitionEdges(graph);
|
|
271
|
+
const elkChildren = [];
|
|
272
|
+
for (const [clusterId, cluster] of graph.clusters) {
|
|
273
|
+
elkChildren.push({
|
|
274
|
+
id: clusterId,
|
|
275
|
+
layoutOptions: CLUSTER_OPTIONS,
|
|
276
|
+
children: cluster.nodes.map((id) => graph.nodes.get(id)).filter((n) => n !== void 0).map((n) => ({ id: n.id, ...nodeDims(n) })),
|
|
277
|
+
edges: byCluster.get(clusterId) ?? []
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
elkChildren.push({
|
|
281
|
+
id: ORCHESTRATOR_CLUSTER_ID,
|
|
282
|
+
layoutOptions: ORCHESTRATOR_OPTIONS,
|
|
283
|
+
children: graph.orphans.map((id) => graph.nodes.get(id)).filter((n) => n !== void 0).map((n) => ({ id: n.id, ...nodeDims(n) })),
|
|
284
|
+
edges: byCluster.get(ORCHESTRATOR_CLUSTER_ID) ?? []
|
|
285
|
+
});
|
|
286
|
+
const elk = new ELK();
|
|
287
|
+
const rootGraph = {
|
|
288
|
+
id: "root",
|
|
289
|
+
layoutOptions: ROOT_OPTIONS,
|
|
290
|
+
children: elkChildren,
|
|
291
|
+
edges: cross
|
|
292
|
+
};
|
|
293
|
+
const layout = await elk.layout(rootGraph);
|
|
294
|
+
const nodePositions = /* @__PURE__ */ new Map();
|
|
295
|
+
const clusterBoxes = /* @__PURE__ */ new Map();
|
|
296
|
+
const edgePaths = /* @__PURE__ */ new Map();
|
|
297
|
+
const walk = (node, parentX, parentY) => {
|
|
298
|
+
const ax = parentX + (node.x ?? 0);
|
|
299
|
+
const ay = parentY + (node.y ?? 0);
|
|
300
|
+
const isCluster = node.id !== "root" && (node.children?.length ?? 0) > 0;
|
|
301
|
+
if (isCluster) {
|
|
302
|
+
clusterBoxes.set(node.id, {
|
|
303
|
+
x: ax,
|
|
304
|
+
y: ay,
|
|
305
|
+
width: node.width ?? 0,
|
|
306
|
+
height: node.height ?? 0
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
if (node.id !== "root" && !isCluster) {
|
|
310
|
+
nodePositions.set(node.id, {
|
|
311
|
+
x: ax,
|
|
312
|
+
y: ay,
|
|
313
|
+
width: node.width ?? 0,
|
|
314
|
+
height: node.height ?? 0
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const edgeOffsetX = node.id === "root" ? parentX : ax;
|
|
318
|
+
const edgeOffsetY = node.id === "root" ? parentY : ay;
|
|
319
|
+
for (const edge of node.edges ?? []) {
|
|
320
|
+
const key = edge.id ? edgeIdToKey.get(edge.id) : void 0;
|
|
321
|
+
const section = edge.sections?.[0];
|
|
322
|
+
if (!key || !section) continue;
|
|
323
|
+
edgePaths.set(key, {
|
|
324
|
+
start: {
|
|
325
|
+
x: edgeOffsetX + section.startPoint.x,
|
|
326
|
+
y: edgeOffsetY + section.startPoint.y
|
|
327
|
+
},
|
|
328
|
+
end: {
|
|
329
|
+
x: edgeOffsetX + section.endPoint.x,
|
|
330
|
+
y: edgeOffsetY + section.endPoint.y
|
|
331
|
+
},
|
|
332
|
+
bends: (section.bendPoints ?? []).map((p) => ({
|
|
333
|
+
x: edgeOffsetX + p.x,
|
|
334
|
+
y: edgeOffsetY + p.y
|
|
335
|
+
}))
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
for (const child of node.children ?? []) walk(child, ax, ay);
|
|
339
|
+
};
|
|
340
|
+
walk(layout, 0, 0);
|
|
341
|
+
return {
|
|
342
|
+
nodePositions,
|
|
343
|
+
clusterBoxes,
|
|
344
|
+
edgePaths,
|
|
345
|
+
totalWidth: layout.width ?? 0,
|
|
346
|
+
totalHeight: layout.height ?? 0
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function quote(value) {
|
|
350
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
351
|
+
}
|
|
352
|
+
function nodeAttrLine(node, pos) {
|
|
353
|
+
const attrs = [];
|
|
354
|
+
const cx = pos.x + pos.width / 2;
|
|
355
|
+
const cy = pos.y + pos.height / 2;
|
|
356
|
+
attrs.push(`pos=${quote(`${cx.toFixed(2)},${cy.toFixed(2)}!`)}`);
|
|
357
|
+
for (const [k, v] of Object.entries(node.attrs)) {
|
|
358
|
+
if (k === "pos") continue;
|
|
359
|
+
attrs.push(`${k}=${quote(v)}`);
|
|
360
|
+
}
|
|
361
|
+
return `${node.id} [${attrs.join(", ")}];`;
|
|
362
|
+
}
|
|
363
|
+
function edgeAttrLine(src, dst, rawAttrs) {
|
|
364
|
+
const body = rawAttrs.trim();
|
|
365
|
+
return `${src} -> ${dst}` + (body ? ` [${body}];` : ";");
|
|
366
|
+
}
|
|
367
|
+
function writeBack(graph, layout) {
|
|
368
|
+
const lines = [];
|
|
369
|
+
lines.push("digraph LibpetriPinned {");
|
|
370
|
+
lines.push(" rankdir=TB;");
|
|
371
|
+
lines.push(' overlap="false";');
|
|
372
|
+
lines.push(' compound="true";');
|
|
373
|
+
lines.push(' fontname="Helvetica,Arial,sans-serif";');
|
|
374
|
+
lines.push(' outputorder="edgesfirst";');
|
|
375
|
+
lines.push(' node [fontname="Helvetica,Arial,sans-serif", fontsize=10];');
|
|
376
|
+
lines.push(' edge [fontname="Helvetica,Arial,sans-serif", fontsize=9];');
|
|
377
|
+
for (const [clusterId, cluster] of graph.clusters) {
|
|
378
|
+
const box = layout.clusterBoxes.get(clusterId);
|
|
379
|
+
lines.push(` subgraph ${clusterId} {`);
|
|
380
|
+
lines.push(` label=${quote(cluster.shortName)};`);
|
|
381
|
+
lines.push(` style="rounded,dashed";`);
|
|
382
|
+
lines.push(` bgcolor="#FAFAFA";`);
|
|
383
|
+
lines.push(` penwidth=1.5;`);
|
|
384
|
+
if (box) {
|
|
385
|
+
const x1 = box.x.toFixed(2);
|
|
386
|
+
const y1 = box.y.toFixed(2);
|
|
387
|
+
const x2 = (box.x + box.width).toFixed(2);
|
|
388
|
+
const y2 = (box.y + box.height).toFixed(2);
|
|
389
|
+
lines.push(` bb=${quote(`${x1},${y1},${x2},${y2}`)};`);
|
|
390
|
+
}
|
|
391
|
+
for (const nodeId of cluster.nodes) {
|
|
392
|
+
const node = graph.nodes.get(nodeId);
|
|
393
|
+
const pos = layout.nodePositions.get(nodeId);
|
|
394
|
+
if (!node || !pos) continue;
|
|
395
|
+
lines.push(" " + nodeAttrLine(node, pos));
|
|
396
|
+
}
|
|
397
|
+
lines.push(" }");
|
|
398
|
+
}
|
|
399
|
+
for (const orphanId of graph.orphans) {
|
|
400
|
+
const node = graph.nodes.get(orphanId);
|
|
401
|
+
const pos = layout.nodePositions.get(orphanId);
|
|
402
|
+
if (!node || !pos) continue;
|
|
403
|
+
lines.push(" " + nodeAttrLine(node, pos));
|
|
404
|
+
}
|
|
405
|
+
for (const edge of graph.edges) {
|
|
406
|
+
lines.push(" " + edgeAttrLine(edge.src, edge.dst, edge.rawAttrs));
|
|
407
|
+
}
|
|
408
|
+
lines.push("}");
|
|
409
|
+
return lines.join("\n");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export {
|
|
413
|
+
parseLibpetriDot,
|
|
414
|
+
foldOrphans,
|
|
415
|
+
replicateShared,
|
|
416
|
+
elkLayout,
|
|
417
|
+
writeBack
|
|
418
|
+
};
|
|
419
|
+
//# sourceMappingURL=chunk-RNWTYK5B.js.map
|