@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.
- package/LICENSE +48 -0
- package/README.md +14 -0
- package/dist/index.cjs +6561 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2732 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +2732 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +6562 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +6244 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +85 -0
- package/src/error.ts +333 -0
- package/src/format.ts +299 -0
- package/src/index.ts +20 -0
- package/src/interval.ts +230 -0
- package/src/parse/index.ts +95 -0
- package/src/parse/meta/and-parser.ts +47 -0
- package/src/parse/meta/capture-parser.ts +56 -0
- package/src/parse/meta/index.ts +13 -0
- package/src/parse/meta/not-parser.ts +28 -0
- package/src/parse/meta/or-parser.ts +47 -0
- package/src/parse/meta/primary-parser.ts +420 -0
- package/src/parse/meta/repeat-parser.ts +133 -0
- package/src/parse/meta/search-parser.ts +56 -0
- package/src/parse/parse-registry.ts +31 -0
- package/src/parse/structure/array-parser.ts +210 -0
- package/src/parse/structure/index.ts +9 -0
- package/src/parse/structure/map-parser.ts +128 -0
- package/src/parse/structure/tagged-parser.ts +269 -0
- package/src/parse/token.ts +997 -0
- package/src/parse/value/bool-parser.ts +33 -0
- package/src/parse/value/bytestring-parser.ts +42 -0
- package/src/parse/value/date-parser.ts +24 -0
- package/src/parse/value/digest-parser.ts +24 -0
- package/src/parse/value/index.ts +14 -0
- package/src/parse/value/known-value-parser.ts +24 -0
- package/src/parse/value/null-parser.ts +19 -0
- package/src/parse/value/number-parser.ts +19 -0
- package/src/parse/value/text-parser.ts +43 -0
- package/src/pattern/index.ts +740 -0
- package/src/pattern/match-registry.ts +137 -0
- package/src/pattern/matcher.ts +388 -0
- package/src/pattern/meta/and-pattern.ts +56 -0
- package/src/pattern/meta/any-pattern.ts +43 -0
- package/src/pattern/meta/capture-pattern.ts +57 -0
- package/src/pattern/meta/index.ts +168 -0
- package/src/pattern/meta/not-pattern.ts +70 -0
- package/src/pattern/meta/or-pattern.ts +56 -0
- package/src/pattern/meta/repeat-pattern.ts +117 -0
- package/src/pattern/meta/search-pattern.ts +298 -0
- package/src/pattern/meta/sequence-pattern.ts +72 -0
- package/src/pattern/structure/array-pattern/assigner.ts +95 -0
- package/src/pattern/structure/array-pattern/backtrack.ts +240 -0
- package/src/pattern/structure/array-pattern/helpers.ts +140 -0
- package/src/pattern/structure/array-pattern/index.ts +502 -0
- package/src/pattern/structure/index.ts +122 -0
- package/src/pattern/structure/map-pattern.ts +255 -0
- package/src/pattern/structure/tagged-pattern.ts +190 -0
- package/src/pattern/value/bool-pattern.ts +67 -0
- package/src/pattern/value/bytes-utils.ts +48 -0
- package/src/pattern/value/bytestring-pattern.ts +111 -0
- package/src/pattern/value/date-pattern.ts +162 -0
- package/src/pattern/value/digest-pattern.ts +136 -0
- package/src/pattern/value/index.ts +168 -0
- package/src/pattern/value/known-value-pattern.ts +123 -0
- package/src/pattern/value/null-pattern.ts +46 -0
- package/src/pattern/value/number-pattern.ts +181 -0
- package/src/pattern/value/text-pattern.ts +82 -0
- package/src/pattern/vm.ts +619 -0
- package/src/quantifier.ts +185 -0
- package/src/reluctance.ts +65 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array pattern for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/structure/array-pattern
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
8
|
+
import { isArray, arrayLength, arrayItem, cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../../format";
|
|
10
|
+
import type { Pattern } from "../../index";
|
|
11
|
+
import type { SequencePattern } from "../../meta/sequence-pattern";
|
|
12
|
+
import type { RepeatPattern } from "../../meta/repeat-pattern";
|
|
13
|
+
import { Interval } from "../../../interval";
|
|
14
|
+
import { matchPattern } from "../../match-registry";
|
|
15
|
+
import {
|
|
16
|
+
hasRepeatPatternsInSlice,
|
|
17
|
+
extractCaptureWithRepeat,
|
|
18
|
+
isRepeatPattern,
|
|
19
|
+
buildSimpleArrayContextPath,
|
|
20
|
+
} from "./helpers";
|
|
21
|
+
import { SequenceAssigner } from "./assigner";
|
|
22
|
+
|
|
23
|
+
// Re-export helper modules
|
|
24
|
+
export * from "./helpers";
|
|
25
|
+
export * from "./backtrack";
|
|
26
|
+
export * from "./assigner";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pattern for matching CBOR array structures.
|
|
30
|
+
*/
|
|
31
|
+
export type ArrayPattern =
|
|
32
|
+
| { readonly variant: "Any" }
|
|
33
|
+
| { readonly variant: "Elements"; readonly pattern: Pattern }
|
|
34
|
+
| { readonly variant: "Length"; readonly length: Interval };
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates an ArrayPattern that matches any array.
|
|
38
|
+
*/
|
|
39
|
+
export const arrayPatternAny = (): ArrayPattern => ({ variant: "Any" });
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates an ArrayPattern that matches arrays with elements matching the pattern.
|
|
43
|
+
*/
|
|
44
|
+
export const arrayPatternWithElements = (pattern: Pattern): ArrayPattern => ({
|
|
45
|
+
variant: "Elements",
|
|
46
|
+
pattern,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates an ArrayPattern that matches arrays with a specific length.
|
|
51
|
+
*/
|
|
52
|
+
export const arrayPatternWithLength = (length: number): ArrayPattern => ({
|
|
53
|
+
variant: "Length",
|
|
54
|
+
length: Interval.exactly(length),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates an ArrayPattern that matches arrays with length in a range.
|
|
59
|
+
*/
|
|
60
|
+
export const arrayPatternWithLengthRange = (min: number, max?: number): ArrayPattern => ({
|
|
61
|
+
variant: "Length",
|
|
62
|
+
length: max !== undefined ? Interval.from(min, max) : Interval.atLeast(min),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates an ArrayPattern that matches arrays with length in an interval.
|
|
67
|
+
*/
|
|
68
|
+
export const arrayPatternWithLengthInterval = (interval: Interval): ArrayPattern => ({
|
|
69
|
+
variant: "Length",
|
|
70
|
+
length: interval,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Gets array elements as Cbor array.
|
|
75
|
+
*/
|
|
76
|
+
const getArrayElements = (haystack: Cbor): Cbor[] | undefined => {
|
|
77
|
+
if (!isArray(haystack)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const len = arrayLength(haystack);
|
|
81
|
+
if (len === undefined) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const elements: Cbor[] = [];
|
|
85
|
+
for (let i = 0; i < len; i++) {
|
|
86
|
+
const item = arrayItem(haystack, i);
|
|
87
|
+
if (item === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
elements.push(item);
|
|
91
|
+
}
|
|
92
|
+
return elements;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Match a single repeat pattern against array elements.
|
|
97
|
+
*/
|
|
98
|
+
const matchRepeatPatternAgainstArray = (repeatPattern: RepeatPattern, arr: Cbor[]): boolean => {
|
|
99
|
+
const quantifier = repeatPattern.quantifier;
|
|
100
|
+
const minCount = quantifier.min();
|
|
101
|
+
const maxCount = quantifier.max() ?? arr.length;
|
|
102
|
+
|
|
103
|
+
// Check if the array length is within the valid range for this repeat
|
|
104
|
+
if (arr.length < minCount || arr.length > maxCount) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if all elements match the repeated pattern
|
|
109
|
+
return arr.every((element) => matchPattern(repeatPattern.pattern, element));
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Match a sequence of patterns against array elements using backtracking.
|
|
114
|
+
*/
|
|
115
|
+
const matchSequencePatternsAgainstArray = (seqPattern: SequencePattern, arr: Cbor[]): boolean => {
|
|
116
|
+
const patterns = seqPattern.patterns;
|
|
117
|
+
const assigner = new SequenceAssigner(patterns, arr, matchPattern);
|
|
118
|
+
return assigner.canMatch();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if a sequence pattern can match against array elements.
|
|
123
|
+
*/
|
|
124
|
+
const canMatchSequenceAgainstArray = (pattern: Pattern, arr: Cbor[]): boolean => {
|
|
125
|
+
if (pattern.kind === "Meta") {
|
|
126
|
+
if (pattern.pattern.type === "Sequence") {
|
|
127
|
+
return matchSequencePatternsAgainstArray(pattern.pattern.pattern, arr);
|
|
128
|
+
}
|
|
129
|
+
if (pattern.pattern.type === "Repeat") {
|
|
130
|
+
return matchRepeatPatternAgainstArray(pattern.pattern.pattern, arr);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// For non-sequence patterns, fall back to simple matching
|
|
134
|
+
const arrayCbor = cbor(arr);
|
|
135
|
+
return matchPattern(pattern, arrayCbor);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Match a complex sequence against array elements.
|
|
140
|
+
*/
|
|
141
|
+
const matchComplexSequence = (haystack: Cbor, pattern: Pattern): Path[] => {
|
|
142
|
+
const arr = getArrayElements(haystack);
|
|
143
|
+
if (arr === undefined) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const canMatch = canMatchSequenceAgainstArray(pattern, arr);
|
|
148
|
+
if (canMatch) {
|
|
149
|
+
return [[haystack]];
|
|
150
|
+
}
|
|
151
|
+
return [];
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Find which array elements are assigned to which sequence patterns.
|
|
156
|
+
*/
|
|
157
|
+
const findSequenceElementAssignments = (
|
|
158
|
+
seqPattern: SequencePattern,
|
|
159
|
+
arr: Cbor[],
|
|
160
|
+
): [number, number][] | undefined => {
|
|
161
|
+
const patterns = seqPattern.patterns;
|
|
162
|
+
const assigner = new SequenceAssigner(patterns, arr, matchPattern);
|
|
163
|
+
return assigner.findAssignments();
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Handle sequence patterns with captures by manually matching elements
|
|
168
|
+
* and collecting captures with proper array context.
|
|
169
|
+
*/
|
|
170
|
+
const handleSequenceCaptures = (
|
|
171
|
+
seqPattern: SequencePattern,
|
|
172
|
+
arrayCbor: Cbor,
|
|
173
|
+
arr: Cbor[],
|
|
174
|
+
): [Path[], Map<string, Path[]>] => {
|
|
175
|
+
const assignments = findSequenceElementAssignments(seqPattern, arr);
|
|
176
|
+
if (assignments === undefined) {
|
|
177
|
+
return [[], new Map<string, Path[]>()];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const allCaptures = new Map<string, Path[]>();
|
|
181
|
+
|
|
182
|
+
// Process each pattern and its assigned elements
|
|
183
|
+
for (let patternIdx = 0; patternIdx < seqPattern.patterns.length; patternIdx++) {
|
|
184
|
+
const pattern = seqPattern.patterns[patternIdx];
|
|
185
|
+
|
|
186
|
+
// Check if this is a capture pattern containing a repeat pattern
|
|
187
|
+
if (pattern.kind === "Meta" && pattern.pattern.type === "Capture") {
|
|
188
|
+
const capturePattern = pattern.pattern.pattern;
|
|
189
|
+
|
|
190
|
+
// Check if the capture contains a repeat pattern
|
|
191
|
+
if (extractCaptureWithRepeat(pattern) !== undefined) {
|
|
192
|
+
// This is a capture pattern with a repeat (like @rest((*)*)
|
|
193
|
+
// We need to capture the sub-array of matched elements
|
|
194
|
+
const capturedElements: Cbor[] = assignments
|
|
195
|
+
.filter(([pIdx, _]) => pIdx === patternIdx)
|
|
196
|
+
.map(([_, eIdx]) => arr[eIdx]);
|
|
197
|
+
|
|
198
|
+
// Create a sub-array from the captured elements
|
|
199
|
+
const subArray = cbor(capturedElements);
|
|
200
|
+
|
|
201
|
+
// For capture patterns, we directly capture the sub-array with the capture name
|
|
202
|
+
const captureName = capturePattern.name;
|
|
203
|
+
const arrayContextPath = buildSimpleArrayContextPath(arrayCbor, subArray);
|
|
204
|
+
|
|
205
|
+
const existing = allCaptures.get(captureName) ?? [];
|
|
206
|
+
existing.push(arrayContextPath);
|
|
207
|
+
allCaptures.set(captureName, existing);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check if this is a direct repeat pattern that might capture multiple elements
|
|
213
|
+
if (isRepeatPattern(pattern) && pattern.kind === "Meta" && pattern.pattern.type === "Repeat") {
|
|
214
|
+
const repeatPattern = pattern.pattern.pattern;
|
|
215
|
+
|
|
216
|
+
// For repeat patterns, check if they have captures
|
|
217
|
+
// by looking at the inner pattern
|
|
218
|
+
const innerPattern = repeatPattern.pattern;
|
|
219
|
+
if (innerPattern.kind === "Meta" && innerPattern.pattern.type === "Capture") {
|
|
220
|
+
// This is a repeat pattern with captures
|
|
221
|
+
const capturedElements: Cbor[] = assignments
|
|
222
|
+
.filter(([pIdx, _]) => pIdx === patternIdx)
|
|
223
|
+
.map(([_, eIdx]) => arr[eIdx]);
|
|
224
|
+
|
|
225
|
+
// Create a sub-array from the captured elements
|
|
226
|
+
const subArray = cbor(capturedElements);
|
|
227
|
+
|
|
228
|
+
// Get the capture name from the inner capture pattern
|
|
229
|
+
const captureName = innerPattern.pattern.pattern.name;
|
|
230
|
+
const arrayContextPath = buildSimpleArrayContextPath(arrayCbor, subArray);
|
|
231
|
+
|
|
232
|
+
const existing = allCaptures.get(captureName) ?? [];
|
|
233
|
+
existing.push(arrayContextPath);
|
|
234
|
+
allCaptures.set(captureName, existing);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// For non-repeat patterns or repeat patterns without captures,
|
|
240
|
+
// process each assigned element individually
|
|
241
|
+
const elementIndices = assignments
|
|
242
|
+
.filter(([pIdx, _]) => pIdx === patternIdx)
|
|
243
|
+
.map(([_, eIdx]) => eIdx);
|
|
244
|
+
|
|
245
|
+
for (const elementIdx of elementIndices) {
|
|
246
|
+
const element = arr[elementIdx];
|
|
247
|
+
|
|
248
|
+
// Check if this pattern has any captures
|
|
249
|
+
if (pattern.kind === "Meta" && pattern.pattern.type === "Capture") {
|
|
250
|
+
const captureName = pattern.pattern.pattern.name;
|
|
251
|
+
const arrayContextPath = buildSimpleArrayContextPath(arrayCbor, element);
|
|
252
|
+
|
|
253
|
+
const existing = allCaptures.get(captureName) ?? [];
|
|
254
|
+
existing.push(arrayContextPath);
|
|
255
|
+
allCaptures.set(captureName, existing);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Return the array path and all captures
|
|
261
|
+
return [[[arrayCbor]], allCaptures];
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Tests if a CBOR value matches this array pattern.
|
|
266
|
+
*/
|
|
267
|
+
export const arrayPatternMatches = (pattern: ArrayPattern, haystack: Cbor): boolean => {
|
|
268
|
+
if (!isArray(haystack)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
switch (pattern.variant) {
|
|
273
|
+
case "Any":
|
|
274
|
+
return true;
|
|
275
|
+
case "Elements": {
|
|
276
|
+
const arr = getArrayElements(haystack);
|
|
277
|
+
if (arr === undefined) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const elemPattern = pattern.pattern;
|
|
282
|
+
|
|
283
|
+
// Check pattern type for appropriate matching
|
|
284
|
+
if (elemPattern.kind === "Meta") {
|
|
285
|
+
if (elemPattern.pattern.type === "Sequence") {
|
|
286
|
+
// Use sequence matching with backtracking
|
|
287
|
+
return matchSequencePatternsAgainstArray(elemPattern.pattern.pattern, arr);
|
|
288
|
+
}
|
|
289
|
+
if (elemPattern.pattern.type === "Repeat") {
|
|
290
|
+
// Use repeat matching
|
|
291
|
+
return matchRepeatPatternAgainstArray(elemPattern.pattern.pattern, arr);
|
|
292
|
+
}
|
|
293
|
+
if (elemPattern.pattern.type === "Capture") {
|
|
294
|
+
// For capture patterns, check if any element matches
|
|
295
|
+
return arr.some((element) => matchPattern(elemPattern, element));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// For value/structure patterns, require exactly one element that matches
|
|
300
|
+
if (
|
|
301
|
+
elemPattern.kind === "Value" ||
|
|
302
|
+
elemPattern.kind === "Structure" ||
|
|
303
|
+
(elemPattern.kind === "Meta" && elemPattern.pattern.type === "Any")
|
|
304
|
+
) {
|
|
305
|
+
if (arr.length !== 1) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
return matchPattern(elemPattern, arr[0]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// For other meta patterns (or, and, etc.), check if any element matches
|
|
312
|
+
return arr.some((element) => matchPattern(elemPattern, element));
|
|
313
|
+
}
|
|
314
|
+
case "Length": {
|
|
315
|
+
const len = arrayLength(haystack);
|
|
316
|
+
return len !== undefined && pattern.length.contains(len);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Returns paths to matching array values.
|
|
323
|
+
*/
|
|
324
|
+
export const arrayPatternPaths = (pattern: ArrayPattern, haystack: Cbor): Path[] => {
|
|
325
|
+
if (!isArray(haystack)) {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const arr = getArrayElements(haystack);
|
|
330
|
+
if (arr === undefined) {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
switch (pattern.variant) {
|
|
335
|
+
case "Any":
|
|
336
|
+
return [[haystack]];
|
|
337
|
+
|
|
338
|
+
case "Elements": {
|
|
339
|
+
const elemPattern = pattern.pattern;
|
|
340
|
+
|
|
341
|
+
// Check pattern type for appropriate matching
|
|
342
|
+
if (elemPattern.kind === "Meta") {
|
|
343
|
+
if (elemPattern.pattern.type === "Sequence") {
|
|
344
|
+
const seqPattern = elemPattern.pattern.pattern;
|
|
345
|
+
const hasRepeats = hasRepeatPatternsInSlice(seqPattern.patterns);
|
|
346
|
+
|
|
347
|
+
if (hasRepeats) {
|
|
348
|
+
// Use complex sequence matching
|
|
349
|
+
return matchComplexSequence(haystack, elemPattern);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Simple sequence: match each pattern against consecutive elements
|
|
353
|
+
if (seqPattern.patterns.length === arr.length) {
|
|
354
|
+
for (let i = 0; i < seqPattern.patterns.length; i++) {
|
|
355
|
+
if (!matchPattern(seqPattern.patterns[i], arr[i])) {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return [[haystack]];
|
|
360
|
+
}
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (elemPattern.pattern.type === "Repeat") {
|
|
365
|
+
return matchComplexSequence(haystack, elemPattern);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (elemPattern.pattern.type === "Capture") {
|
|
369
|
+
// For capture patterns, check if any element matches
|
|
370
|
+
const hasMatch = arr.some((element) => matchPattern(elemPattern, element));
|
|
371
|
+
return hasMatch ? [[haystack]] : [];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// For value/structure patterns, require exactly one element that matches
|
|
376
|
+
if (
|
|
377
|
+
elemPattern.kind === "Value" ||
|
|
378
|
+
elemPattern.kind === "Structure" ||
|
|
379
|
+
(elemPattern.kind === "Meta" && elemPattern.pattern.type === "Any")
|
|
380
|
+
) {
|
|
381
|
+
if (arr.length !== 1) {
|
|
382
|
+
return [];
|
|
383
|
+
}
|
|
384
|
+
return matchPattern(elemPattern, arr[0]) ? [[haystack]] : [];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// For other meta patterns, check if any element matches
|
|
388
|
+
const hasMatch = arr.some((element) => matchPattern(elemPattern, element));
|
|
389
|
+
return hasMatch ? [[haystack]] : [];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
case "Length":
|
|
393
|
+
return pattern.length.contains(arr.length) ? [[haystack]] : [];
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Returns paths with captures for array patterns.
|
|
399
|
+
*/
|
|
400
|
+
export const arrayPatternPathsWithCaptures = (
|
|
401
|
+
pattern: ArrayPattern,
|
|
402
|
+
haystack: Cbor,
|
|
403
|
+
): [Path[], Map<string, Path[]>] => {
|
|
404
|
+
if (!isArray(haystack)) {
|
|
405
|
+
return [[], new Map<string, Path[]>()];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const arr = getArrayElements(haystack);
|
|
409
|
+
if (arr === undefined) {
|
|
410
|
+
return [[], new Map<string, Path[]>()];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
switch (pattern.variant) {
|
|
414
|
+
case "Any":
|
|
415
|
+
case "Length":
|
|
416
|
+
return [arrayPatternPaths(pattern, haystack), new Map<string, Path[]>()];
|
|
417
|
+
|
|
418
|
+
case "Elements": {
|
|
419
|
+
const elemPattern = pattern.pattern;
|
|
420
|
+
|
|
421
|
+
// Check for sequence patterns with captures
|
|
422
|
+
if (elemPattern.kind === "Meta" && elemPattern.pattern.type === "Sequence") {
|
|
423
|
+
const seqPattern = elemPattern.pattern.pattern;
|
|
424
|
+
|
|
425
|
+
// First check if this pattern matches
|
|
426
|
+
if (!arrayPatternMatches(pattern, haystack)) {
|
|
427
|
+
return [[], new Map<string, Path[]>()];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return handleSequenceCaptures(seqPattern, haystack, arr);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// For capture patterns
|
|
434
|
+
if (elemPattern.kind === "Meta" && elemPattern.pattern.type === "Capture") {
|
|
435
|
+
const capturePattern = elemPattern.pattern.pattern;
|
|
436
|
+
const matchingElements = arr.filter((element) => matchPattern(elemPattern, element));
|
|
437
|
+
|
|
438
|
+
if (matchingElements.length === 0) {
|
|
439
|
+
return [[], new Map<string, Path[]>()];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const captures = new Map<string, Path[]>();
|
|
443
|
+
const paths: Path[] = [];
|
|
444
|
+
|
|
445
|
+
for (const element of matchingElements) {
|
|
446
|
+
paths.push(buildSimpleArrayContextPath(haystack, element));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
captures.set(capturePattern.name, paths);
|
|
450
|
+
return [[[haystack]], captures];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Default: no captures
|
|
454
|
+
return [arrayPatternPaths(pattern, haystack), new Map<string, Path[]>()];
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Formats an ArrayPattern as a string.
|
|
461
|
+
*/
|
|
462
|
+
export const arrayPatternDisplay = (
|
|
463
|
+
pattern: ArrayPattern,
|
|
464
|
+
patternDisplay: (p: Pattern) => string,
|
|
465
|
+
): string => {
|
|
466
|
+
switch (pattern.variant) {
|
|
467
|
+
case "Any":
|
|
468
|
+
return "array";
|
|
469
|
+
case "Elements": {
|
|
470
|
+
const elemPattern = pattern.pattern;
|
|
471
|
+
// For sequence patterns within arrays, format elements with commas
|
|
472
|
+
if (elemPattern.kind === "Meta" && elemPattern.pattern.type === "Sequence") {
|
|
473
|
+
const parts = elemPattern.pattern.pattern.patterns.map(patternDisplay);
|
|
474
|
+
return `[${parts.join(", ")}]`;
|
|
475
|
+
}
|
|
476
|
+
return `[${patternDisplay(pattern.pattern)}]`;
|
|
477
|
+
}
|
|
478
|
+
case "Length":
|
|
479
|
+
return `[${pattern.length.toString()}]`;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Compares two ArrayPatterns for equality.
|
|
485
|
+
*/
|
|
486
|
+
export const arrayPatternEquals = (
|
|
487
|
+
a: ArrayPattern,
|
|
488
|
+
b: ArrayPattern,
|
|
489
|
+
patternEquals: (p1: Pattern, p2: Pattern) => boolean,
|
|
490
|
+
): boolean => {
|
|
491
|
+
if (a.variant !== b.variant) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
switch (a.variant) {
|
|
495
|
+
case "Any":
|
|
496
|
+
return true;
|
|
497
|
+
case "Elements":
|
|
498
|
+
return patternEquals(a.pattern, (b as typeof a).pattern);
|
|
499
|
+
case "Length":
|
|
500
|
+
return a.length.equals((b as typeof a).length);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure patterns for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/structure
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from "./array-pattern";
|
|
8
|
+
export * from "./map-pattern";
|
|
9
|
+
export * from "./tagged-pattern";
|
|
10
|
+
|
|
11
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
12
|
+
import type { Path } from "../../format";
|
|
13
|
+
import type { Pattern } from "../index";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
type ArrayPattern,
|
|
17
|
+
arrayPatternPaths,
|
|
18
|
+
arrayPatternDisplay,
|
|
19
|
+
arrayPatternPathsWithCaptures,
|
|
20
|
+
} from "./array-pattern";
|
|
21
|
+
import {
|
|
22
|
+
type MapPattern,
|
|
23
|
+
mapPatternPaths,
|
|
24
|
+
mapPatternDisplay,
|
|
25
|
+
mapPatternPathsWithCaptures,
|
|
26
|
+
} from "./map-pattern";
|
|
27
|
+
import {
|
|
28
|
+
type TaggedPattern,
|
|
29
|
+
taggedPatternPaths,
|
|
30
|
+
taggedPatternDisplay,
|
|
31
|
+
taggedPatternPathsWithCaptures,
|
|
32
|
+
} from "./tagged-pattern";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Union of all structure pattern types.
|
|
36
|
+
*/
|
|
37
|
+
export type StructurePattern =
|
|
38
|
+
| { readonly type: "Array"; readonly pattern: ArrayPattern }
|
|
39
|
+
| { readonly type: "Map"; readonly pattern: MapPattern }
|
|
40
|
+
| { readonly type: "Tagged"; readonly pattern: TaggedPattern };
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns paths to matching structures for a StructurePattern.
|
|
44
|
+
*/
|
|
45
|
+
export const structurePatternPaths = (pattern: StructurePattern, haystack: Cbor): Path[] => {
|
|
46
|
+
switch (pattern.type) {
|
|
47
|
+
case "Array":
|
|
48
|
+
return arrayPatternPaths(pattern.pattern, haystack);
|
|
49
|
+
case "Map":
|
|
50
|
+
return mapPatternPaths(pattern.pattern, haystack);
|
|
51
|
+
case "Tagged":
|
|
52
|
+
return taggedPatternPaths(pattern.pattern, haystack);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Tests if a CBOR value matches a StructurePattern.
|
|
58
|
+
*/
|
|
59
|
+
export const structurePatternMatches = (pattern: StructurePattern, haystack: Cbor): boolean => {
|
|
60
|
+
return structurePatternPaths(pattern, haystack).length > 0;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns paths with captures for a StructurePattern.
|
|
65
|
+
* Used internally by the VM to avoid infinite recursion.
|
|
66
|
+
*/
|
|
67
|
+
export const structurePatternPathsWithCaptures = (
|
|
68
|
+
pattern: StructurePattern,
|
|
69
|
+
haystack: Cbor,
|
|
70
|
+
): [Path[], Map<string, Path[]>] => {
|
|
71
|
+
switch (pattern.type) {
|
|
72
|
+
case "Array":
|
|
73
|
+
return arrayPatternPathsWithCaptures(pattern.pattern, haystack);
|
|
74
|
+
case "Map":
|
|
75
|
+
return mapPatternPathsWithCaptures(pattern.pattern, haystack);
|
|
76
|
+
case "Tagged":
|
|
77
|
+
return taggedPatternPathsWithCaptures(pattern.pattern, haystack);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Formats a StructurePattern as a string.
|
|
83
|
+
*/
|
|
84
|
+
export const structurePatternDisplay = (
|
|
85
|
+
pattern: StructurePattern,
|
|
86
|
+
patternDisplay: (p: Pattern) => string,
|
|
87
|
+
): string => {
|
|
88
|
+
switch (pattern.type) {
|
|
89
|
+
case "Array":
|
|
90
|
+
return arrayPatternDisplay(pattern.pattern, patternDisplay);
|
|
91
|
+
case "Map":
|
|
92
|
+
return mapPatternDisplay(pattern.pattern, patternDisplay);
|
|
93
|
+
case "Tagged":
|
|
94
|
+
return taggedPatternDisplay(pattern.pattern, patternDisplay);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Convenience constructors for StructurePattern
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates an Array StructurePattern.
|
|
102
|
+
*/
|
|
103
|
+
export const structureArray = (pattern: ArrayPattern): StructurePattern => ({
|
|
104
|
+
type: "Array",
|
|
105
|
+
pattern,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Creates a Map StructurePattern.
|
|
110
|
+
*/
|
|
111
|
+
export const structureMap = (pattern: MapPattern): StructurePattern => ({
|
|
112
|
+
type: "Map",
|
|
113
|
+
pattern,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates a Tagged StructurePattern.
|
|
118
|
+
*/
|
|
119
|
+
export const structureTagged = (pattern: TaggedPattern): StructurePattern => ({
|
|
120
|
+
type: "Tagged",
|
|
121
|
+
pattern,
|
|
122
|
+
});
|