@bcts/dcbor-pattern 1.0.0-alpha.11

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 (73) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +14 -0
  3. package/dist/index.cjs +6561 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +2732 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +2732 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +6562 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +6244 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +85 -0
  14. package/src/error.ts +333 -0
  15. package/src/format.ts +299 -0
  16. package/src/index.ts +20 -0
  17. package/src/interval.ts +230 -0
  18. package/src/parse/index.ts +95 -0
  19. package/src/parse/meta/and-parser.ts +47 -0
  20. package/src/parse/meta/capture-parser.ts +56 -0
  21. package/src/parse/meta/index.ts +13 -0
  22. package/src/parse/meta/not-parser.ts +28 -0
  23. package/src/parse/meta/or-parser.ts +47 -0
  24. package/src/parse/meta/primary-parser.ts +420 -0
  25. package/src/parse/meta/repeat-parser.ts +133 -0
  26. package/src/parse/meta/search-parser.ts +56 -0
  27. package/src/parse/parse-registry.ts +31 -0
  28. package/src/parse/structure/array-parser.ts +210 -0
  29. package/src/parse/structure/index.ts +9 -0
  30. package/src/parse/structure/map-parser.ts +128 -0
  31. package/src/parse/structure/tagged-parser.ts +269 -0
  32. package/src/parse/token.ts +997 -0
  33. package/src/parse/value/bool-parser.ts +33 -0
  34. package/src/parse/value/bytestring-parser.ts +42 -0
  35. package/src/parse/value/date-parser.ts +24 -0
  36. package/src/parse/value/digest-parser.ts +24 -0
  37. package/src/parse/value/index.ts +14 -0
  38. package/src/parse/value/known-value-parser.ts +24 -0
  39. package/src/parse/value/null-parser.ts +19 -0
  40. package/src/parse/value/number-parser.ts +19 -0
  41. package/src/parse/value/text-parser.ts +43 -0
  42. package/src/pattern/index.ts +740 -0
  43. package/src/pattern/match-registry.ts +137 -0
  44. package/src/pattern/matcher.ts +388 -0
  45. package/src/pattern/meta/and-pattern.ts +56 -0
  46. package/src/pattern/meta/any-pattern.ts +43 -0
  47. package/src/pattern/meta/capture-pattern.ts +57 -0
  48. package/src/pattern/meta/index.ts +168 -0
  49. package/src/pattern/meta/not-pattern.ts +70 -0
  50. package/src/pattern/meta/or-pattern.ts +56 -0
  51. package/src/pattern/meta/repeat-pattern.ts +117 -0
  52. package/src/pattern/meta/search-pattern.ts +298 -0
  53. package/src/pattern/meta/sequence-pattern.ts +72 -0
  54. package/src/pattern/structure/array-pattern/assigner.ts +95 -0
  55. package/src/pattern/structure/array-pattern/backtrack.ts +240 -0
  56. package/src/pattern/structure/array-pattern/helpers.ts +140 -0
  57. package/src/pattern/structure/array-pattern/index.ts +502 -0
  58. package/src/pattern/structure/index.ts +122 -0
  59. package/src/pattern/structure/map-pattern.ts +255 -0
  60. package/src/pattern/structure/tagged-pattern.ts +190 -0
  61. package/src/pattern/value/bool-pattern.ts +67 -0
  62. package/src/pattern/value/bytes-utils.ts +48 -0
  63. package/src/pattern/value/bytestring-pattern.ts +111 -0
  64. package/src/pattern/value/date-pattern.ts +162 -0
  65. package/src/pattern/value/digest-pattern.ts +136 -0
  66. package/src/pattern/value/index.ts +168 -0
  67. package/src/pattern/value/known-value-pattern.ts +123 -0
  68. package/src/pattern/value/null-pattern.ts +46 -0
  69. package/src/pattern/value/number-pattern.ts +181 -0
  70. package/src/pattern/value/text-pattern.ts +82 -0
  71. package/src/pattern/vm.ts +619 -0
  72. package/src/quantifier.ts +185 -0
  73. package/src/reluctance.ts +65 -0
@@ -0,0 +1,619 @@
1
+ /**
2
+ * Tiny Thompson-style VM for walking dCBOR trees.
3
+ *
4
+ * The VM runs byte-code produced by Pattern compile methods.
5
+ *
6
+ * @module pattern/vm
7
+ */
8
+
9
+ import type { Cbor } from "@bcts/dcbor";
10
+ import {
11
+ isArray,
12
+ isMap,
13
+ isTagged,
14
+ arrayLength,
15
+ arrayItem,
16
+ mapKeys,
17
+ mapValue,
18
+ tagContent,
19
+ } from "@bcts/dcbor";
20
+ import type { Path } from "../format";
21
+ import type { Pattern } from "./index";
22
+ import type { Quantifier } from "../quantifier";
23
+ import { Reluctance } from "../reluctance";
24
+ import { getPatternPaths, getPatternPathsWithCapturesDirect } from "./match-registry";
25
+ import {
26
+ searchPatternPathsWithCaptures,
27
+ searchPattern as createSearchPattern,
28
+ } from "./meta/search-pattern";
29
+
30
+ /**
31
+ * Navigation axis for traversing dCBOR tree structures.
32
+ */
33
+ export type Axis = "ArrayElement" | "MapKey" | "MapValue" | "TaggedContent";
34
+
35
+ /**
36
+ * Return child CBOR values reachable from `cbor` via the given axis.
37
+ */
38
+ export const axisChildren = (axis: Axis, cbor: Cbor): Cbor[] => {
39
+ switch (axis) {
40
+ case "ArrayElement": {
41
+ if (!isArray(cbor)) return [];
42
+ const len = arrayLength(cbor);
43
+ if (len === undefined) return [];
44
+ const children: Cbor[] = [];
45
+ for (let i = 0; i < len; i++) {
46
+ const item = arrayItem(cbor, i);
47
+ if (item !== undefined) {
48
+ children.push(item);
49
+ }
50
+ }
51
+ return children;
52
+ }
53
+ case "MapKey": {
54
+ if (!isMap(cbor)) return [];
55
+ const keys = mapKeys(cbor);
56
+ if (keys === undefined || keys === null) return [];
57
+ return keys;
58
+ }
59
+ case "MapValue": {
60
+ if (!isMap(cbor)) return [];
61
+ const keys = mapKeys(cbor);
62
+ if (keys === undefined || keys === null) return [];
63
+ const values: Cbor[] = [];
64
+ for (const key of keys) {
65
+ const value = mapValue(cbor, key);
66
+ if (value !== undefined && value !== null) {
67
+ values.push(value as Cbor);
68
+ }
69
+ }
70
+ return values;
71
+ }
72
+ case "TaggedContent": {
73
+ if (!isTagged(cbor)) return [];
74
+ const content = tagContent(cbor);
75
+ if (content === undefined) return [];
76
+ return [content];
77
+ }
78
+ }
79
+ };
80
+
81
+ /**
82
+ * Bytecode instructions for the pattern VM.
83
+ */
84
+ export type Instr =
85
+ | { type: "MatchPredicate"; literalIndex: number }
86
+ | { type: "MatchStructure"; literalIndex: number }
87
+ | { type: "Split"; a: number; b: number }
88
+ | { type: "Jump"; address: number }
89
+ | { type: "PushAxis"; axis: Axis }
90
+ | { type: "Pop" }
91
+ | { type: "Save" }
92
+ | { type: "Accept" }
93
+ | {
94
+ type: "Search";
95
+ patternIndex: number;
96
+ captureMap: [string, number][];
97
+ }
98
+ | { type: "ExtendSequence" }
99
+ | { type: "CombineSequence" }
100
+ | { type: "NotMatch"; patternIndex: number }
101
+ | { type: "Repeat"; patternIndex: number; quantifier: Quantifier }
102
+ | { type: "CaptureStart"; captureIndex: number }
103
+ | { type: "CaptureEnd"; captureIndex: number };
104
+
105
+ /**
106
+ * A compiled pattern program.
107
+ */
108
+ export interface Program {
109
+ code: Instr[];
110
+ literals: Pattern[];
111
+ captureNames: string[];
112
+ }
113
+
114
+ /**
115
+ * Internal back-tracking state.
116
+ */
117
+ interface Thread {
118
+ pc: number;
119
+ cbor: Cbor;
120
+ path: Path;
121
+ savedPaths: Path[];
122
+ captures: Path[][];
123
+ captureStack: number[][];
124
+ }
125
+
126
+ /**
127
+ * Compares two CBOR values for equality by their serialized form.
128
+ */
129
+ const cborEquals = (a: Cbor, b: Cbor): boolean => {
130
+ // Simple reference equality check first
131
+ if (a === b) return true;
132
+
133
+ // Compare by JSON representation (simplified)
134
+ try {
135
+ return JSON.stringify(a) === JSON.stringify(b);
136
+ } catch {
137
+ return false;
138
+ }
139
+ };
140
+
141
+ /**
142
+ * Hash a path for deduplication.
143
+ * Uses CBOR diagnostic notation for proper serialization.
144
+ */
145
+ const pathHash = (path: Path): string => {
146
+ // Use toDiagnostic for proper CBOR serialization
147
+ return path
148
+ .map((item) => (typeof item.toDiagnostic === "function" ? item.toDiagnostic() : String(item)))
149
+ .join("|");
150
+ };
151
+
152
+ /**
153
+ * Match atomic patterns without recursion into the VM.
154
+ *
155
+ * This function handles only the patterns that are safe to use in
156
+ * MatchPredicate instructions.
157
+ */
158
+ export const atomicPaths = (pattern: Pattern, cbor: Cbor): Path[] => {
159
+ switch (pattern.kind) {
160
+ case "Value":
161
+ case "Structure":
162
+ return getPatternPaths(pattern, cbor);
163
+ case "Meta":
164
+ if (pattern.pattern.type === "Any") {
165
+ return [[cbor]];
166
+ }
167
+ throw new Error(`Non-atomic meta pattern used in MatchPredicate: ${pattern.pattern.type}`);
168
+ }
169
+ };
170
+
171
+ /**
172
+ * Compute repeat paths based on pattern, quantifier, and starting state.
173
+ */
174
+ const repeatPaths = (
175
+ pattern: Pattern,
176
+ cbor: Cbor,
177
+ path: Path,
178
+ quantifier: Quantifier,
179
+ ): { cbor: Cbor; path: Path }[] => {
180
+ // Build states for all possible repetition counts
181
+ const states: { cbor: Cbor; path: Path }[][] = [[{ cbor, path: [...path] }]];
182
+ const bound = quantifier.max() ?? Number.MAX_SAFE_INTEGER;
183
+
184
+ // Try matching the pattern repeatedly
185
+ for (let rep = 0; rep < bound; rep++) {
186
+ const next: { cbor: Cbor; path: Path }[] = [];
187
+ const lastState = states[states.length - 1];
188
+
189
+ for (const state of lastState) {
190
+ const subPaths = getPatternPaths(pattern, state.cbor);
191
+
192
+ for (const subPath of subPaths) {
193
+ const last = subPath[subPath.length - 1];
194
+ if (last === undefined) continue;
195
+
196
+ // Avoid infinite loops
197
+ if (cborEquals(last, state.cbor)) continue;
198
+
199
+ const combined = [...state.path];
200
+ // Skip first element if it's the same as current cbor
201
+ const firstElement = subPath[0];
202
+ const startIdx = firstElement !== undefined && cborEquals(firstElement, state.cbor) ? 1 : 0;
203
+ for (let i = startIdx; i < subPath.length; i++) {
204
+ combined.push(subPath[i]);
205
+ }
206
+ next.push({ cbor: last, path: combined });
207
+ }
208
+ }
209
+
210
+ if (next.length === 0) break;
211
+ states.push(next);
212
+ }
213
+
214
+ // Zero repetition case
215
+ const hasZeroRep = quantifier.min() === 0;
216
+ const zeroRepResult = hasZeroRep ? [{ cbor, path: [...path] }] : [];
217
+
218
+ // Calculate maximum allowed repetitions
219
+ const maxPossible = states.length - 1;
220
+ const maxAllowed = Math.min(bound, maxPossible);
221
+
222
+ // Check if we can satisfy the minimum repetition requirement
223
+ if (maxAllowed < quantifier.min() && quantifier.min() > 0) {
224
+ return [];
225
+ }
226
+
227
+ // Calculate the range of repetition counts
228
+ const minCount = quantifier.min() === 0 ? 1 : quantifier.min();
229
+ const maxCount = maxAllowed < minCount ? -1 : maxAllowed;
230
+
231
+ if (maxCount < minCount) {
232
+ return zeroRepResult;
233
+ }
234
+
235
+ // Generate list of counts based on reluctance
236
+ let counts: number[];
237
+ const reluctance = quantifier.reluctance();
238
+
239
+ if (reluctance === Reluctance.Greedy) {
240
+ counts = [];
241
+ for (let i = maxCount; i >= minCount; i--) {
242
+ counts.push(i);
243
+ }
244
+ } else if (reluctance === Reluctance.Lazy) {
245
+ counts = [];
246
+ for (let i = minCount; i <= maxCount; i++) {
247
+ counts.push(i);
248
+ }
249
+ } else {
250
+ // Possessive
251
+ counts = maxCount >= minCount ? [maxCount] : [];
252
+ }
253
+
254
+ // Collect results
255
+ const out: { cbor: Cbor; path: Path }[] = [];
256
+
257
+ if (reluctance === Reluctance.Greedy) {
258
+ for (const c of counts) {
259
+ const list = states[c];
260
+ if (list !== undefined) {
261
+ out.push(...list);
262
+ }
263
+ }
264
+ if (hasZeroRep && out.length === 0) {
265
+ out.push({ cbor, path: [...path] });
266
+ }
267
+ } else {
268
+ if (hasZeroRep) {
269
+ out.push({ cbor, path: [...path] });
270
+ }
271
+ for (const c of counts) {
272
+ const list = states[c];
273
+ if (list !== undefined) {
274
+ out.push(...list);
275
+ }
276
+ }
277
+ }
278
+
279
+ return out;
280
+ };
281
+
282
+ /**
283
+ * Execute a single thread until it halts.
284
+ */
285
+ const runThread = (
286
+ prog: Program,
287
+ start: Thread,
288
+ out: { path: Path; captures: Path[][] }[],
289
+ ): boolean => {
290
+ let produced = false;
291
+ const stack: Thread[] = [start];
292
+
293
+ while (stack.length > 0) {
294
+ const th = stack.pop();
295
+ if (th === undefined) break;
296
+
297
+ threadLoop: while (true) {
298
+ const instr = prog.code[th.pc];
299
+
300
+ switch (instr.type) {
301
+ case "MatchPredicate": {
302
+ const paths = atomicPaths(prog.literals[instr.literalIndex], th.cbor);
303
+ if (paths.length === 0) {
304
+ break threadLoop;
305
+ }
306
+ th.pc += 1;
307
+ break;
308
+ }
309
+
310
+ case "MatchStructure": {
311
+ const pattern = prog.literals[instr.literalIndex];
312
+ if (pattern.kind !== "Structure") {
313
+ throw new Error("MatchStructure used with non-structure pattern");
314
+ }
315
+
316
+ const result = getPatternPathsWithCapturesDirect(pattern, th.cbor);
317
+ if (result.paths.length === 0) {
318
+ break threadLoop;
319
+ }
320
+
321
+ // Merge structure captures into thread captures
322
+ for (let i = 0; i < prog.captureNames.length; i++) {
323
+ const name = prog.captureNames[i];
324
+ const capturedPaths = result.captures.get(name);
325
+ if (capturedPaths !== undefined) {
326
+ while (th.captures.length <= i) {
327
+ th.captures.push([]);
328
+ }
329
+ th.captures[i].push(...capturedPaths);
330
+ }
331
+ }
332
+
333
+ // Handle structure paths
334
+ if (result.paths.length === 1 && result.paths[0].length === 1) {
335
+ th.pc += 1;
336
+ } else {
337
+ for (const structurePath of result.paths) {
338
+ const target = structurePath[structurePath.length - 1];
339
+ if (target !== undefined) {
340
+ const newThread: Thread = {
341
+ pc: th.pc + 1,
342
+ cbor: target,
343
+ path: [...th.path, ...structurePath.slice(1)],
344
+ savedPaths: [...th.savedPaths],
345
+ captures: th.captures.map((c) => [...c]),
346
+ captureStack: th.captureStack.map((s) => [...s]),
347
+ };
348
+ stack.push(newThread);
349
+ }
350
+ }
351
+ break threadLoop;
352
+ }
353
+ break;
354
+ }
355
+
356
+ case "Split": {
357
+ const th2: Thread = {
358
+ pc: instr.b,
359
+ cbor: th.cbor,
360
+ path: [...th.path],
361
+ savedPaths: [...th.savedPaths],
362
+ captures: th.captures.map((c) => [...c]),
363
+ captureStack: th.captureStack.map((s) => [...s]),
364
+ };
365
+ stack.push(th2);
366
+ th.pc = instr.a;
367
+ break;
368
+ }
369
+
370
+ case "Jump": {
371
+ th.pc = instr.address;
372
+ break;
373
+ }
374
+
375
+ case "PushAxis": {
376
+ const children = axisChildren(instr.axis, th.cbor);
377
+ for (const child of children) {
378
+ const newThread: Thread = {
379
+ pc: th.pc + 1,
380
+ cbor: child,
381
+ path: [...th.path, child],
382
+ savedPaths: [...th.savedPaths],
383
+ captures: th.captures.map((c) => [...c]),
384
+ captureStack: th.captureStack.map((s) => [...s]),
385
+ };
386
+ stack.push(newThread);
387
+ }
388
+ break threadLoop;
389
+ }
390
+
391
+ case "Pop": {
392
+ if (th.path.length === 0) {
393
+ break threadLoop;
394
+ }
395
+ th.path.pop();
396
+ const parent = th.path[th.path.length - 1];
397
+ if (parent !== undefined) {
398
+ th.cbor = parent;
399
+ }
400
+ th.pc += 1;
401
+ break;
402
+ }
403
+
404
+ case "Save": {
405
+ out.push({ path: [...th.path], captures: th.captures.map((c) => [...c]) });
406
+ produced = true;
407
+ th.pc += 1;
408
+ break;
409
+ }
410
+
411
+ case "Accept": {
412
+ out.push({ path: [...th.path], captures: th.captures.map((c) => [...c]) });
413
+ produced = true;
414
+ break threadLoop;
415
+ }
416
+
417
+ case "Search": {
418
+ // Create a SearchPattern wrapper for the inner pattern
419
+ const innerPattern = prog.literals[instr.patternIndex];
420
+ const searchPat = createSearchPattern(innerPattern);
421
+
422
+ // Use recursive search with captures
423
+ const result = searchPatternPathsWithCaptures(searchPat, th.cbor);
424
+
425
+ for (const searchPath of result.paths) {
426
+ const newThread: Thread = {
427
+ pc: th.pc + 1,
428
+ cbor: th.cbor,
429
+ path: searchPath,
430
+ savedPaths: [...th.savedPaths],
431
+ captures: th.captures.map((c) => [...c]),
432
+ captureStack: th.captureStack.map((s) => [...s]),
433
+ };
434
+
435
+ // Apply capture mappings
436
+ for (const [name, captureIdx] of instr.captureMap) {
437
+ if (captureIdx < newThread.captures.length) {
438
+ const capturePaths = result.captures.get(name);
439
+ if (capturePaths !== undefined) {
440
+ for (const capturePath of capturePaths) {
441
+ newThread.captures[captureIdx].push(capturePath);
442
+ }
443
+ }
444
+ }
445
+ }
446
+
447
+ stack.push(newThread);
448
+ }
449
+ break threadLoop;
450
+ }
451
+
452
+ case "ExtendSequence": {
453
+ th.savedPaths.push([...th.path]);
454
+ const last = th.path[th.path.length - 1];
455
+ if (last !== undefined) {
456
+ th.path = [last];
457
+ th.cbor = last;
458
+ }
459
+ th.pc += 1;
460
+ break;
461
+ }
462
+
463
+ case "CombineSequence": {
464
+ const saved = th.savedPaths.pop();
465
+ if (saved !== undefined) {
466
+ const combined = [...saved];
467
+ if (th.path.length > 1) {
468
+ combined.push(...th.path.slice(1));
469
+ }
470
+ th.path = combined;
471
+ }
472
+ th.pc += 1;
473
+ break;
474
+ }
475
+
476
+ case "NotMatch": {
477
+ const paths = getPatternPaths(prog.literals[instr.patternIndex], th.cbor);
478
+ if (paths.length > 0) {
479
+ break threadLoop; // Pattern matched, so NOT fails
480
+ }
481
+ th.pc += 1;
482
+ break;
483
+ }
484
+
485
+ case "Repeat": {
486
+ const results = repeatPaths(
487
+ prog.literals[instr.patternIndex],
488
+ th.cbor,
489
+ th.path,
490
+ instr.quantifier,
491
+ );
492
+
493
+ for (const result of results) {
494
+ const newThread: Thread = {
495
+ pc: th.pc + 1,
496
+ cbor: result.cbor,
497
+ path: result.path,
498
+ savedPaths: [...th.savedPaths],
499
+ captures: th.captures.map((c) => [...c]),
500
+ captureStack: th.captureStack.map((s) => [...s]),
501
+ };
502
+ stack.push(newThread);
503
+ }
504
+ break threadLoop;
505
+ }
506
+
507
+ case "CaptureStart": {
508
+ const idx = instr.captureIndex;
509
+ while (th.captures.length <= idx) {
510
+ th.captures.push([]);
511
+ }
512
+ while (th.captureStack.length <= idx) {
513
+ th.captureStack.push([]);
514
+ }
515
+ th.captureStack[idx].push(th.path.length);
516
+ th.pc += 1;
517
+ break;
518
+ }
519
+
520
+ case "CaptureEnd": {
521
+ const idx = instr.captureIndex;
522
+ const stack = th.captureStack[idx];
523
+ if (stack !== undefined && stack.length > 0) {
524
+ stack.pop();
525
+ const capturedPath = [...th.path];
526
+ const captureArray = th.captures[idx];
527
+ if (captureArray !== undefined) {
528
+ captureArray.push(capturedPath);
529
+ }
530
+ }
531
+ th.pc += 1;
532
+ break;
533
+ }
534
+ }
535
+ }
536
+ }
537
+
538
+ return produced;
539
+ };
540
+
541
+ /**
542
+ * Execute a program against a dCBOR value, returning all matching paths and captures.
543
+ */
544
+ export const run = (
545
+ prog: Program,
546
+ root: Cbor,
547
+ ): { paths: Path[]; captures: Map<string, Path[]> } => {
548
+ // Initialize captures array with one empty array per capture name
549
+ const initialCaptures: Path[][] = prog.captureNames.map(() => []);
550
+
551
+ const start: Thread = {
552
+ pc: 0,
553
+ cbor: root,
554
+ path: [root],
555
+ savedPaths: [],
556
+ captures: initialCaptures,
557
+ captureStack: [],
558
+ };
559
+
560
+ const results: { path: Path; captures: Path[][] }[] = [];
561
+ runThread(prog, start, results);
562
+
563
+ // Deduplicate paths while preserving original order
564
+ const seenPaths = new Set<string>();
565
+ const paths: Path[] = [];
566
+
567
+ for (const result of results) {
568
+ const hash = pathHash(result.path);
569
+ if (!seenPaths.has(hash)) {
570
+ seenPaths.add(hash);
571
+ paths.push(result.path);
572
+ }
573
+ }
574
+
575
+ // Build capture map from capture names and results
576
+ const captures = new Map<string, Path[]>();
577
+
578
+ for (let i = 0; i < prog.captureNames.length; i++) {
579
+ const name = prog.captureNames[i];
580
+ const capturedPaths: Path[] = [];
581
+
582
+ for (const result of results) {
583
+ const captureGroup = result.captures[i];
584
+ if (captureGroup !== undefined) {
585
+ capturedPaths.push(...captureGroup);
586
+ }
587
+ }
588
+
589
+ // Deduplicate captured paths
590
+ if (capturedPaths.length > 0) {
591
+ const seenCapturePaths = new Set<string>();
592
+ const deduplicated: Path[] = [];
593
+
594
+ for (const path of capturedPaths) {
595
+ const hash = pathHash(path);
596
+ if (!seenCapturePaths.has(hash)) {
597
+ seenCapturePaths.add(hash);
598
+ deduplicated.push(path);
599
+ }
600
+ }
601
+
602
+ captures.set(name, deduplicated);
603
+ }
604
+ }
605
+
606
+ return { paths, captures };
607
+ };
608
+
609
+ /**
610
+ * VM for executing pattern programs against dCBOR values.
611
+ */
612
+ export class Vm {
613
+ /**
614
+ * Execute a program against a dCBOR value.
615
+ */
616
+ static run(prog: Program, root: Cbor): { paths: Path[]; captures: Map<string, Path[]> } {
617
+ return run(prog, root);
618
+ }
619
+ }