libpetri 2.7.0 → 2.8.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-E3ZWB645.js +27 -0
- package/dist/chunk-E3ZWB645.js.map +1 -0
- package/dist/{chunk-ULX3OG6H.js → chunk-JVI5HFRX.js} +12 -6
- package/dist/chunk-JVI5HFRX.js.map +1 -0
- package/dist/chunk-M3KT7BFO.js +2773 -0
- package/dist/chunk-M3KT7BFO.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/debug/index.js +2 -1
- package/dist/debug/index.js.map +1 -1
- package/dist/doclet/index.d.ts +1 -1
- package/dist/doclet/index.js +3 -2
- package/dist/doclet/index.js.map +1 -1
- package/dist/dot-exporter-SHBYMMJ3.js +9 -0
- package/dist/{event-store-8XkpYUeU.d.ts → event-store-CAkN_ayv.d.ts} +1 -1
- package/dist/export/index.d.ts +1 -1
- package/dist/export/index.js +2 -1
- package/dist/index.d.ts +27 -4
- package/dist/index.js +280 -8
- package/dist/index.js.map +1 -1
- package/dist/{petri-net-D73-PO6d.d.ts → petri-net-B4wQwsUj.d.ts} +140 -3
- package/dist/verification/index.d.ts +40 -2
- package/dist/verification/index.js +9 -508
- package/dist/verification/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ETNHEAS6.js +0 -1458
- package/dist/chunk-ETNHEAS6.js.map +0 -1
- package/dist/chunk-ULX3OG6H.js.map +0 -1
- package/dist/dot-exporter-TYC6FUAD.js +0 -8
- /package/dist/{dot-exporter-TYC6FUAD.js.map → dot-exporter-SHBYMMJ3.js.map} +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/core/match-spec.ts
|
|
2
|
+
function matchKey(place, key) {
|
|
3
|
+
return { place, key };
|
|
4
|
+
}
|
|
5
|
+
function matchSpec(...keys) {
|
|
6
|
+
if (keys.length < 2) {
|
|
7
|
+
throw new Error(`MatchSpec must correlate at least 2 input places, got ${keys.length}`);
|
|
8
|
+
}
|
|
9
|
+
return { keys };
|
|
10
|
+
}
|
|
11
|
+
function keyForPlace(spec, placeName) {
|
|
12
|
+
for (const k of spec.keys) {
|
|
13
|
+
if (k.place.name === placeName) return k.key;
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
function matchCorrelates(spec, placeName) {
|
|
18
|
+
return spec.keys.some((k) => k.place.name === placeName);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
matchKey,
|
|
23
|
+
matchSpec,
|
|
24
|
+
keyForPlace,
|
|
25
|
+
matchCorrelates
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=chunk-E3ZWB645.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/match-spec.ts"],"sourcesContent":["/**\n * ν-net join correlation ([[MatchSpec]]).\n *\n * A {@link MatchSpec} declares that a subset of a transition's **input** places\n * must be correlated by **name equality**: the transition is enabled only when\n * there exists a single {@link NameId} `n` such that every correlated input\n * supplies (at least) its required token count whose projected name equals `n`.\n * On firing, exactly those name-matched tokens are consumed (spec NU-020).\n *\n * This is the single *decidable* predicate — equality of opaque names — and is\n * deliberately NOT a general guard: it correlates the name dimension *across*\n * places (composition-structural, like cardinality) rather than evaluating an\n * arbitrary boolean per token. When both a unary input `guard` and a match are\n * present the guard filters first, then the name correlation runs over the\n * survivors (NU-021).\n */\nimport type { Place } from './place.js';\nimport type { NameId } from './name.js';\n\n/**\n * A name projection: maps a token's value to its {@link NameId}. A projection\n * that yields no name (returns `null`/`undefined` at runtime) is treated as\n * \"no name\" — that token never correlates — mirroring the Java/Rust `KeyFn`\n * contract; the binding selector handles a nullish result defensively.\n */\nexport type KeyFn<T = any> = (value: T) => NameId;\n\n/** One correlated input: the place plus its name projection. */\nexport interface MatchKey<T = any> {\n readonly place: Place<T>;\n readonly key: KeyFn<T>;\n}\n\n/** Correlated fork/join match specification (ν-net join side). */\nexport interface MatchSpec {\n readonly keys: readonly MatchKey[];\n}\n\n/** Builds one correlated input for a {@link MatchSpec}. */\nexport function matchKey<T>(place: Place<T>, key: KeyFn<T>): MatchKey<T> {\n return { place, key };\n}\n\n/**\n * Builds a {@link MatchSpec} from two or more correlated inputs.\n *\n * @example\n * Transition.builder('join')\n * .inputs(one(branchA), one(branchB))\n * .match(matchSpec(\n * matchKey(branchA, (m: Msg) => nameId(m.correlationId)),\n * matchKey(branchB, (m: Msg) => nameId(m.correlationId)),\n * ))\n *\n * @throws if fewer than two inputs are correlated (a match over a single place\n * is just a guard).\n */\nexport function matchSpec(...keys: MatchKey[]): MatchSpec {\n if (keys.length < 2) {\n throw new Error(`MatchSpec must correlate at least 2 input places, got ${keys.length}`);\n }\n return { keys };\n}\n\n/** Returns the name projection for `placeName`, or `undefined` if not correlated. */\nexport function keyForPlace(spec: MatchSpec, placeName: string): KeyFn | undefined {\n for (const k of spec.keys) {\n if (k.place.name === placeName) return k.key;\n }\n return undefined;\n}\n\n/** True when `placeName` is one of the correlated inputs. */\nexport function matchCorrelates(spec: MatchSpec, placeName: string): boolean {\n return spec.keys.some(k => k.place.name === placeName);\n}\n"],"mappings":";AAuCO,SAAS,SAAY,OAAiB,KAA4B;AACvE,SAAO,EAAE,OAAO,IAAI;AACtB;AAgBO,SAAS,aAAa,MAA6B;AACxD,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,IAAI,MAAM,yDAAyD,KAAK,MAAM,EAAE;AAAA,EACxF;AACA,SAAO,EAAE,KAAK;AAChB;AAGO,SAAS,YAAY,MAAiB,WAAsC;AACjF,aAAW,KAAK,KAAK,MAAM;AACzB,QAAI,EAAE,MAAM,SAAS,UAAW,QAAO,EAAE;AAAA,EAC3C;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAiB,WAA4B;AAC3E,SAAO,KAAK,KAAK,KAAK,OAAK,EAAE,MAAM,SAAS,SAAS;AACvD;","names":[]}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
matchCorrelates
|
|
3
|
+
} from "./chunk-E3ZWB645.js";
|
|
1
4
|
import {
|
|
2
5
|
earliest,
|
|
3
6
|
hasDeadline,
|
|
@@ -32,6 +35,7 @@ function nodeStyle(category) {
|
|
|
32
35
|
function edgeStyle(arcType) {
|
|
33
36
|
return EDGE_STYLES[arcType];
|
|
34
37
|
}
|
|
38
|
+
var MATCH_INPUT_EDGE = { color: "#0d9488", style: "solid", arrowhead: "normal" };
|
|
35
39
|
|
|
36
40
|
// src/export/subnet-prefixes.ts
|
|
37
41
|
function instancePrefixOf(nodeName) {
|
|
@@ -285,19 +289,21 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
|
|
|
285
289
|
const combined = /* @__PURE__ */ new Set();
|
|
286
290
|
for (const spec of t.inputSpecs) {
|
|
287
291
|
const pid = "p_" + sanitize(spec.place.name);
|
|
288
|
-
const
|
|
289
|
-
|
|
292
|
+
const correlated = t.matchSpec !== null && matchCorrelates(t.matchSpec, spec.place.name);
|
|
293
|
+
const inputStyle = correlated ? MATCH_INPUT_EDGE : edgeStyle("input");
|
|
294
|
+
let card;
|
|
290
295
|
switch (spec.type) {
|
|
291
296
|
case "exactly":
|
|
292
|
-
|
|
297
|
+
card = `\xD7${spec.count}`;
|
|
293
298
|
break;
|
|
294
299
|
case "all":
|
|
295
|
-
|
|
300
|
+
card = "*";
|
|
296
301
|
break;
|
|
297
302
|
case "at-least":
|
|
298
|
-
|
|
303
|
+
card = `\u2265${spec.minimum}`;
|
|
299
304
|
break;
|
|
300
305
|
}
|
|
306
|
+
const label = !correlated ? card : card === void 0 ? "\u27E8n\u27E9" : `\u27E8n\u27E9 ${card}`;
|
|
301
307
|
edges.push({
|
|
302
308
|
from: pid,
|
|
303
309
|
to: tid,
|
|
@@ -708,4 +714,4 @@ export {
|
|
|
708
714
|
renderDot,
|
|
709
715
|
dotExport
|
|
710
716
|
};
|
|
711
|
-
//# sourceMappingURL=chunk-
|
|
717
|
+
//# sourceMappingURL=chunk-JVI5HFRX.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/** ν-net correlated input edge (a join input named by a MatchSpec, NU-020 / EXP-018). */\nexport const MATCH_INPUT_EDGE: EdgeVisual = { color: '#0d9488', style: 'solid', arrowhead: 'normal' };\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, MATCH_INPUT_EDGE, FONT, GRAPH } from './styles.js';\nimport type { NodeCategory } from './styles.js';\nimport { matchCorrelates } from '../core/match-spec.js';\nimport { partition } from './cluster-builder.js';\nimport { instancePrefixOf } from './subnet-prefixes.js';\n\n// ======================== Configuration ========================\n\n/**\n * Selects how DOT export groups nodes into `subgraph cluster_*` blocks (per\n * **MOD-040** / **EXP-016**).\n *\n * - `'auto'` — use subnet-membership metadata (per MOD-026) when the net\n * carries any; otherwise fall back to instance-prefix name detection.\n * Default.\n * - `'metadata'` — strictly cluster from subnet-membership metadata; a node\n * with no metadata entry — including a prefix-named instance node — is not\n * clustered. Use `'auto'` to also cluster prefix-named nodes.\n * - `'prefix'` — always cluster from instance-prefix name segments; ignore\n * metadata.\n * - `'none'` — emit no clusters; every node renders at the top level.\n */\nexport type ClusterSource = 'auto' | 'metadata' | 'prefix' | 'none';\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 * How to group nodes into `subgraph cluster_*` blocks (per MOD-040 /\n * EXP-016). Additive; defaults to `'auto'` when omitted.\n */\n readonly clusterSource?: ClusterSource;\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 cluster key (per [MOD-040]) so we can partition\n // into subgraph clusters at the end. Nodes with no cluster key are absent\n // from this map and stay at the top level.\n const nodeIdToPrefix = new Map<string, string>();\n\n // MOD-040 / EXP-016: 'auto' uses subnet-membership metadata (MOD-026) when\n // present and falls back to instance-prefix detection; 'metadata' is strict\n // (metadata only, no fallback); 'prefix' ignores metadata; 'none' suppresses\n // clustering. Flat and prefix-instantiated nets stay byte-identical under\n // 'auto'.\n const membership = net.subnetMembership;\n const clusterSource: ClusterSource = config.clusterSource ?? 'auto';\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 key = clusterKeyOf(name, clusterSource, membership);\n if (key !== undefined) {\n nodeIdToPrefix.set(nodeId, key);\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 key = clusterKeyOf(t.name, clusterSource, membership);\n if (key !== undefined) {\n nodeIdToPrefix.set(tid, key);\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 cluster key — tracked here\n // so the partition step routes them correctly.\n const tPrefix = clusterKeyOf(t.name, clusterSource, membership);\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 // ν-net correlated inputs (NU-020 / EXP-018) get a teal edge + ⟨n⟩ label.\n const correlated = t.matchSpec !== null && matchCorrelates(t.matchSpec, spec.place.name);\n const inputStyle = correlated ? MATCH_INPUT_EDGE : edgeStyle('input');\n let card: string | undefined;\n switch (spec.type) {\n case 'exactly':\n card = `×${spec.count}`;\n break;\n case 'all':\n card = '*';\n break;\n case 'at-least':\n card = `≥${spec.minimum}`;\n break;\n }\n const label = !correlated ? card : (card === undefined ? '⟨n⟩' : `⟨n⟩ ${card}`);\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\n/**\n * Resolves the cluster key for a node (place or transition) per **MOD-040** /\n * **EXP-016**:\n *\n * - `'auto'` — the owning subnet name from membership metadata (MOD-026),\n * falling back to instance-prefix detection for any node without an entry,\n * so mixed direct + instance composition keeps both cluster kinds.\n * - `'metadata'` — strictly the owning subnet name; a node with no metadata\n * entry is not clustered.\n * - `'prefix'` — strictly the instance-prefix segment; metadata is ignored.\n * - `'none'` — never clustered.\n *\n * @param nodeName the semantic node name (place or transition)\n * @param source the configured cluster source\n * @param membership the net's subnet-membership map (MOD-026)\n * @returns the cluster key, or `undefined` when the node is not clustered\n */\nfunction clusterKeyOf(\n nodeName: string,\n source: ClusterSource,\n membership: ReadonlyMap<string, string>,\n): string | undefined {\n switch (source) {\n case 'none':\n return undefined;\n case 'prefix':\n return instancePrefixOf(nodeName);\n case 'metadata':\n return membership.get(nodeName);\n case 'auto':\n return membership.get(nodeName) ?? instancePrefixOf(nodeName);\n default: {\n // Exhaustiveness guard: a future ClusterSource member is a compile error.\n const _exhaustive: never = source;\n return _exhaustive;\n }\n }\n}\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":[]}
|