@loaders.gl/graphs 5.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +66 -0
  2. package/dist/dot-loader-types.d.ts +36 -0
  3. package/dist/dot-loader-types.d.ts.map +1 -0
  4. package/dist/dot-loader-types.js +26 -0
  5. package/dist/dot-loader-types.js.map +1 -0
  6. package/dist/dot-loader.cjs +1482 -0
  7. package/dist/dot-loader.cjs.map +6 -0
  8. package/dist/dot-loader.d.ts +20 -0
  9. package/dist/dot-loader.d.ts.map +1 -0
  10. package/dist/dot-loader.js +13 -0
  11. package/dist/dot-loader.js.map +1 -0
  12. package/dist/dot-types.d.ts +32 -0
  13. package/dist/dot-types.d.ts.map +1 -0
  14. package/dist/dot-types.js +5 -0
  15. package/dist/dot-types.js.map +1 -0
  16. package/dist/graph-types.d.ts +28 -0
  17. package/dist/graph-types.d.ts.map +1 -0
  18. package/dist/graph-types.js +5 -0
  19. package/dist/graph-types.js.map +1 -0
  20. package/dist/graphml-loader-types.d.ts +36 -0
  21. package/dist/graphml-loader-types.d.ts.map +1 -0
  22. package/dist/graphml-loader-types.js +26 -0
  23. package/dist/graphml-loader-types.js.map +1 -0
  24. package/dist/graphml-loader.cjs +650 -0
  25. package/dist/graphml-loader.cjs.map +6 -0
  26. package/dist/graphml-loader.d.ts +20 -0
  27. package/dist/graphml-loader.d.ts.map +1 -0
  28. package/dist/graphml-loader.js +13 -0
  29. package/dist/graphml-loader.js.map +1 -0
  30. package/dist/index.cjs +1136 -0
  31. package/dist/index.cjs.map +6 -0
  32. package/dist/index.d.ts +5 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +6 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/lib/parse-dot.d.ts +4 -0
  37. package/dist/lib/parse-dot.d.ts.map +1 -0
  38. package/dist/lib/parse-dot.js +724 -0
  39. package/dist/lib/parse-dot.js.map +1 -0
  40. package/dist/lib/parse-graphml.d.ts +6 -0
  41. package/dist/lib/parse-graphml.d.ts.map +1 -0
  42. package/dist/lib/parse-graphml.js +296 -0
  43. package/dist/lib/parse-graphml.js.map +1 -0
  44. package/package.json +58 -0
  45. package/src/dot-loader-types.ts +31 -0
  46. package/src/dot-loader.ts +14 -0
  47. package/src/dot-types.ts +38 -0
  48. package/src/graph-types.ts +33 -0
  49. package/src/graphml-loader-types.ts +31 -0
  50. package/src/graphml-loader.ts +14 -0
  51. package/src/index.ts +8 -0
  52. package/src/lib/parse-dot.ts +924 -0
  53. package/src/lib/parse-graphml.ts +414 -0
@@ -0,0 +1,1482 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res, err) => function __init() {
7
+ if (err) throw err[0];
8
+ try {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ } catch (e) {
11
+ throw err = [e], e;
12
+ }
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+
28
+ // src/dot-loader-types.ts
29
+ async function preload() {
30
+ const { DOTLoaderWithParser: DOTLoaderWithParser3 } = await Promise.resolve().then(() => (init_dot_loader(), dot_loader_exports));
31
+ return DOTLoaderWithParser3;
32
+ }
33
+ var VERSION, DOTLoader;
34
+ var init_dot_loader_types = __esm({
35
+ "src/dot-loader-types.ts"() {
36
+ "use strict";
37
+ VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "latest";
38
+ DOTLoader = {
39
+ name: "DOT",
40
+ id: "dot",
41
+ module: "graphs",
42
+ version: VERSION,
43
+ extensions: ["dot", "gv"],
44
+ mimeTypes: ["text/vnd.graphviz", "text/x-graphviz", "application/vnd.graphviz"],
45
+ text: true,
46
+ worker: false,
47
+ dataType: null,
48
+ batchType: null,
49
+ options: {},
50
+ preload
51
+ };
52
+ }
53
+ });
54
+
55
+ // src/lib/parse-dot.ts
56
+ function parseDOT(input) {
57
+ const parsed = parseDOTDocument(input);
58
+ const subgraphs = Array.from(parsed.subgraphs.values(), (subgraph) => ({
59
+ ...subgraph,
60
+ attributes: { ...subgraph.attributes }
61
+ }));
62
+ return {
63
+ shape: "plain-graph-data",
64
+ nodes: Array.from(parsed.nodes.values(), (node) => ({
65
+ id: node.id,
66
+ ...typeof node.attributes.label === "string" ? { label: node.attributes.label } : {},
67
+ attributes: {
68
+ ...node.attributes,
69
+ ...node.subgraphs.size ? { subgraphs: Array.from(node.subgraphs, (id) => describeSubgraph(id, parsed.subgraphs)) } : {}
70
+ }
71
+ })),
72
+ edges: parsed.edges.map((edge) => ({
73
+ id: edge.id,
74
+ sourceId: edge.sourceId,
75
+ targetId: edge.targetId,
76
+ directed: edge.directed,
77
+ ...typeof edge.attributes.label === "string" ? { label: edge.attributes.label } : {},
78
+ attributes: {
79
+ ...edge.attributes,
80
+ ...edge.subgraphs.length ? { subgraphs: edge.subgraphs.map((id) => describeSubgraph(id, parsed.subgraphs)) } : {}
81
+ }
82
+ })),
83
+ metadata: {
84
+ id: parsed.id,
85
+ directed: parsed.directed,
86
+ strict: parsed.strict,
87
+ attributes: { ...parsed.graphAttributes },
88
+ subgraphs
89
+ }
90
+ };
91
+ }
92
+ function describeSubgraph(id, descriptors) {
93
+ const subgraph = descriptors.get(id);
94
+ return {
95
+ id,
96
+ attributes: { ...subgraph.attributes },
97
+ parentId: subgraph.parentId
98
+ };
99
+ }
100
+ function parseDOTDocument(input) {
101
+ const parser = new DOTParser(tokenize(input));
102
+ return parser.parse();
103
+ }
104
+ function tokenize(input) {
105
+ const tokens = [];
106
+ let index = 0;
107
+ while (index < input.length) {
108
+ const { token, nextIndex } = readNextToken(input, index);
109
+ if (nextIndex <= index) {
110
+ throw new Error(`Tokenizer did not advance at position ${index}.`);
111
+ }
112
+ if (token) {
113
+ tokens.push(token);
114
+ }
115
+ index = nextIndex;
116
+ }
117
+ return tokens;
118
+ }
119
+ function readNextToken(input, index) {
120
+ if (index >= input.length) {
121
+ return { token: null, nextIndex: input.length };
122
+ }
123
+ const character = input[index];
124
+ if (isWhitespace(character)) {
125
+ return { token: null, nextIndex: index + 1 };
126
+ }
127
+ const commentEnd = skipComment(input, index);
128
+ if (commentEnd !== null) {
129
+ return { token: null, nextIndex: commentEnd };
130
+ }
131
+ const arrowToken = readArrowToken(input, index);
132
+ if (arrowToken) {
133
+ return arrowToken;
134
+ }
135
+ const punctuation = readPunctuationToken(character);
136
+ if (punctuation) {
137
+ return { token: punctuation, nextIndex: index + 1 };
138
+ }
139
+ if (character === '"') {
140
+ const { value, nextIndex } = readQuotedString(input, index + 1);
141
+ return { token: { type: "string", value }, nextIndex };
142
+ }
143
+ if (character === "<") {
144
+ const { value, nextIndex } = readHtmlString(input, index + 1);
145
+ return { token: { type: "html", value }, nextIndex };
146
+ }
147
+ const identifier = readIdentifier(input, index);
148
+ if (identifier.value) {
149
+ return {
150
+ token: { type: "identifier", value: identifier.value },
151
+ nextIndex: identifier.nextIndex
152
+ };
153
+ }
154
+ throw new Error(`Unexpected token at position ${index}.`);
155
+ }
156
+ function skipComment(input, index) {
157
+ const character = input[index];
158
+ if (character === "/") {
159
+ const next = input[index + 1];
160
+ if (next === "/") {
161
+ return skipLineComment(input, index + 2);
162
+ }
163
+ if (next === "*") {
164
+ return skipBlockComment(input, index + 2);
165
+ }
166
+ return null;
167
+ }
168
+ if (character === "#") {
169
+ return skipLineComment(input, index + 1);
170
+ }
171
+ return null;
172
+ }
173
+ function skipLineComment(input, startIndex) {
174
+ let cursor = startIndex;
175
+ while (cursor < input.length && !isLineBreak(input[cursor])) {
176
+ cursor++;
177
+ }
178
+ return cursor;
179
+ }
180
+ function skipBlockComment(input, startIndex) {
181
+ let cursor = startIndex;
182
+ while (cursor < input.length) {
183
+ if (input[cursor] === "*" && input[cursor + 1] === "/") {
184
+ return cursor + 2;
185
+ }
186
+ cursor++;
187
+ }
188
+ throw new Error("Unterminated block comment in DOT source.");
189
+ }
190
+ function readArrowToken(input, index) {
191
+ const next = input[index + 1];
192
+ if (input[index] === "-" && next === "-") {
193
+ return { token: { type: "arrow", value: "--" }, nextIndex: index + 2 };
194
+ }
195
+ if (input[index] === "-" && next === ">") {
196
+ return { token: { type: "arrow", value: "->" }, nextIndex: index + 2 };
197
+ }
198
+ return null;
199
+ }
200
+ function readPunctuationToken(character) {
201
+ switch (character) {
202
+ case "{":
203
+ return { type: "lbrace", value: character };
204
+ case "}":
205
+ return { type: "rbrace", value: character };
206
+ case "[":
207
+ return { type: "lbrack", value: character };
208
+ case "]":
209
+ return { type: "rbrack", value: character };
210
+ case "=":
211
+ return { type: "equals", value: character };
212
+ case ",":
213
+ return { type: "comma", value: character };
214
+ case ";":
215
+ return { type: "semicolon", value: character };
216
+ default:
217
+ return null;
218
+ }
219
+ }
220
+ function readQuotedString(input, startIndex) {
221
+ let value = "";
222
+ let index = startIndex;
223
+ while (index < input.length) {
224
+ const character = input[index];
225
+ if (character === '"') {
226
+ return { value, nextIndex: index + 1 };
227
+ }
228
+ if (character === "\\") {
229
+ const escape = readEscapedCharacter(input, index + 1);
230
+ value += escape.value;
231
+ index = escape.nextIndex;
232
+ } else {
233
+ value += character;
234
+ index++;
235
+ }
236
+ }
237
+ throw new Error("Unterminated string literal in DOT source.");
238
+ }
239
+ function readEscapedCharacter(input, startIndex) {
240
+ const next = input[startIndex];
241
+ switch (next) {
242
+ case "n":
243
+ case "l":
244
+ case "L":
245
+ return { value: "\n", nextIndex: startIndex + 1 };
246
+ case "t":
247
+ return { value: " ", nextIndex: startIndex + 1 };
248
+ case "r":
249
+ return { value: "\r", nextIndex: startIndex + 1 };
250
+ case '"':
251
+ return { value: '"', nextIndex: startIndex + 1 };
252
+ case "\\":
253
+ return { value: "\\", nextIndex: startIndex + 1 };
254
+ default: {
255
+ if (typeof next === "undefined") {
256
+ throw new Error("Unterminated escape sequence in DOT source.");
257
+ }
258
+ return { value: next, nextIndex: startIndex + 1 };
259
+ }
260
+ }
261
+ }
262
+ function readHtmlString(input, startIndex) {
263
+ let value = "<";
264
+ let depth = 1;
265
+ let index = startIndex;
266
+ while (index < input.length) {
267
+ const character = input[index];
268
+ value += character;
269
+ if (character === "<") {
270
+ depth++;
271
+ } else if (character === ">") {
272
+ depth--;
273
+ if (depth === 0) {
274
+ return { value, nextIndex: index + 1 };
275
+ }
276
+ }
277
+ index++;
278
+ }
279
+ throw new Error("Unterminated HTML-like string literal in DOT source.");
280
+ }
281
+ function readIdentifier(input, startIndex) {
282
+ let index = startIndex;
283
+ let value = "";
284
+ while (index < input.length && !isIdentifierTerminator(input, index)) {
285
+ value += input[index];
286
+ index++;
287
+ }
288
+ if (value.includes(":")) {
289
+ throw new Error("DOT node ports are not supported; quote identifiers containing colons.");
290
+ }
291
+ return { value, nextIndex: index };
292
+ }
293
+ function isIdentifierTerminator(input, index) {
294
+ const character = input[index];
295
+ if (isWhitespace(character) || IDENTIFIER_TERMINATORS.has(character)) {
296
+ return true;
297
+ }
298
+ if (isArrowOperatorStart(input, index)) {
299
+ return true;
300
+ }
301
+ if (isCommentStart(input, index)) {
302
+ return true;
303
+ }
304
+ return false;
305
+ }
306
+ function isArrowOperatorStart(input, index) {
307
+ if (input[index] !== "-") {
308
+ return false;
309
+ }
310
+ const next = input[index + 1];
311
+ return next === "-" || next === ">";
312
+ }
313
+ function isCommentStart(input, index) {
314
+ if (input[index] !== "/") {
315
+ return false;
316
+ }
317
+ const next = input[index + 1];
318
+ return next === "/" || next === "*";
319
+ }
320
+ function isWhitespace(character) {
321
+ return character === " " || character === "\n" || character === "\r" || character === " " || character === "\f";
322
+ }
323
+ function isLineBreak(character) {
324
+ return character === "\n" || character === "\r";
325
+ }
326
+ function isKeyword(token, keyword) {
327
+ return token.type === "identifier" && token.value.toLowerCase() === keyword.toLowerCase();
328
+ }
329
+ function isGraphType(token) {
330
+ return isKeyword(token, "graph") || isKeyword(token, "digraph");
331
+ }
332
+ function isIdentifierLike(token) {
333
+ return token.type === "identifier" || token.type === "string" || token.type === "html";
334
+ }
335
+ function parseIdentifierValue(token) {
336
+ return token.value;
337
+ }
338
+ function parseAttributeValue(token) {
339
+ if (token.type === "string" || token.type === "html") {
340
+ return token.value;
341
+ }
342
+ if (token.type === "identifier") {
343
+ const numeric = Number(token.value);
344
+ if (!Number.isNaN(numeric)) {
345
+ return numeric;
346
+ }
347
+ return token.value;
348
+ }
349
+ throw new Error("Invalid attribute value in DOT input.");
350
+ }
351
+ function deriveEdgeId(attributes, sourceId, targetId, counter) {
352
+ const candidate = attributes.id ?? attributes.Id ?? attributes.ID;
353
+ if (typeof candidate === "string" || typeof candidate === "number") {
354
+ return String(candidate);
355
+ }
356
+ return `${String(sourceId)}-${String(targetId)}-${counter}`;
357
+ }
358
+ function deriveDirectedFlag(attributes, defaultDirected) {
359
+ const candidate = attributes.directed;
360
+ if (typeof candidate === "boolean") {
361
+ return candidate;
362
+ }
363
+ const directionAttribute = attributes.dir;
364
+ if (typeof directionAttribute === "string") {
365
+ const normalized = directionAttribute.toLowerCase();
366
+ if (normalized === "none") {
367
+ return false;
368
+ }
369
+ return true;
370
+ }
371
+ return defaultDirected;
372
+ }
373
+ var DOTParser, IDENTIFIER_TERMINATORS;
374
+ var init_parse_dot = __esm({
375
+ "src/lib/parse-dot.ts"() {
376
+ "use strict";
377
+ DOTParser = class {
378
+ /** Tokens for DOT parsing. */
379
+ tokens;
380
+ /** Position for DOT parsing. */
381
+ position = 0;
382
+ /** Accumulated graph records and attributes. */
383
+ result = {
384
+ directed: false,
385
+ strict: false,
386
+ graphAttributes: /* @__PURE__ */ Object.create(null),
387
+ nodes: /* @__PURE__ */ new Map(),
388
+ edges: [],
389
+ subgraphs: /* @__PURE__ */ new Map()
390
+ };
391
+ /** Scopes for DOT parsing. */
392
+ scopes = [
393
+ {
394
+ nodeDefaults: /* @__PURE__ */ Object.create(null),
395
+ edgeDefaults: /* @__PURE__ */ Object.create(null),
396
+ graphAttributes: /* @__PURE__ */ Object.create(null)
397
+ }
398
+ ];
399
+ /** Subgraph counter for DOT parsing. */
400
+ subgraphCounter = 0;
401
+ /** Edge counter for DOT parsing. */
402
+ edgeCounter = 0;
403
+ /** Edge lookup used to coalesce parallel edges in strict graphs. */
404
+ strictEdges = /* @__PURE__ */ new Map();
405
+ /** Persistent attributes and defaults for reopened named subgraphs. */
406
+ subgraphScopes = /* @__PURE__ */ new Map();
407
+ /** Explicit names reserved so anonymous subgraph IDs cannot collide with them. */
408
+ namedSubgraphIds = /* @__PURE__ */ new Set();
409
+ /** Initializes the token stream. */
410
+ constructor(tokens) {
411
+ this.tokens = tokens;
412
+ for (let index = 0; index < tokens.length - 1; index++) {
413
+ if (isKeyword(tokens[index], "subgraph") && isIdentifierLike(tokens[index + 1])) {
414
+ this.namedSubgraphIds.add(tokens[index + 1].value);
415
+ }
416
+ }
417
+ }
418
+ /** Parses one complete DOT document. */
419
+ parse() {
420
+ this.parseGraph();
421
+ this.consumeOptionalSemicolon();
422
+ if (this.peek()) {
423
+ throw new Error("Unexpected content after DOT graph.");
424
+ }
425
+ return this.result;
426
+ }
427
+ /** Reads graph identity, declaration, and statements. */
428
+ parseGraph() {
429
+ const strictToken = this.peek();
430
+ if (strictToken && isKeyword(strictToken, "strict")) {
431
+ this.consume();
432
+ this.result.strict = true;
433
+ }
434
+ const typeToken = this.peek();
435
+ if (!typeToken || !isGraphType(typeToken)) {
436
+ throw new Error("DOT graph must start with graph or digraph keyword.");
437
+ }
438
+ this.consume();
439
+ this.result.directed = isKeyword(typeToken, "digraph");
440
+ const idToken = this.peek();
441
+ if (idToken && isIdentifierLike(idToken)) {
442
+ this.result.id = parseIdentifierValue(this.consume());
443
+ }
444
+ this.expect("lbrace");
445
+ while (!this.match("rbrace")) {
446
+ if (!this.parseStatement()) {
447
+ throw new Error("Expected closing brace in DOT graph.");
448
+ }
449
+ }
450
+ this.result.graphAttributes = { ...this.scopes[0].graphAttributes };
451
+ }
452
+ /** Dispatches a graph statement. */
453
+ parseStatement() {
454
+ if (this.consumeSemicolonIfPresent()) {
455
+ return true;
456
+ }
457
+ const token = this.peek();
458
+ if (!token) {
459
+ return false;
460
+ }
461
+ if (this.tryParseSubgraphStatement(token)) {
462
+ return true;
463
+ }
464
+ if (this.tryParseKeywordStatement(token)) {
465
+ return true;
466
+ }
467
+ if (this.tryParseAssignmentStatement(token)) {
468
+ return true;
469
+ }
470
+ if (isIdentifierLike(token)) {
471
+ this.parseNodeOrEdgeStatement();
472
+ this.consumeOptionalSemicolon();
473
+ return true;
474
+ }
475
+ throw new Error(`Unexpected token: ${token.value}`);
476
+ }
477
+ /** Consumes an empty statement. */
478
+ consumeSemicolonIfPresent() {
479
+ const next = this.peek();
480
+ if ((next == null ? void 0 : next.type) === "semicolon") {
481
+ this.consume();
482
+ return true;
483
+ }
484
+ return false;
485
+ }
486
+ /** Reads a standalone subgraph statement. */
487
+ tryParseSubgraphStatement(token) {
488
+ if (token.type !== "lbrace" && !isKeyword(token, "subgraph")) {
489
+ return false;
490
+ }
491
+ this.parseSubgraph();
492
+ return true;
493
+ }
494
+ /** Applies graph, node, or edge defaults. */
495
+ tryParseKeywordStatement(token) {
496
+ if (!(isKeyword(token, "graph") || isKeyword(token, "node") || isKeyword(token, "edge"))) {
497
+ return false;
498
+ }
499
+ this.consume();
500
+ const explicitAttributes = this.parseAttributeList();
501
+ if (isKeyword(token, "graph")) {
502
+ Object.assign(this.currentScope().graphAttributes, explicitAttributes);
503
+ } else if (isKeyword(token, "node")) {
504
+ Object.assign(this.currentScope().nodeDefaults, explicitAttributes);
505
+ } else {
506
+ Object.assign(this.currentScope().edgeDefaults, explicitAttributes);
507
+ }
508
+ this.consumeOptionalSemicolon();
509
+ return true;
510
+ }
511
+ /** Reads a graph attribute assignment. */
512
+ tryParseAssignmentStatement(token) {
513
+ if (!isIdentifierLike(token)) {
514
+ return false;
515
+ }
516
+ const next = this.peek(1);
517
+ if ((next == null ? void 0 : next.type) !== "equals") {
518
+ return false;
519
+ }
520
+ const key = parseIdentifierValue(this.consume());
521
+ this.consume();
522
+ const valueToken = this.consume();
523
+ const value = parseAttributeValue(valueToken);
524
+ this.currentScope().graphAttributes[key] = value;
525
+ this.consumeOptionalSemicolon();
526
+ return true;
527
+ }
528
+ /** Reads a node statement or chained edges. */
529
+ parseNodeOrEdgeStatement() {
530
+ const first = parseIdentifierValue(this.consume());
531
+ const references = [first];
532
+ const operators = [];
533
+ let operatorToken = this.match("arrow");
534
+ while (operatorToken) {
535
+ operators.push(operatorToken.value);
536
+ const referenceToken = this.consume();
537
+ if (!isIdentifierLike(referenceToken)) {
538
+ throw new Error("Expected node identifier in edge statement.");
539
+ }
540
+ references.push(parseIdentifierValue(referenceToken));
541
+ operatorToken = this.match("arrow");
542
+ }
543
+ if (operators.length === 0) {
544
+ const explicitAttributes2 = this.parseAttributeList();
545
+ this.addNode(first, explicitAttributes2);
546
+ return;
547
+ }
548
+ const explicitAttributes = this.parseAttributeList();
549
+ this.addEdgeChain(references, operators, explicitAttributes);
550
+ }
551
+ /** Merges explicit attributes into a node. */
552
+ addNode(id, explicitAttributes) {
553
+ const membership = this.getCurrentSubgraphChain();
554
+ const node = this.ensureNode(id, membership);
555
+ node.attributes = { ...node.attributes, ...explicitAttributes };
556
+ }
557
+ /** Expands an edge chain into node and edge records. */
558
+ addEdgeChain(nodes, operators, explicitAttributes) {
559
+ const membership = this.getCurrentSubgraphChain();
560
+ const defaults = this.currentScope().edgeDefaults;
561
+ const attributes = { ...defaults, ...explicitAttributes };
562
+ for (let index = 0; index < nodes.length - 1; index++) {
563
+ const sourceId = nodes[index];
564
+ const targetId = nodes[index + 1];
565
+ const operator = operators[index];
566
+ const directed = operator === "->";
567
+ if (directed !== this.result.directed) {
568
+ throw new Error("DOT edge operator must match graph or digraph declaration.");
569
+ }
570
+ const edgeId = deriveEdgeId(attributes, sourceId, targetId, ++this.edgeCounter);
571
+ const edgeAttributes = { ...attributes };
572
+ this.ensureNode(sourceId, membership);
573
+ this.ensureNode(targetId, membership);
574
+ const strictKey = JSON.stringify(
575
+ directed ? [sourceId, targetId] : [sourceId, targetId].sort()
576
+ );
577
+ const existingEdge = this.result.strict ? this.strictEdges.get(strictKey) : void 0;
578
+ if (existingEdge) {
579
+ existingEdge.attributes = { ...existingEdge.attributes, ...explicitAttributes };
580
+ existingEdge.directed = deriveDirectedFlag(existingEdge.attributes, directed);
581
+ existingEdge.subgraphs = Array.from(/* @__PURE__ */ new Set([...existingEdge.subgraphs, ...membership]));
582
+ continue;
583
+ }
584
+ const directedOverride = deriveDirectedFlag(edgeAttributes, directed);
585
+ const edge = {
586
+ id: edgeId,
587
+ sourceId,
588
+ targetId,
589
+ directed: directedOverride,
590
+ attributes: edgeAttributes,
591
+ subgraphs: membership
592
+ };
593
+ this.result.edges.push(edge);
594
+ if (this.result.strict) {
595
+ this.strictEdges.set(strictKey, edge);
596
+ }
597
+ }
598
+ }
599
+ /** Reads a nested scope with inherited defaults. */
600
+ parseSubgraph() {
601
+ let idToken = this.peek();
602
+ let subgraphId;
603
+ if (idToken && isKeyword(idToken, "subgraph")) {
604
+ this.consume();
605
+ idToken = this.peek();
606
+ }
607
+ if (idToken && isIdentifierLike(idToken)) {
608
+ subgraphId = parseIdentifierValue(this.consume());
609
+ } else {
610
+ do {
611
+ subgraphId = `subgraph_${++this.subgraphCounter}`;
612
+ } while (this.namedSubgraphIds.has(subgraphId));
613
+ }
614
+ this.expect("lbrace");
615
+ const parentId = this.findCurrentSubgraphId();
616
+ let context = this.subgraphScopes.get(subgraphId);
617
+ if (!context) {
618
+ context = {
619
+ id: subgraphId,
620
+ nodeDefaults: Object.assign(/* @__PURE__ */ Object.create(null), this.currentScope().nodeDefaults),
621
+ edgeDefaults: Object.assign(/* @__PURE__ */ Object.create(null), this.currentScope().edgeDefaults),
622
+ graphAttributes: /* @__PURE__ */ Object.create(null)
623
+ };
624
+ this.subgraphScopes.set(subgraphId, context);
625
+ this.result.subgraphs.set(subgraphId, {
626
+ id: subgraphId,
627
+ attributes: context.graphAttributes,
628
+ parentId
629
+ });
630
+ }
631
+ this.scopes.push(context);
632
+ let shouldContinue = true;
633
+ while (shouldContinue) {
634
+ if (this.match("rbrace")) {
635
+ break;
636
+ }
637
+ shouldContinue = this.parseStatement();
638
+ if (!shouldContinue) {
639
+ throw new Error("Expected closing brace in DOT subgraph.");
640
+ }
641
+ }
642
+ this.scopes.pop();
643
+ }
644
+ /** Reads repeated attribute lists. */
645
+ parseAttributeList() {
646
+ var _a, _b;
647
+ const attributes = /* @__PURE__ */ Object.create(null);
648
+ while (this.match("lbrack")) {
649
+ while (!this.match("rbrack")) {
650
+ const keyToken = this.consume();
651
+ if (!isIdentifierLike(keyToken)) {
652
+ throw new Error("Expected attribute name.");
653
+ }
654
+ const key = parseIdentifierValue(keyToken);
655
+ let value = true;
656
+ if (this.match("equals")) {
657
+ const valueToken = this.consume();
658
+ value = parseAttributeValue(valueToken);
659
+ }
660
+ attributes[key] = value;
661
+ if (((_a = this.peek()) == null ? void 0 : _a.type) === "comma" || ((_b = this.peek()) == null ? void 0 : _b.type) === "semicolon") {
662
+ this.consume();
663
+ }
664
+ }
665
+ }
666
+ return attributes;
667
+ }
668
+ /** Creates an implicit node and records its subgraph memberships. */
669
+ ensureNode(id, membership) {
670
+ let node = this.result.nodes.get(id);
671
+ if (!node) {
672
+ const defaults = this.currentScope().nodeDefaults;
673
+ node = { id, attributes: { ...defaults }, subgraphs: /* @__PURE__ */ new Set() };
674
+ this.result.nodes.set(id, node);
675
+ }
676
+ membership.forEach((subgraphId) => node.subgraphs.add(subgraphId));
677
+ return node;
678
+ }
679
+ /** Lists enclosing subgraphs. */
680
+ getCurrentSubgraphChain() {
681
+ const chain = [];
682
+ for (const scope of this.scopes) {
683
+ if (scope.id) {
684
+ chain.push(scope.id);
685
+ }
686
+ }
687
+ return chain;
688
+ }
689
+ /** Finds the nearest enclosing named or generated subgraph. */
690
+ findCurrentSubgraphId() {
691
+ for (let index = this.scopes.length - 1; index >= 0; index--) {
692
+ const scope = this.scopes[index];
693
+ if (scope.id) {
694
+ return scope.id;
695
+ }
696
+ }
697
+ return void 0;
698
+ }
699
+ /** Returns the active attribute scope. */
700
+ currentScope() {
701
+ return this.scopes[this.scopes.length - 1];
702
+ }
703
+ /** Consumes an optional statement terminator. */
704
+ consumeOptionalSemicolon() {
705
+ const next = this.peek();
706
+ if ((next == null ? void 0 : next.type) === "semicolon") {
707
+ this.consume();
708
+ }
709
+ }
710
+ /** Requires the next token to have the requested type. */
711
+ expect(type) {
712
+ const token = this.consume();
713
+ if (!token || token.type !== type) {
714
+ throw new Error(`Expected token ${type}.`);
715
+ }
716
+ return token;
717
+ }
718
+ /** Consumes the next token or reports truncated input. */
719
+ consume() {
720
+ const token = this.tokens[this.position];
721
+ if (!token) {
722
+ throw new Error("Unexpected end of DOT input.");
723
+ }
724
+ this.position++;
725
+ return token;
726
+ }
727
+ /** Consumes a token when its type matches. */
728
+ match(type) {
729
+ const token = this.peek();
730
+ if (token && token.type === type) {
731
+ this.position++;
732
+ return token;
733
+ }
734
+ return null;
735
+ }
736
+ /** Inspects a token without consuming it. */
737
+ peek(offset = 0) {
738
+ return this.tokens[this.position + offset] ?? null;
739
+ }
740
+ };
741
+ IDENTIFIER_TERMINATORS = /* @__PURE__ */ new Set(["{", "}", "[", "]", "=", ";", ",", '"', "<", "#"]);
742
+ }
743
+ });
744
+
745
+ // src/dot-loader.ts
746
+ var dot_loader_exports = {};
747
+ __export(dot_loader_exports, {
748
+ DOTLoaderWithParser: () => DOTLoaderWithParser
749
+ });
750
+ var DOTLoaderWithParser;
751
+ var init_dot_loader = __esm({
752
+ "src/dot-loader.ts"() {
753
+ "use strict";
754
+ init_dot_loader_types();
755
+ init_parse_dot();
756
+ DOTLoaderWithParser = {
757
+ ...DOTLoader,
758
+ parse: async (data) => parseDOT(new TextDecoder().decode(data)),
759
+ parseSync: (data) => parseDOT(new TextDecoder().decode(data)),
760
+ parseTextSync: parseDOT
761
+ };
762
+ }
763
+ });
764
+
765
+ // dist/dot-loader.js
766
+ var dot_loader_exports2 = {};
767
+ __export(dot_loader_exports2, {
768
+ DOTLoaderWithParser: () => DOTLoaderWithParser2
769
+ });
770
+ module.exports = __toCommonJS(dot_loader_exports2);
771
+
772
+ // dist/dot-loader-types.js
773
+ var VERSION2 = true ? "5.0.0-alpha.9" : "latest";
774
+ async function preload2() {
775
+ const { DOTLoaderWithParser: DOTLoaderWithParser3 } = await Promise.resolve().then(() => (init_dot_loader(), dot_loader_exports));
776
+ return DOTLoaderWithParser3;
777
+ }
778
+ var DOTLoader2 = {
779
+ name: "DOT",
780
+ id: "dot",
781
+ module: "graphs",
782
+ version: VERSION2,
783
+ extensions: ["dot", "gv"],
784
+ mimeTypes: ["text/vnd.graphviz", "text/x-graphviz", "application/vnd.graphviz"],
785
+ text: true,
786
+ worker: false,
787
+ dataType: null,
788
+ batchType: null,
789
+ options: {},
790
+ preload: preload2
791
+ };
792
+
793
+ // dist/lib/parse-dot.js
794
+ function parseDOT2(input) {
795
+ const parsed = parseDOTDocument2(input);
796
+ const subgraphs = Array.from(parsed.subgraphs.values(), (subgraph) => ({
797
+ ...subgraph,
798
+ attributes: { ...subgraph.attributes }
799
+ }));
800
+ return {
801
+ shape: "plain-graph-data",
802
+ nodes: Array.from(parsed.nodes.values(), (node) => ({
803
+ id: node.id,
804
+ ...typeof node.attributes.label === "string" ? { label: node.attributes.label } : {},
805
+ attributes: {
806
+ ...node.attributes,
807
+ ...node.subgraphs.size ? { subgraphs: Array.from(node.subgraphs, (id) => describeSubgraph2(id, parsed.subgraphs)) } : {}
808
+ }
809
+ })),
810
+ edges: parsed.edges.map((edge) => ({
811
+ id: edge.id,
812
+ sourceId: edge.sourceId,
813
+ targetId: edge.targetId,
814
+ directed: edge.directed,
815
+ ...typeof edge.attributes.label === "string" ? { label: edge.attributes.label } : {},
816
+ attributes: {
817
+ ...edge.attributes,
818
+ ...edge.subgraphs.length ? { subgraphs: edge.subgraphs.map((id) => describeSubgraph2(id, parsed.subgraphs)) } : {}
819
+ }
820
+ })),
821
+ metadata: {
822
+ id: parsed.id,
823
+ directed: parsed.directed,
824
+ strict: parsed.strict,
825
+ attributes: { ...parsed.graphAttributes },
826
+ subgraphs
827
+ }
828
+ };
829
+ }
830
+ function describeSubgraph2(id, descriptors) {
831
+ const subgraph = descriptors.get(id);
832
+ return {
833
+ id,
834
+ attributes: { ...subgraph.attributes },
835
+ parentId: subgraph.parentId
836
+ };
837
+ }
838
+ var DOTParser2 = class {
839
+ /** Tokens for DOT parsing. */
840
+ tokens;
841
+ /** Position for DOT parsing. */
842
+ position = 0;
843
+ /** Accumulated graph records and attributes. */
844
+ result = {
845
+ directed: false,
846
+ strict: false,
847
+ graphAttributes: /* @__PURE__ */ Object.create(null),
848
+ nodes: /* @__PURE__ */ new Map(),
849
+ edges: [],
850
+ subgraphs: /* @__PURE__ */ new Map()
851
+ };
852
+ /** Scopes for DOT parsing. */
853
+ scopes = [
854
+ {
855
+ nodeDefaults: /* @__PURE__ */ Object.create(null),
856
+ edgeDefaults: /* @__PURE__ */ Object.create(null),
857
+ graphAttributes: /* @__PURE__ */ Object.create(null)
858
+ }
859
+ ];
860
+ /** Subgraph counter for DOT parsing. */
861
+ subgraphCounter = 0;
862
+ /** Edge counter for DOT parsing. */
863
+ edgeCounter = 0;
864
+ /** Edge lookup used to coalesce parallel edges in strict graphs. */
865
+ strictEdges = /* @__PURE__ */ new Map();
866
+ /** Persistent attributes and defaults for reopened named subgraphs. */
867
+ subgraphScopes = /* @__PURE__ */ new Map();
868
+ /** Explicit names reserved so anonymous subgraph IDs cannot collide with them. */
869
+ namedSubgraphIds = /* @__PURE__ */ new Set();
870
+ /** Initializes the token stream. */
871
+ constructor(tokens) {
872
+ this.tokens = tokens;
873
+ for (let index = 0; index < tokens.length - 1; index++) {
874
+ if (isKeyword2(tokens[index], "subgraph") && isIdentifierLike2(tokens[index + 1])) {
875
+ this.namedSubgraphIds.add(tokens[index + 1].value);
876
+ }
877
+ }
878
+ }
879
+ /** Parses one complete DOT document. */
880
+ parse() {
881
+ this.parseGraph();
882
+ this.consumeOptionalSemicolon();
883
+ if (this.peek()) {
884
+ throw new Error("Unexpected content after DOT graph.");
885
+ }
886
+ return this.result;
887
+ }
888
+ /** Reads graph identity, declaration, and statements. */
889
+ parseGraph() {
890
+ const strictToken = this.peek();
891
+ if (strictToken && isKeyword2(strictToken, "strict")) {
892
+ this.consume();
893
+ this.result.strict = true;
894
+ }
895
+ const typeToken = this.peek();
896
+ if (!typeToken || !isGraphType2(typeToken)) {
897
+ throw new Error("DOT graph must start with graph or digraph keyword.");
898
+ }
899
+ this.consume();
900
+ this.result.directed = isKeyword2(typeToken, "digraph");
901
+ const idToken = this.peek();
902
+ if (idToken && isIdentifierLike2(idToken)) {
903
+ this.result.id = parseIdentifierValue2(this.consume());
904
+ }
905
+ this.expect("lbrace");
906
+ while (!this.match("rbrace")) {
907
+ if (!this.parseStatement()) {
908
+ throw new Error("Expected closing brace in DOT graph.");
909
+ }
910
+ }
911
+ this.result.graphAttributes = { ...this.scopes[0].graphAttributes };
912
+ }
913
+ /** Dispatches a graph statement. */
914
+ parseStatement() {
915
+ if (this.consumeSemicolonIfPresent()) {
916
+ return true;
917
+ }
918
+ const token = this.peek();
919
+ if (!token) {
920
+ return false;
921
+ }
922
+ if (this.tryParseSubgraphStatement(token)) {
923
+ return true;
924
+ }
925
+ if (this.tryParseKeywordStatement(token)) {
926
+ return true;
927
+ }
928
+ if (this.tryParseAssignmentStatement(token)) {
929
+ return true;
930
+ }
931
+ if (isIdentifierLike2(token)) {
932
+ this.parseNodeOrEdgeStatement();
933
+ this.consumeOptionalSemicolon();
934
+ return true;
935
+ }
936
+ throw new Error(`Unexpected token: ${token.value}`);
937
+ }
938
+ /** Consumes an empty statement. */
939
+ consumeSemicolonIfPresent() {
940
+ const next = this.peek();
941
+ if ((next == null ? void 0 : next.type) === "semicolon") {
942
+ this.consume();
943
+ return true;
944
+ }
945
+ return false;
946
+ }
947
+ /** Reads a standalone subgraph statement. */
948
+ tryParseSubgraphStatement(token) {
949
+ if (token.type !== "lbrace" && !isKeyword2(token, "subgraph")) {
950
+ return false;
951
+ }
952
+ this.parseSubgraph();
953
+ return true;
954
+ }
955
+ /** Applies graph, node, or edge defaults. */
956
+ tryParseKeywordStatement(token) {
957
+ if (!(isKeyword2(token, "graph") || isKeyword2(token, "node") || isKeyword2(token, "edge"))) {
958
+ return false;
959
+ }
960
+ this.consume();
961
+ const explicitAttributes = this.parseAttributeList();
962
+ if (isKeyword2(token, "graph")) {
963
+ Object.assign(this.currentScope().graphAttributes, explicitAttributes);
964
+ } else if (isKeyword2(token, "node")) {
965
+ Object.assign(this.currentScope().nodeDefaults, explicitAttributes);
966
+ } else {
967
+ Object.assign(this.currentScope().edgeDefaults, explicitAttributes);
968
+ }
969
+ this.consumeOptionalSemicolon();
970
+ return true;
971
+ }
972
+ /** Reads a graph attribute assignment. */
973
+ tryParseAssignmentStatement(token) {
974
+ if (!isIdentifierLike2(token)) {
975
+ return false;
976
+ }
977
+ const next = this.peek(1);
978
+ if ((next == null ? void 0 : next.type) !== "equals") {
979
+ return false;
980
+ }
981
+ const key = parseIdentifierValue2(this.consume());
982
+ this.consume();
983
+ const valueToken = this.consume();
984
+ const value = parseAttributeValue2(valueToken);
985
+ this.currentScope().graphAttributes[key] = value;
986
+ this.consumeOptionalSemicolon();
987
+ return true;
988
+ }
989
+ /** Reads a node statement or chained edges. */
990
+ parseNodeOrEdgeStatement() {
991
+ const first = parseIdentifierValue2(this.consume());
992
+ const references = [first];
993
+ const operators = [];
994
+ let operatorToken = this.match("arrow");
995
+ while (operatorToken) {
996
+ operators.push(operatorToken.value);
997
+ const referenceToken = this.consume();
998
+ if (!isIdentifierLike2(referenceToken)) {
999
+ throw new Error("Expected node identifier in edge statement.");
1000
+ }
1001
+ references.push(parseIdentifierValue2(referenceToken));
1002
+ operatorToken = this.match("arrow");
1003
+ }
1004
+ if (operators.length === 0) {
1005
+ const explicitAttributes2 = this.parseAttributeList();
1006
+ this.addNode(first, explicitAttributes2);
1007
+ return;
1008
+ }
1009
+ const explicitAttributes = this.parseAttributeList();
1010
+ this.addEdgeChain(references, operators, explicitAttributes);
1011
+ }
1012
+ /** Merges explicit attributes into a node. */
1013
+ addNode(id, explicitAttributes) {
1014
+ const membership = this.getCurrentSubgraphChain();
1015
+ const node = this.ensureNode(id, membership);
1016
+ node.attributes = { ...node.attributes, ...explicitAttributes };
1017
+ }
1018
+ /** Expands an edge chain into node and edge records. */
1019
+ addEdgeChain(nodes, operators, explicitAttributes) {
1020
+ const membership = this.getCurrentSubgraphChain();
1021
+ const defaults = this.currentScope().edgeDefaults;
1022
+ const attributes = { ...defaults, ...explicitAttributes };
1023
+ for (let index = 0; index < nodes.length - 1; index++) {
1024
+ const sourceId = nodes[index];
1025
+ const targetId = nodes[index + 1];
1026
+ const operator = operators[index];
1027
+ const directed = operator === "->";
1028
+ if (directed !== this.result.directed) {
1029
+ throw new Error("DOT edge operator must match graph or digraph declaration.");
1030
+ }
1031
+ const edgeId = deriveEdgeId2(attributes, sourceId, targetId, ++this.edgeCounter);
1032
+ const edgeAttributes = { ...attributes };
1033
+ this.ensureNode(sourceId, membership);
1034
+ this.ensureNode(targetId, membership);
1035
+ const strictKey = JSON.stringify(directed ? [sourceId, targetId] : [sourceId, targetId].sort());
1036
+ const existingEdge = this.result.strict ? this.strictEdges.get(strictKey) : void 0;
1037
+ if (existingEdge) {
1038
+ existingEdge.attributes = { ...existingEdge.attributes, ...explicitAttributes };
1039
+ existingEdge.directed = deriveDirectedFlag2(existingEdge.attributes, directed);
1040
+ existingEdge.subgraphs = Array.from(/* @__PURE__ */ new Set([...existingEdge.subgraphs, ...membership]));
1041
+ continue;
1042
+ }
1043
+ const directedOverride = deriveDirectedFlag2(edgeAttributes, directed);
1044
+ const edge = {
1045
+ id: edgeId,
1046
+ sourceId,
1047
+ targetId,
1048
+ directed: directedOverride,
1049
+ attributes: edgeAttributes,
1050
+ subgraphs: membership
1051
+ };
1052
+ this.result.edges.push(edge);
1053
+ if (this.result.strict) {
1054
+ this.strictEdges.set(strictKey, edge);
1055
+ }
1056
+ }
1057
+ }
1058
+ /** Reads a nested scope with inherited defaults. */
1059
+ parseSubgraph() {
1060
+ let idToken = this.peek();
1061
+ let subgraphId;
1062
+ if (idToken && isKeyword2(idToken, "subgraph")) {
1063
+ this.consume();
1064
+ idToken = this.peek();
1065
+ }
1066
+ if (idToken && isIdentifierLike2(idToken)) {
1067
+ subgraphId = parseIdentifierValue2(this.consume());
1068
+ } else {
1069
+ do {
1070
+ subgraphId = `subgraph_${++this.subgraphCounter}`;
1071
+ } while (this.namedSubgraphIds.has(subgraphId));
1072
+ }
1073
+ this.expect("lbrace");
1074
+ const parentId = this.findCurrentSubgraphId();
1075
+ let context = this.subgraphScopes.get(subgraphId);
1076
+ if (!context) {
1077
+ context = {
1078
+ id: subgraphId,
1079
+ nodeDefaults: Object.assign(/* @__PURE__ */ Object.create(null), this.currentScope().nodeDefaults),
1080
+ edgeDefaults: Object.assign(/* @__PURE__ */ Object.create(null), this.currentScope().edgeDefaults),
1081
+ graphAttributes: /* @__PURE__ */ Object.create(null)
1082
+ };
1083
+ this.subgraphScopes.set(subgraphId, context);
1084
+ this.result.subgraphs.set(subgraphId, {
1085
+ id: subgraphId,
1086
+ attributes: context.graphAttributes,
1087
+ parentId
1088
+ });
1089
+ }
1090
+ this.scopes.push(context);
1091
+ let shouldContinue = true;
1092
+ while (shouldContinue) {
1093
+ if (this.match("rbrace")) {
1094
+ break;
1095
+ }
1096
+ shouldContinue = this.parseStatement();
1097
+ if (!shouldContinue) {
1098
+ throw new Error("Expected closing brace in DOT subgraph.");
1099
+ }
1100
+ }
1101
+ this.scopes.pop();
1102
+ }
1103
+ /** Reads repeated attribute lists. */
1104
+ parseAttributeList() {
1105
+ var _a, _b;
1106
+ const attributes = /* @__PURE__ */ Object.create(null);
1107
+ while (this.match("lbrack")) {
1108
+ while (!this.match("rbrack")) {
1109
+ const keyToken = this.consume();
1110
+ if (!isIdentifierLike2(keyToken)) {
1111
+ throw new Error("Expected attribute name.");
1112
+ }
1113
+ const key = parseIdentifierValue2(keyToken);
1114
+ let value = true;
1115
+ if (this.match("equals")) {
1116
+ const valueToken = this.consume();
1117
+ value = parseAttributeValue2(valueToken);
1118
+ }
1119
+ attributes[key] = value;
1120
+ if (((_a = this.peek()) == null ? void 0 : _a.type) === "comma" || ((_b = this.peek()) == null ? void 0 : _b.type) === "semicolon") {
1121
+ this.consume();
1122
+ }
1123
+ }
1124
+ }
1125
+ return attributes;
1126
+ }
1127
+ /** Creates an implicit node and records its subgraph memberships. */
1128
+ ensureNode(id, membership) {
1129
+ let node = this.result.nodes.get(id);
1130
+ if (!node) {
1131
+ const defaults = this.currentScope().nodeDefaults;
1132
+ node = { id, attributes: { ...defaults }, subgraphs: /* @__PURE__ */ new Set() };
1133
+ this.result.nodes.set(id, node);
1134
+ }
1135
+ membership.forEach((subgraphId) => node.subgraphs.add(subgraphId));
1136
+ return node;
1137
+ }
1138
+ /** Lists enclosing subgraphs. */
1139
+ getCurrentSubgraphChain() {
1140
+ const chain = [];
1141
+ for (const scope of this.scopes) {
1142
+ if (scope.id) {
1143
+ chain.push(scope.id);
1144
+ }
1145
+ }
1146
+ return chain;
1147
+ }
1148
+ /** Finds the nearest enclosing named or generated subgraph. */
1149
+ findCurrentSubgraphId() {
1150
+ for (let index = this.scopes.length - 1; index >= 0; index--) {
1151
+ const scope = this.scopes[index];
1152
+ if (scope.id) {
1153
+ return scope.id;
1154
+ }
1155
+ }
1156
+ return void 0;
1157
+ }
1158
+ /** Returns the active attribute scope. */
1159
+ currentScope() {
1160
+ return this.scopes[this.scopes.length - 1];
1161
+ }
1162
+ /** Consumes an optional statement terminator. */
1163
+ consumeOptionalSemicolon() {
1164
+ const next = this.peek();
1165
+ if ((next == null ? void 0 : next.type) === "semicolon") {
1166
+ this.consume();
1167
+ }
1168
+ }
1169
+ /** Requires the next token to have the requested type. */
1170
+ expect(type) {
1171
+ const token = this.consume();
1172
+ if (!token || token.type !== type) {
1173
+ throw new Error(`Expected token ${type}.`);
1174
+ }
1175
+ return token;
1176
+ }
1177
+ /** Consumes the next token or reports truncated input. */
1178
+ consume() {
1179
+ const token = this.tokens[this.position];
1180
+ if (!token) {
1181
+ throw new Error("Unexpected end of DOT input.");
1182
+ }
1183
+ this.position++;
1184
+ return token;
1185
+ }
1186
+ /** Consumes a token when its type matches. */
1187
+ match(type) {
1188
+ const token = this.peek();
1189
+ if (token && token.type === type) {
1190
+ this.position++;
1191
+ return token;
1192
+ }
1193
+ return null;
1194
+ }
1195
+ /** Inspects a token without consuming it. */
1196
+ peek(offset = 0) {
1197
+ return this.tokens[this.position + offset] ?? null;
1198
+ }
1199
+ };
1200
+ function parseDOTDocument2(input) {
1201
+ const parser = new DOTParser2(tokenize2(input));
1202
+ return parser.parse();
1203
+ }
1204
+ var IDENTIFIER_TERMINATORS2 = /* @__PURE__ */ new Set(["{", "}", "[", "]", "=", ";", ",", '"', "<", "#"]);
1205
+ function tokenize2(input) {
1206
+ const tokens = [];
1207
+ let index = 0;
1208
+ while (index < input.length) {
1209
+ const { token, nextIndex } = readNextToken2(input, index);
1210
+ if (nextIndex <= index) {
1211
+ throw new Error(`Tokenizer did not advance at position ${index}.`);
1212
+ }
1213
+ if (token) {
1214
+ tokens.push(token);
1215
+ }
1216
+ index = nextIndex;
1217
+ }
1218
+ return tokens;
1219
+ }
1220
+ function readNextToken2(input, index) {
1221
+ if (index >= input.length) {
1222
+ return { token: null, nextIndex: input.length };
1223
+ }
1224
+ const character = input[index];
1225
+ if (isWhitespace2(character)) {
1226
+ return { token: null, nextIndex: index + 1 };
1227
+ }
1228
+ const commentEnd = skipComment2(input, index);
1229
+ if (commentEnd !== null) {
1230
+ return { token: null, nextIndex: commentEnd };
1231
+ }
1232
+ const arrowToken = readArrowToken2(input, index);
1233
+ if (arrowToken) {
1234
+ return arrowToken;
1235
+ }
1236
+ const punctuation = readPunctuationToken2(character);
1237
+ if (punctuation) {
1238
+ return { token: punctuation, nextIndex: index + 1 };
1239
+ }
1240
+ if (character === '"') {
1241
+ const { value, nextIndex } = readQuotedString2(input, index + 1);
1242
+ return { token: { type: "string", value }, nextIndex };
1243
+ }
1244
+ if (character === "<") {
1245
+ const { value, nextIndex } = readHtmlString2(input, index + 1);
1246
+ return { token: { type: "html", value }, nextIndex };
1247
+ }
1248
+ const identifier = readIdentifier2(input, index);
1249
+ if (identifier.value) {
1250
+ return {
1251
+ token: { type: "identifier", value: identifier.value },
1252
+ nextIndex: identifier.nextIndex
1253
+ };
1254
+ }
1255
+ throw new Error(`Unexpected token at position ${index}.`);
1256
+ }
1257
+ function skipComment2(input, index) {
1258
+ const character = input[index];
1259
+ if (character === "/") {
1260
+ const next = input[index + 1];
1261
+ if (next === "/") {
1262
+ return skipLineComment2(input, index + 2);
1263
+ }
1264
+ if (next === "*") {
1265
+ return skipBlockComment2(input, index + 2);
1266
+ }
1267
+ return null;
1268
+ }
1269
+ if (character === "#") {
1270
+ return skipLineComment2(input, index + 1);
1271
+ }
1272
+ return null;
1273
+ }
1274
+ function skipLineComment2(input, startIndex) {
1275
+ let cursor = startIndex;
1276
+ while (cursor < input.length && !isLineBreak2(input[cursor])) {
1277
+ cursor++;
1278
+ }
1279
+ return cursor;
1280
+ }
1281
+ function skipBlockComment2(input, startIndex) {
1282
+ let cursor = startIndex;
1283
+ while (cursor < input.length) {
1284
+ if (input[cursor] === "*" && input[cursor + 1] === "/") {
1285
+ return cursor + 2;
1286
+ }
1287
+ cursor++;
1288
+ }
1289
+ throw new Error("Unterminated block comment in DOT source.");
1290
+ }
1291
+ function readArrowToken2(input, index) {
1292
+ const next = input[index + 1];
1293
+ if (input[index] === "-" && next === "-") {
1294
+ return { token: { type: "arrow", value: "--" }, nextIndex: index + 2 };
1295
+ }
1296
+ if (input[index] === "-" && next === ">") {
1297
+ return { token: { type: "arrow", value: "->" }, nextIndex: index + 2 };
1298
+ }
1299
+ return null;
1300
+ }
1301
+ function readPunctuationToken2(character) {
1302
+ switch (character) {
1303
+ case "{":
1304
+ return { type: "lbrace", value: character };
1305
+ case "}":
1306
+ return { type: "rbrace", value: character };
1307
+ case "[":
1308
+ return { type: "lbrack", value: character };
1309
+ case "]":
1310
+ return { type: "rbrack", value: character };
1311
+ case "=":
1312
+ return { type: "equals", value: character };
1313
+ case ",":
1314
+ return { type: "comma", value: character };
1315
+ case ";":
1316
+ return { type: "semicolon", value: character };
1317
+ default:
1318
+ return null;
1319
+ }
1320
+ }
1321
+ function readQuotedString2(input, startIndex) {
1322
+ let value = "";
1323
+ let index = startIndex;
1324
+ while (index < input.length) {
1325
+ const character = input[index];
1326
+ if (character === '"') {
1327
+ return { value, nextIndex: index + 1 };
1328
+ }
1329
+ if (character === "\\") {
1330
+ const escape = readEscapedCharacter2(input, index + 1);
1331
+ value += escape.value;
1332
+ index = escape.nextIndex;
1333
+ } else {
1334
+ value += character;
1335
+ index++;
1336
+ }
1337
+ }
1338
+ throw new Error("Unterminated string literal in DOT source.");
1339
+ }
1340
+ function readEscapedCharacter2(input, startIndex) {
1341
+ const next = input[startIndex];
1342
+ switch (next) {
1343
+ case "n":
1344
+ case "l":
1345
+ case "L":
1346
+ return { value: "\n", nextIndex: startIndex + 1 };
1347
+ case "t":
1348
+ return { value: " ", nextIndex: startIndex + 1 };
1349
+ case "r":
1350
+ return { value: "\r", nextIndex: startIndex + 1 };
1351
+ case '"':
1352
+ return { value: '"', nextIndex: startIndex + 1 };
1353
+ case "\\":
1354
+ return { value: "\\", nextIndex: startIndex + 1 };
1355
+ default: {
1356
+ if (typeof next === "undefined") {
1357
+ throw new Error("Unterminated escape sequence in DOT source.");
1358
+ }
1359
+ return { value: next, nextIndex: startIndex + 1 };
1360
+ }
1361
+ }
1362
+ }
1363
+ function readHtmlString2(input, startIndex) {
1364
+ let value = "<";
1365
+ let depth = 1;
1366
+ let index = startIndex;
1367
+ while (index < input.length) {
1368
+ const character = input[index];
1369
+ value += character;
1370
+ if (character === "<") {
1371
+ depth++;
1372
+ } else if (character === ">") {
1373
+ depth--;
1374
+ if (depth === 0) {
1375
+ return { value, nextIndex: index + 1 };
1376
+ }
1377
+ }
1378
+ index++;
1379
+ }
1380
+ throw new Error("Unterminated HTML-like string literal in DOT source.");
1381
+ }
1382
+ function readIdentifier2(input, startIndex) {
1383
+ let index = startIndex;
1384
+ let value = "";
1385
+ while (index < input.length && !isIdentifierTerminator2(input, index)) {
1386
+ value += input[index];
1387
+ index++;
1388
+ }
1389
+ if (value.includes(":")) {
1390
+ throw new Error("DOT node ports are not supported; quote identifiers containing colons.");
1391
+ }
1392
+ return { value, nextIndex: index };
1393
+ }
1394
+ function isIdentifierTerminator2(input, index) {
1395
+ const character = input[index];
1396
+ if (isWhitespace2(character) || IDENTIFIER_TERMINATORS2.has(character)) {
1397
+ return true;
1398
+ }
1399
+ if (isArrowOperatorStart2(input, index)) {
1400
+ return true;
1401
+ }
1402
+ if (isCommentStart2(input, index)) {
1403
+ return true;
1404
+ }
1405
+ return false;
1406
+ }
1407
+ function isArrowOperatorStart2(input, index) {
1408
+ if (input[index] !== "-") {
1409
+ return false;
1410
+ }
1411
+ const next = input[index + 1];
1412
+ return next === "-" || next === ">";
1413
+ }
1414
+ function isCommentStart2(input, index) {
1415
+ if (input[index] !== "/") {
1416
+ return false;
1417
+ }
1418
+ const next = input[index + 1];
1419
+ return next === "/" || next === "*";
1420
+ }
1421
+ function isWhitespace2(character) {
1422
+ return character === " " || character === "\n" || character === "\r" || character === " " || character === "\f";
1423
+ }
1424
+ function isLineBreak2(character) {
1425
+ return character === "\n" || character === "\r";
1426
+ }
1427
+ function isKeyword2(token, keyword) {
1428
+ return token.type === "identifier" && token.value.toLowerCase() === keyword.toLowerCase();
1429
+ }
1430
+ function isGraphType2(token) {
1431
+ return isKeyword2(token, "graph") || isKeyword2(token, "digraph");
1432
+ }
1433
+ function isIdentifierLike2(token) {
1434
+ return token.type === "identifier" || token.type === "string" || token.type === "html";
1435
+ }
1436
+ function parseIdentifierValue2(token) {
1437
+ return token.value;
1438
+ }
1439
+ function parseAttributeValue2(token) {
1440
+ if (token.type === "string" || token.type === "html") {
1441
+ return token.value;
1442
+ }
1443
+ if (token.type === "identifier") {
1444
+ const numeric = Number(token.value);
1445
+ if (!Number.isNaN(numeric)) {
1446
+ return numeric;
1447
+ }
1448
+ return token.value;
1449
+ }
1450
+ throw new Error("Invalid attribute value in DOT input.");
1451
+ }
1452
+ function deriveEdgeId2(attributes, sourceId, targetId, counter) {
1453
+ const candidate = attributes.id ?? attributes.Id ?? attributes.ID;
1454
+ if (typeof candidate === "string" || typeof candidate === "number") {
1455
+ return String(candidate);
1456
+ }
1457
+ return `${String(sourceId)}-${String(targetId)}-${counter}`;
1458
+ }
1459
+ function deriveDirectedFlag2(attributes, defaultDirected) {
1460
+ const candidate = attributes.directed;
1461
+ if (typeof candidate === "boolean") {
1462
+ return candidate;
1463
+ }
1464
+ const directionAttribute = attributes.dir;
1465
+ if (typeof directionAttribute === "string") {
1466
+ const normalized = directionAttribute.toLowerCase();
1467
+ if (normalized === "none") {
1468
+ return false;
1469
+ }
1470
+ return true;
1471
+ }
1472
+ return defaultDirected;
1473
+ }
1474
+
1475
+ // dist/dot-loader.js
1476
+ var DOTLoaderWithParser2 = {
1477
+ ...DOTLoader2,
1478
+ parse: async (data) => parseDOT2(new TextDecoder().decode(data)),
1479
+ parseSync: (data) => parseDOT2(new TextDecoder().decode(data)),
1480
+ parseTextSync: parseDOT2
1481
+ };
1482
+ //# sourceMappingURL=dot-loader.cjs.map