@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,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map pattern for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/structure/map-pattern
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cbor, CborInput } from "@bcts/dcbor";
|
|
8
|
+
import { isMap, mapSize, mapKeys, mapValue, cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
import { Interval } from "../../interval";
|
|
12
|
+
import { matchPattern } from "../match-registry";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pattern for matching CBOR map structures.
|
|
16
|
+
*/
|
|
17
|
+
export type MapPattern =
|
|
18
|
+
| { readonly variant: "Any" }
|
|
19
|
+
| {
|
|
20
|
+
readonly variant: "Constraints";
|
|
21
|
+
readonly constraints: [Pattern, Pattern][];
|
|
22
|
+
}
|
|
23
|
+
| { readonly variant: "Length"; readonly length: Interval };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a MapPattern that matches any map.
|
|
27
|
+
*/
|
|
28
|
+
export const mapPatternAny = (): MapPattern => ({ variant: "Any" });
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a MapPattern that matches maps with key-value constraints.
|
|
32
|
+
*/
|
|
33
|
+
export const mapPatternWithConstraints = (constraints: [Pattern, Pattern][]): MapPattern => ({
|
|
34
|
+
variant: "Constraints",
|
|
35
|
+
constraints,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a MapPattern that matches maps with a specific number of entries.
|
|
40
|
+
*/
|
|
41
|
+
export const mapPatternWithLength = (length: number): MapPattern => ({
|
|
42
|
+
variant: "Length",
|
|
43
|
+
length: Interval.exactly(length),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a MapPattern that matches maps with length in a range.
|
|
48
|
+
*/
|
|
49
|
+
export const mapPatternWithLengthRange = (min: number, max?: number): MapPattern => ({
|
|
50
|
+
variant: "Length",
|
|
51
|
+
length: max !== undefined ? Interval.from(min, max) : Interval.atLeast(min),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a MapPattern that matches maps with length in an interval.
|
|
56
|
+
*/
|
|
57
|
+
export const mapPatternWithLengthInterval = (interval: Interval): MapPattern => ({
|
|
58
|
+
variant: "Length",
|
|
59
|
+
length: interval,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tests if a CBOR value matches this map pattern.
|
|
64
|
+
*/
|
|
65
|
+
export const mapPatternMatches = (pattern: MapPattern, haystack: Cbor): boolean => {
|
|
66
|
+
if (!isMap(haystack)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch (pattern.variant) {
|
|
71
|
+
case "Any":
|
|
72
|
+
return true;
|
|
73
|
+
case "Constraints": {
|
|
74
|
+
const keys = mapKeys(haystack);
|
|
75
|
+
if (keys === undefined) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
// All constraints must be satisfied
|
|
79
|
+
for (const [keyPattern, valuePattern] of pattern.constraints) {
|
|
80
|
+
let foundMatch = false;
|
|
81
|
+
for (const key of keys) {
|
|
82
|
+
if (matchPattern(keyPattern, key)) {
|
|
83
|
+
const rawValue = mapValue(haystack, key);
|
|
84
|
+
if (rawValue !== undefined && rawValue !== null) {
|
|
85
|
+
// Wrap raw JavaScript value in CBOR if needed
|
|
86
|
+
const value = (rawValue as Cbor)?.isCbor
|
|
87
|
+
? (rawValue as Cbor)
|
|
88
|
+
: cbor(rawValue as CborInput);
|
|
89
|
+
if (matchPattern(valuePattern, value)) {
|
|
90
|
+
foundMatch = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!foundMatch) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
case "Length": {
|
|
103
|
+
const size = mapSize(haystack);
|
|
104
|
+
return size !== undefined && pattern.length.contains(size);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns paths to matching map values.
|
|
111
|
+
*/
|
|
112
|
+
export const mapPatternPaths = (pattern: MapPattern, haystack: Cbor): Path[] => {
|
|
113
|
+
if (mapPatternMatches(pattern, haystack)) {
|
|
114
|
+
return [[haystack]];
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Helper to build a map context path (map -> element).
|
|
121
|
+
*/
|
|
122
|
+
const buildMapContextPath = (mapCbor: Cbor, element: Cbor): Path => {
|
|
123
|
+
return [mapCbor, element];
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Collects captures from a pattern by checking if it's a capture pattern.
|
|
128
|
+
*/
|
|
129
|
+
const collectCapturesFromPattern = (
|
|
130
|
+
pattern: Pattern,
|
|
131
|
+
matchedValue: Cbor,
|
|
132
|
+
mapContext: Cbor,
|
|
133
|
+
captures: Map<string, Path[]>,
|
|
134
|
+
): void => {
|
|
135
|
+
if (pattern.kind === "Meta" && pattern.pattern.type === "Capture") {
|
|
136
|
+
const captureName = pattern.pattern.pattern.name;
|
|
137
|
+
const contextPath = buildMapContextPath(mapContext, matchedValue);
|
|
138
|
+
const existing = captures.get(captureName) ?? [];
|
|
139
|
+
existing.push(contextPath);
|
|
140
|
+
captures.set(captureName, existing);
|
|
141
|
+
|
|
142
|
+
// Also collect from inner pattern
|
|
143
|
+
collectCapturesFromPattern(pattern.pattern.pattern.pattern, matchedValue, mapContext, captures);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns paths with captures for map patterns.
|
|
149
|
+
*/
|
|
150
|
+
export const mapPatternPathsWithCaptures = (
|
|
151
|
+
pattern: MapPattern,
|
|
152
|
+
haystack: Cbor,
|
|
153
|
+
): [Path[], Map<string, Path[]>] => {
|
|
154
|
+
if (!isMap(haystack)) {
|
|
155
|
+
return [[], new Map<string, Path[]>()];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
switch (pattern.variant) {
|
|
159
|
+
case "Any":
|
|
160
|
+
case "Length":
|
|
161
|
+
return [mapPatternPaths(pattern, haystack), new Map<string, Path[]>()];
|
|
162
|
+
|
|
163
|
+
case "Constraints": {
|
|
164
|
+
const keys = mapKeys(haystack);
|
|
165
|
+
if (keys === undefined) {
|
|
166
|
+
return [[], new Map<string, Path[]>()];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const captures = new Map<string, Path[]>();
|
|
170
|
+
|
|
171
|
+
// For each constraint, find the matching key-value pair and collect captures
|
|
172
|
+
for (const [keyPattern, valuePattern] of pattern.constraints) {
|
|
173
|
+
for (const key of keys) {
|
|
174
|
+
if (matchPattern(keyPattern, key)) {
|
|
175
|
+
const rawValue = mapValue(haystack, key);
|
|
176
|
+
if (rawValue !== undefined && rawValue !== null) {
|
|
177
|
+
// Wrap raw JavaScript value in CBOR if needed
|
|
178
|
+
const value = (rawValue as Cbor)?.isCbor
|
|
179
|
+
? (rawValue as Cbor)
|
|
180
|
+
: cbor(rawValue as CborInput);
|
|
181
|
+
if (matchPattern(valuePattern, value)) {
|
|
182
|
+
// Collect captures from key pattern
|
|
183
|
+
collectCapturesFromPattern(keyPattern, key, haystack, captures);
|
|
184
|
+
// Collect captures from value pattern
|
|
185
|
+
collectCapturesFromPattern(valuePattern, value, haystack, captures);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// If pattern matches, return the map path with captures
|
|
194
|
+
if (mapPatternMatches(pattern, haystack)) {
|
|
195
|
+
return [[[haystack]], captures];
|
|
196
|
+
}
|
|
197
|
+
return [[], new Map<string, Path[]>()];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Formats a MapPattern as a string.
|
|
204
|
+
*/
|
|
205
|
+
export const mapPatternDisplay = (
|
|
206
|
+
pattern: MapPattern,
|
|
207
|
+
patternDisplay: (p: Pattern) => string,
|
|
208
|
+
): string => {
|
|
209
|
+
switch (pattern.variant) {
|
|
210
|
+
case "Any":
|
|
211
|
+
return "map";
|
|
212
|
+
case "Constraints": {
|
|
213
|
+
const parts = pattern.constraints.map(
|
|
214
|
+
([k, v]) => `${patternDisplay(k)}: ${patternDisplay(v)}`,
|
|
215
|
+
);
|
|
216
|
+
return `{${parts.join(", ")}}`;
|
|
217
|
+
}
|
|
218
|
+
case "Length":
|
|
219
|
+
return `{${pattern.length.toString()}}`;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Compares two MapPatterns for equality.
|
|
225
|
+
*/
|
|
226
|
+
export const mapPatternEquals = (
|
|
227
|
+
a: MapPattern,
|
|
228
|
+
b: MapPattern,
|
|
229
|
+
patternEquals: (p1: Pattern, p2: Pattern) => boolean,
|
|
230
|
+
): boolean => {
|
|
231
|
+
if (a.variant !== b.variant) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
switch (a.variant) {
|
|
235
|
+
case "Any":
|
|
236
|
+
return true;
|
|
237
|
+
case "Constraints": {
|
|
238
|
+
const bConstraints = (b as typeof a).constraints;
|
|
239
|
+
if (a.constraints.length !== bConstraints.length) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
for (let i = 0; i < a.constraints.length; i++) {
|
|
243
|
+
if (
|
|
244
|
+
!patternEquals(a.constraints[i][0], bConstraints[i][0]) ||
|
|
245
|
+
!patternEquals(a.constraints[i][1], bConstraints[i][1])
|
|
246
|
+
) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
case "Length":
|
|
253
|
+
return a.length.equals((b as typeof a).length);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tagged pattern for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/structure/tagged-pattern
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cbor, Tag } from "@bcts/dcbor";
|
|
8
|
+
import { isTagged, tagValue, tagContent } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
import { matchPattern, getPatternPathsWithCapturesDirect } from "../match-registry";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pattern for matching CBOR tagged value structures.
|
|
15
|
+
*/
|
|
16
|
+
export type TaggedPattern =
|
|
17
|
+
| { readonly variant: "Any" }
|
|
18
|
+
| {
|
|
19
|
+
readonly variant: "Tag";
|
|
20
|
+
readonly tag: Tag;
|
|
21
|
+
readonly pattern: Pattern;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
readonly variant: "Name";
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly pattern: Pattern;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
readonly variant: "Regex";
|
|
30
|
+
readonly regex: RegExp;
|
|
31
|
+
readonly pattern: Pattern;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a TaggedPattern that matches any tagged value.
|
|
36
|
+
*/
|
|
37
|
+
export const taggedPatternAny = (): TaggedPattern => ({ variant: "Any" });
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a TaggedPattern that matches tagged values with specific tag and content.
|
|
41
|
+
*/
|
|
42
|
+
export const taggedPatternWithTag = (tag: Tag, pattern: Pattern): TaggedPattern => ({
|
|
43
|
+
variant: "Tag",
|
|
44
|
+
tag,
|
|
45
|
+
pattern,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a TaggedPattern that matches tagged values with a tag having the given name.
|
|
50
|
+
*/
|
|
51
|
+
export const taggedPatternWithName = (name: string, pattern: Pattern): TaggedPattern => ({
|
|
52
|
+
variant: "Name",
|
|
53
|
+
name,
|
|
54
|
+
pattern,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates a TaggedPattern that matches tagged values with a tag name matching the regex.
|
|
59
|
+
*/
|
|
60
|
+
export const taggedPatternWithRegex = (regex: RegExp, pattern: Pattern): TaggedPattern => ({
|
|
61
|
+
variant: "Regex",
|
|
62
|
+
regex,
|
|
63
|
+
pattern,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Tests if a CBOR value matches this tagged pattern.
|
|
68
|
+
*/
|
|
69
|
+
export const taggedPatternMatches = (pattern: TaggedPattern, haystack: Cbor): boolean => {
|
|
70
|
+
if (!isTagged(haystack)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const tag = tagValue(haystack);
|
|
75
|
+
const content = tagContent(haystack);
|
|
76
|
+
|
|
77
|
+
if (content === undefined) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
switch (pattern.variant) {
|
|
82
|
+
case "Any":
|
|
83
|
+
return true;
|
|
84
|
+
case "Tag":
|
|
85
|
+
return tag === pattern.tag.value && matchPattern(pattern.pattern, content);
|
|
86
|
+
case "Name": {
|
|
87
|
+
// Get tag name from global tags store
|
|
88
|
+
// For now, compare the tag value as string
|
|
89
|
+
const tagName = String(tag);
|
|
90
|
+
return tagName === pattern.name && matchPattern(pattern.pattern, content);
|
|
91
|
+
}
|
|
92
|
+
case "Regex": {
|
|
93
|
+
const tagName = String(tag);
|
|
94
|
+
return pattern.regex.test(tagName) && matchPattern(pattern.pattern, content);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns paths to matching tagged values.
|
|
101
|
+
*/
|
|
102
|
+
export const taggedPatternPaths = (pattern: TaggedPattern, haystack: Cbor): Path[] => {
|
|
103
|
+
if (taggedPatternMatches(pattern, haystack)) {
|
|
104
|
+
return [[haystack]];
|
|
105
|
+
}
|
|
106
|
+
return [];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns paths with captures for a tagged pattern.
|
|
111
|
+
* Collects captures from inner patterns for Tag variant.
|
|
112
|
+
*/
|
|
113
|
+
export const taggedPatternPathsWithCaptures = (
|
|
114
|
+
pattern: TaggedPattern,
|
|
115
|
+
haystack: Cbor,
|
|
116
|
+
): [Path[], Map<string, Path[]>] => {
|
|
117
|
+
if (!isTagged(haystack)) {
|
|
118
|
+
return [[], new Map<string, Path[]>()];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const tag = tagValue(haystack);
|
|
122
|
+
const content = tagContent(haystack);
|
|
123
|
+
|
|
124
|
+
if (content === undefined) {
|
|
125
|
+
return [[], new Map<string, Path[]>()];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
switch (pattern.variant) {
|
|
129
|
+
case "Any":
|
|
130
|
+
// Matches any tagged value, no captures
|
|
131
|
+
return [[[haystack]], new Map<string, Path[]>()];
|
|
132
|
+
|
|
133
|
+
case "Tag": {
|
|
134
|
+
if (tag !== pattern.tag.value) {
|
|
135
|
+
return [[], new Map<string, Path[]>()];
|
|
136
|
+
}
|
|
137
|
+
// Get paths and captures from inner pattern
|
|
138
|
+
const innerResult = getPatternPathsWithCapturesDirect(pattern.pattern, content);
|
|
139
|
+
if (innerResult.paths.length === 0) {
|
|
140
|
+
return [[], innerResult.captures];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Build paths that include the tagged value as root
|
|
144
|
+
const taggedPaths: Path[] = innerResult.paths.map((contentPath: Path) => {
|
|
145
|
+
const path: Cbor[] = [haystack];
|
|
146
|
+
// Skip the content's root in the path
|
|
147
|
+
if (contentPath.length > 1) {
|
|
148
|
+
path.push(...contentPath.slice(1));
|
|
149
|
+
}
|
|
150
|
+
return path;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Update captures to include tagged value as root
|
|
154
|
+
const updatedCaptures = new Map<string, Path[]>();
|
|
155
|
+
for (const [name, capturePaths] of innerResult.captures) {
|
|
156
|
+
const updated: Path[] = capturePaths.map((_capturePath: Path) => {
|
|
157
|
+
// For tagged patterns, capture path is [tagged_value, content]
|
|
158
|
+
return [haystack, content];
|
|
159
|
+
});
|
|
160
|
+
updatedCaptures.set(name, updated);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return [taggedPaths, updatedCaptures];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
case "Name":
|
|
167
|
+
case "Regex":
|
|
168
|
+
// For other variants, fall back to basic paths without captures
|
|
169
|
+
return [taggedPatternPaths(pattern, haystack), new Map<string, Path[]>()];
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Formats a TaggedPattern as a string.
|
|
175
|
+
*/
|
|
176
|
+
export const taggedPatternDisplay = (
|
|
177
|
+
pattern: TaggedPattern,
|
|
178
|
+
patternDisplay: (p: Pattern) => string,
|
|
179
|
+
): string => {
|
|
180
|
+
switch (pattern.variant) {
|
|
181
|
+
case "Any":
|
|
182
|
+
return "tagged";
|
|
183
|
+
case "Tag":
|
|
184
|
+
return `tagged(${pattern.tag.value}, ${patternDisplay(pattern.pattern)})`;
|
|
185
|
+
case "Name":
|
|
186
|
+
return `tagged(${pattern.name}, ${patternDisplay(pattern.pattern)})`;
|
|
187
|
+
case "Regex":
|
|
188
|
+
return `tagged(/${pattern.regex.source}/, ${patternDisplay(pattern.pattern)})`;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean pattern for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/value/bool-pattern
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
8
|
+
import { asBoolean } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Pattern for matching boolean values in dCBOR.
|
|
13
|
+
*/
|
|
14
|
+
export type BoolPattern =
|
|
15
|
+
| { readonly variant: "Any" }
|
|
16
|
+
| { readonly variant: "Value"; readonly value: boolean };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a BoolPattern that matches any boolean value.
|
|
20
|
+
*/
|
|
21
|
+
export const boolPatternAny = (): BoolPattern => ({ variant: "Any" });
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a BoolPattern that matches a specific boolean value.
|
|
25
|
+
*/
|
|
26
|
+
export const boolPatternValue = (value: boolean): BoolPattern => ({
|
|
27
|
+
variant: "Value",
|
|
28
|
+
value,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tests if a CBOR value matches this boolean pattern.
|
|
33
|
+
*/
|
|
34
|
+
export const boolPatternMatches = (pattern: BoolPattern, haystack: Cbor): boolean => {
|
|
35
|
+
const value = asBoolean(haystack);
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
switch (pattern.variant) {
|
|
40
|
+
case "Any":
|
|
41
|
+
return true;
|
|
42
|
+
case "Value":
|
|
43
|
+
return value === pattern.value;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns paths to matching boolean values.
|
|
49
|
+
*/
|
|
50
|
+
export const boolPatternPaths = (pattern: BoolPattern, haystack: Cbor): Path[] => {
|
|
51
|
+
if (boolPatternMatches(pattern, haystack)) {
|
|
52
|
+
return [[haystack]];
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Formats a BoolPattern as a string.
|
|
59
|
+
*/
|
|
60
|
+
export const boolPatternDisplay = (pattern: BoolPattern): string => {
|
|
61
|
+
switch (pattern.variant) {
|
|
62
|
+
case "Any":
|
|
63
|
+
return "bool";
|
|
64
|
+
case "Value":
|
|
65
|
+
return pattern.value ? "true" : "false";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte array utility functions.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/value/bytes-utils
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compares two Uint8Arrays for equality.
|
|
9
|
+
*/
|
|
10
|
+
export const bytesEqual = (a: Uint8Array, b: Uint8Array): boolean => {
|
|
11
|
+
if (a.length !== b.length) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
for (let i = 0; i < a.length; i++) {
|
|
15
|
+
if (a[i] !== b[i]) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tests if bytes start with a prefix.
|
|
24
|
+
*/
|
|
25
|
+
export const bytesStartsWith = (bytes: Uint8Array, prefix: Uint8Array): boolean => {
|
|
26
|
+
if (bytes.length < prefix.length) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
30
|
+
if (bytes[i] !== prefix[i]) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Converts a Uint8Array to a Latin-1 string for regex matching.
|
|
39
|
+
* Each byte value (0-255) maps directly to a character code.
|
|
40
|
+
* This mimics Rust's regex::bytes::Regex behavior.
|
|
41
|
+
*/
|
|
42
|
+
export const bytesToLatin1 = (bytes: Uint8Array): string => {
|
|
43
|
+
let result = "";
|
|
44
|
+
for (const byte of bytes) {
|
|
45
|
+
result += String.fromCharCode(byte);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ByteString pattern for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/value/bytestring-pattern
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
8
|
+
import { asBytes, bytesToHex } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import { bytesEqual, bytesToLatin1 } from "./bytes-utils";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pattern for matching byte string values in dCBOR.
|
|
14
|
+
*
|
|
15
|
+
* The BinaryRegex variant matches against raw bytes by converting them to
|
|
16
|
+
* a Latin-1 string (where each byte 0-255 maps to exactly one character).
|
|
17
|
+
* This mimics Rust's regex::bytes::Regex behavior.
|
|
18
|
+
*
|
|
19
|
+
* For example:
|
|
20
|
+
* - To match bytes starting with 0x00: `/^\x00/`
|
|
21
|
+
* - To match ASCII digits: `/^\d+$/`
|
|
22
|
+
* - To match specific hex pattern: `/\x48\x65\x6c\x6c\x6f/` (matches "Hello")
|
|
23
|
+
*/
|
|
24
|
+
export type ByteStringPattern =
|
|
25
|
+
| { readonly variant: "Any" }
|
|
26
|
+
| { readonly variant: "Value"; readonly value: Uint8Array }
|
|
27
|
+
| { readonly variant: "BinaryRegex"; readonly pattern: RegExp };
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a ByteStringPattern that matches any byte string.
|
|
31
|
+
*/
|
|
32
|
+
export const byteStringPatternAny = (): ByteStringPattern => ({
|
|
33
|
+
variant: "Any",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a ByteStringPattern that matches a specific byte string value.
|
|
38
|
+
*/
|
|
39
|
+
export const byteStringPatternValue = (value: Uint8Array): ByteStringPattern => ({
|
|
40
|
+
variant: "Value",
|
|
41
|
+
value,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a ByteStringPattern that matches byte strings by binary regex.
|
|
46
|
+
*
|
|
47
|
+
* The regex matches against raw bytes converted to a Latin-1 string.
|
|
48
|
+
* Use escape sequences like `\x00` to match specific byte values.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Match bytes starting with 0x00
|
|
53
|
+
* byteStringPatternBinaryRegex(/^\x00/)
|
|
54
|
+
*
|
|
55
|
+
* // Match ASCII "Hello"
|
|
56
|
+
* byteStringPatternBinaryRegex(/Hello/)
|
|
57
|
+
*
|
|
58
|
+
* // Match any digits
|
|
59
|
+
* byteStringPatternBinaryRegex(/^\d+$/)
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export const byteStringPatternBinaryRegex = (pattern: RegExp): ByteStringPattern => ({
|
|
63
|
+
variant: "BinaryRegex",
|
|
64
|
+
pattern,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Tests if a CBOR value matches this byte string pattern.
|
|
69
|
+
*/
|
|
70
|
+
export const byteStringPatternMatches = (pattern: ByteStringPattern, haystack: Cbor): boolean => {
|
|
71
|
+
const value = asBytes(haystack);
|
|
72
|
+
if (value === undefined) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
switch (pattern.variant) {
|
|
76
|
+
case "Any":
|
|
77
|
+
return true;
|
|
78
|
+
case "Value":
|
|
79
|
+
return bytesEqual(value, pattern.value);
|
|
80
|
+
case "BinaryRegex": {
|
|
81
|
+
// Convert bytes to Latin-1 string for regex matching
|
|
82
|
+
// This mimics Rust's regex::bytes::Regex behavior
|
|
83
|
+
const latin1String = bytesToLatin1(value);
|
|
84
|
+
return pattern.pattern.test(latin1String);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns paths to matching byte string values.
|
|
91
|
+
*/
|
|
92
|
+
export const byteStringPatternPaths = (pattern: ByteStringPattern, haystack: Cbor): Path[] => {
|
|
93
|
+
if (byteStringPatternMatches(pattern, haystack)) {
|
|
94
|
+
return [[haystack]];
|
|
95
|
+
}
|
|
96
|
+
return [];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Formats a ByteStringPattern as a string.
|
|
101
|
+
*/
|
|
102
|
+
export const byteStringPatternDisplay = (pattern: ByteStringPattern): string => {
|
|
103
|
+
switch (pattern.variant) {
|
|
104
|
+
case "Any":
|
|
105
|
+
return "bstr";
|
|
106
|
+
case "Value":
|
|
107
|
+
return `h'${bytesToHex(pattern.value)}'`;
|
|
108
|
+
case "BinaryRegex":
|
|
109
|
+
return `h'/${pattern.pattern.source}/'`;
|
|
110
|
+
}
|
|
111
|
+
};
|