@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,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Late-binding registry for pattern matching functions.
|
|
3
|
+
* This avoids circular dependencies between pattern modules.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/match-registry
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../format";
|
|
10
|
+
|
|
11
|
+
// Forward declare Pattern type to avoid circular import
|
|
12
|
+
// The actual Pattern type is defined in ./index.ts
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
type Pattern = any;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Match result with paths and captures.
|
|
18
|
+
*/
|
|
19
|
+
export interface MatchResultInternal {
|
|
20
|
+
readonly paths: Path[];
|
|
21
|
+
readonly captures: Map<string, Path[]>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Registry for the pattern matching function.
|
|
26
|
+
* This gets set by pattern/index.ts after all modules are loaded.
|
|
27
|
+
*/
|
|
28
|
+
export let matchFn: ((pattern: Pattern, haystack: Cbor) => boolean) | undefined;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Registry for the pattern paths function.
|
|
32
|
+
* This gets set by pattern/index.ts after all modules are loaded.
|
|
33
|
+
*/
|
|
34
|
+
export let pathsFn: ((pattern: Pattern, haystack: Cbor) => Path[]) | undefined;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Registry for the pattern paths with captures function (VM-based).
|
|
38
|
+
* This gets set by pattern/index.ts after all modules are loaded.
|
|
39
|
+
*/
|
|
40
|
+
export let pathsWithCapturesFn:
|
|
41
|
+
| ((pattern: Pattern, haystack: Cbor) => MatchResultInternal)
|
|
42
|
+
| undefined;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Registry for the direct pattern paths with captures function (non-VM).
|
|
46
|
+
* This is used by the VM to avoid infinite recursion.
|
|
47
|
+
*/
|
|
48
|
+
export let pathsWithCapturesDirectFn:
|
|
49
|
+
| ((pattern: Pattern, haystack: Cbor) => MatchResultInternal)
|
|
50
|
+
| undefined;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sets the pattern matching function.
|
|
54
|
+
* Called by pattern/index.ts during module initialization.
|
|
55
|
+
*/
|
|
56
|
+
export const setMatchFn = (fn: (pattern: Pattern, haystack: Cbor) => boolean): void => {
|
|
57
|
+
matchFn = fn;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sets the pattern paths function.
|
|
62
|
+
* Called by pattern/index.ts during module initialization.
|
|
63
|
+
*/
|
|
64
|
+
export const setPathsFn = (fn: (pattern: Pattern, haystack: Cbor) => Path[]): void => {
|
|
65
|
+
pathsFn = fn;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Sets the pattern paths with captures function.
|
|
70
|
+
* Called by pattern/index.ts during module initialization.
|
|
71
|
+
*/
|
|
72
|
+
export const setPathsWithCapturesFn = (
|
|
73
|
+
fn: (pattern: Pattern, haystack: Cbor) => MatchResultInternal,
|
|
74
|
+
): void => {
|
|
75
|
+
pathsWithCapturesFn = fn;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sets the direct pattern paths with captures function (non-VM).
|
|
80
|
+
* Called by pattern/index.ts during module initialization.
|
|
81
|
+
*/
|
|
82
|
+
export const setPathsWithCapturesDirectFn = (
|
|
83
|
+
fn: (pattern: Pattern, haystack: Cbor) => MatchResultInternal,
|
|
84
|
+
): void => {
|
|
85
|
+
pathsWithCapturesDirectFn = fn;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Matches a pattern against a CBOR value using the registered function.
|
|
90
|
+
* @throws Error if the match function hasn't been registered yet.
|
|
91
|
+
*/
|
|
92
|
+
export const matchPattern = (pattern: Pattern, haystack: Cbor): boolean => {
|
|
93
|
+
if (matchFn === undefined) {
|
|
94
|
+
throw new Error("Pattern match function not initialized");
|
|
95
|
+
}
|
|
96
|
+
return matchFn(pattern, haystack);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Gets paths for a pattern against a CBOR value using the registered function.
|
|
101
|
+
* @throws Error if the paths function hasn't been registered yet.
|
|
102
|
+
*/
|
|
103
|
+
export const getPatternPaths = (pattern: Pattern, haystack: Cbor): Path[] => {
|
|
104
|
+
if (pathsFn === undefined) {
|
|
105
|
+
throw new Error("Pattern paths function not initialized");
|
|
106
|
+
}
|
|
107
|
+
return pathsFn(pattern, haystack);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Gets paths with captures for a pattern against a CBOR value (VM-based).
|
|
112
|
+
* @throws Error if the function hasn't been registered yet.
|
|
113
|
+
*/
|
|
114
|
+
export const getPatternPathsWithCaptures = (
|
|
115
|
+
pattern: Pattern,
|
|
116
|
+
haystack: Cbor,
|
|
117
|
+
): MatchResultInternal => {
|
|
118
|
+
if (pathsWithCapturesFn === undefined) {
|
|
119
|
+
throw new Error("Pattern paths with captures function not initialized");
|
|
120
|
+
}
|
|
121
|
+
return pathsWithCapturesFn(pattern, haystack);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Gets paths with captures directly without the VM (non-recursive).
|
|
126
|
+
* This is used by the VM to avoid infinite recursion.
|
|
127
|
+
* @throws Error if the function hasn't been registered yet.
|
|
128
|
+
*/
|
|
129
|
+
export const getPatternPathsWithCapturesDirect = (
|
|
130
|
+
pattern: Pattern,
|
|
131
|
+
haystack: Cbor,
|
|
132
|
+
): MatchResultInternal => {
|
|
133
|
+
if (pathsWithCapturesDirectFn === undefined) {
|
|
134
|
+
throw new Error("Direct pattern paths with captures function not initialized");
|
|
135
|
+
}
|
|
136
|
+
return pathsWithCapturesDirectFn(pattern, haystack);
|
|
137
|
+
};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matcher interface for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/matcher
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
8
|
+
import type { Path } from "../format";
|
|
9
|
+
import type { Instr, Program } from "./vm";
|
|
10
|
+
import type { Pattern } from "./index";
|
|
11
|
+
import type { StructurePattern } from "./structure";
|
|
12
|
+
import type { MetaPattern } from "./meta";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Result of pattern matching with captures.
|
|
16
|
+
*/
|
|
17
|
+
export interface MatchWithCaptures {
|
|
18
|
+
readonly paths: Path[];
|
|
19
|
+
readonly captures: Map<string, Path[]>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Interface for objects that can match against CBOR values.
|
|
24
|
+
*
|
|
25
|
+
* This interface defines the contract for all pattern types in the system.
|
|
26
|
+
* Implementations handle matching, path collection, and VM bytecode compilation.
|
|
27
|
+
*/
|
|
28
|
+
export interface Matcher {
|
|
29
|
+
/**
|
|
30
|
+
* Return all matching paths along with any named captures.
|
|
31
|
+
*
|
|
32
|
+
* @param haystack - The CBOR value to match against
|
|
33
|
+
* @returns A tuple of paths and captures map
|
|
34
|
+
*/
|
|
35
|
+
pathsWithCaptures(haystack: Cbor): MatchWithCaptures;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Return only the matching paths, discarding any captures.
|
|
39
|
+
*
|
|
40
|
+
* @param haystack - The CBOR value to match against
|
|
41
|
+
* @returns Array of paths to matching elements
|
|
42
|
+
*/
|
|
43
|
+
paths(haystack: Cbor): Path[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if the pattern matches the given CBOR value.
|
|
47
|
+
*
|
|
48
|
+
* @param haystack - The CBOR value to test
|
|
49
|
+
* @returns true if the pattern matches
|
|
50
|
+
*/
|
|
51
|
+
matches(haystack: Cbor): boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Compile this pattern into VM bytecode.
|
|
55
|
+
*
|
|
56
|
+
* @param code - The instruction array to append to
|
|
57
|
+
* @param literals - The literals array to append to
|
|
58
|
+
* @param captures - The capture names array
|
|
59
|
+
*/
|
|
60
|
+
compile(code: Instr[], literals: Pattern[], captures: string[]): void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Recursively collect all capture names from this pattern.
|
|
64
|
+
*
|
|
65
|
+
* @param names - The array to collect names into
|
|
66
|
+
*/
|
|
67
|
+
collectCaptureNames(names: string[]): void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if the pattern display is "complex" (requires parentheses).
|
|
71
|
+
*
|
|
72
|
+
* @returns true if the pattern requires grouping
|
|
73
|
+
*/
|
|
74
|
+
isComplex(): boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Format the pattern as a string.
|
|
78
|
+
*/
|
|
79
|
+
toString(): string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Default implementation helpers for Matcher.
|
|
84
|
+
*/
|
|
85
|
+
export const MatcherDefaults = {
|
|
86
|
+
/**
|
|
87
|
+
* Default paths implementation using pathsWithCaptures.
|
|
88
|
+
*/
|
|
89
|
+
paths(matcher: Pick<Matcher, "pathsWithCaptures">, haystack: Cbor): Path[] {
|
|
90
|
+
return matcher.pathsWithCaptures(haystack).paths;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Default matches implementation using paths.
|
|
95
|
+
*/
|
|
96
|
+
matches(matcher: Pick<Matcher, "paths">, haystack: Cbor): boolean {
|
|
97
|
+
return matcher.paths(haystack).length > 0;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Default pathsWithCaptures throws not implemented.
|
|
102
|
+
*/
|
|
103
|
+
pathsWithCaptures(_haystack: Cbor): MatchWithCaptures {
|
|
104
|
+
throw new Error("pathsWithCaptures not implemented");
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Default compile throws not implemented.
|
|
109
|
+
*/
|
|
110
|
+
compile(_code: Instr[], _literals: Pattern[], _captures: string[]): void {
|
|
111
|
+
throw new Error("compile not implemented");
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Default collectCaptureNames does nothing.
|
|
116
|
+
*/
|
|
117
|
+
collectCaptureNames(_names: string[]): void {
|
|
118
|
+
// Default implementation does nothing
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Default isComplex returns false.
|
|
123
|
+
*/
|
|
124
|
+
isComplex(): boolean {
|
|
125
|
+
return false;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Compiles a pattern into a VM program.
|
|
131
|
+
*
|
|
132
|
+
* @param pattern - The pattern to compile
|
|
133
|
+
* @returns A compiled program ready for execution
|
|
134
|
+
*/
|
|
135
|
+
export const compilePattern = (pattern: Pattern): Program => {
|
|
136
|
+
const code: Instr[] = [];
|
|
137
|
+
const literals: Pattern[] = [];
|
|
138
|
+
const captureNames: string[] = [];
|
|
139
|
+
|
|
140
|
+
// Collect all capture names first
|
|
141
|
+
collectPatternCaptureNames(pattern, captureNames);
|
|
142
|
+
|
|
143
|
+
// Compile the pattern
|
|
144
|
+
compilePatternToCode(pattern, code, literals, captureNames);
|
|
145
|
+
|
|
146
|
+
// Add final Accept instruction
|
|
147
|
+
code.push({ type: "Accept" });
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
code,
|
|
151
|
+
literals,
|
|
152
|
+
captureNames,
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Recursively collects capture names from a pattern.
|
|
158
|
+
*/
|
|
159
|
+
const collectPatternCaptureNames = (pattern: Pattern, names: string[]): void => {
|
|
160
|
+
switch (pattern.kind) {
|
|
161
|
+
case "Value":
|
|
162
|
+
// Value patterns don't have captures
|
|
163
|
+
break;
|
|
164
|
+
case "Structure":
|
|
165
|
+
// Structure patterns may have element patterns with captures
|
|
166
|
+
collectStructurePatternCaptureNames(pattern.pattern, names);
|
|
167
|
+
break;
|
|
168
|
+
case "Meta":
|
|
169
|
+
collectMetaPatternCaptureNames(pattern.pattern, names);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Collects capture names from structure patterns.
|
|
176
|
+
*/
|
|
177
|
+
const collectStructurePatternCaptureNames = (pattern: StructurePattern, names: string[]): void => {
|
|
178
|
+
switch (pattern.type) {
|
|
179
|
+
case "Array":
|
|
180
|
+
if (pattern.pattern.variant === "Elements") {
|
|
181
|
+
collectPatternCaptureNames(pattern.pattern.pattern, names);
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case "Map":
|
|
185
|
+
if (pattern.pattern.variant === "Constraints") {
|
|
186
|
+
for (const constraint of pattern.pattern.constraints) {
|
|
187
|
+
// Constraints are tuples: [keyPattern, valuePattern]
|
|
188
|
+
collectPatternCaptureNames(constraint[0], names);
|
|
189
|
+
collectPatternCaptureNames(constraint[1], names);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case "Tagged":
|
|
194
|
+
// Tagged patterns (Tag, Name, Regex variants) have a `pattern` property
|
|
195
|
+
if (pattern.pattern.variant !== "Any") {
|
|
196
|
+
collectPatternCaptureNames(pattern.pattern.pattern, names);
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Collects capture names from meta patterns.
|
|
204
|
+
*/
|
|
205
|
+
const collectMetaPatternCaptureNames = (pattern: MetaPattern, names: string[]): void => {
|
|
206
|
+
switch (pattern.type) {
|
|
207
|
+
case "Capture":
|
|
208
|
+
if (!names.includes(pattern.pattern.name)) {
|
|
209
|
+
names.push(pattern.pattern.name);
|
|
210
|
+
}
|
|
211
|
+
collectPatternCaptureNames(pattern.pattern.pattern, names);
|
|
212
|
+
break;
|
|
213
|
+
case "And":
|
|
214
|
+
for (const p of pattern.pattern.patterns) {
|
|
215
|
+
collectPatternCaptureNames(p, names);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case "Or":
|
|
219
|
+
for (const p of pattern.pattern.patterns) {
|
|
220
|
+
collectPatternCaptureNames(p, names);
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
case "Not":
|
|
224
|
+
collectPatternCaptureNames(pattern.pattern.pattern, names);
|
|
225
|
+
break;
|
|
226
|
+
case "Repeat":
|
|
227
|
+
collectPatternCaptureNames(pattern.pattern.pattern, names);
|
|
228
|
+
break;
|
|
229
|
+
case "Search":
|
|
230
|
+
collectPatternCaptureNames(pattern.pattern.pattern, names);
|
|
231
|
+
break;
|
|
232
|
+
case "Sequence":
|
|
233
|
+
for (const p of pattern.pattern.patterns) {
|
|
234
|
+
collectPatternCaptureNames(p, names);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case "Any":
|
|
238
|
+
// Any patterns don't have captures
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Compiles a pattern to VM bytecode.
|
|
245
|
+
*/
|
|
246
|
+
const compilePatternToCode = (
|
|
247
|
+
pattern: Pattern,
|
|
248
|
+
code: Instr[],
|
|
249
|
+
literals: Pattern[],
|
|
250
|
+
captureNames: string[],
|
|
251
|
+
): void => {
|
|
252
|
+
switch (pattern.kind) {
|
|
253
|
+
case "Value":
|
|
254
|
+
// Value patterns use MatchPredicate
|
|
255
|
+
literals.push(pattern);
|
|
256
|
+
code.push({ type: "MatchPredicate", literalIndex: literals.length - 1 });
|
|
257
|
+
break;
|
|
258
|
+
case "Structure":
|
|
259
|
+
// Structure patterns use MatchStructure
|
|
260
|
+
literals.push(pattern);
|
|
261
|
+
code.push({ type: "MatchStructure", literalIndex: literals.length - 1 });
|
|
262
|
+
break;
|
|
263
|
+
case "Meta":
|
|
264
|
+
compileMetaPattern(pattern.pattern, code, literals, captureNames);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Compiles meta patterns to VM bytecode.
|
|
271
|
+
*/
|
|
272
|
+
const compileMetaPattern = (
|
|
273
|
+
pattern: MetaPattern,
|
|
274
|
+
code: Instr[],
|
|
275
|
+
literals: Pattern[],
|
|
276
|
+
captureNames: string[],
|
|
277
|
+
): void => {
|
|
278
|
+
switch (pattern.type) {
|
|
279
|
+
case "Any":
|
|
280
|
+
// Any matches everything - compile as predicate
|
|
281
|
+
literals.push({ kind: "Meta", pattern });
|
|
282
|
+
code.push({ type: "MatchPredicate", literalIndex: literals.length - 1 });
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case "And": {
|
|
286
|
+
// All patterns must match
|
|
287
|
+
for (const p of pattern.pattern.patterns) {
|
|
288
|
+
compilePatternToCode(p, code, literals, captureNames);
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case "Or": {
|
|
294
|
+
// Use splits to try each alternative
|
|
295
|
+
const patterns = pattern.pattern.patterns;
|
|
296
|
+
if (patterns.length === 0) break;
|
|
297
|
+
if (patterns.length === 1) {
|
|
298
|
+
compilePatternToCode(patterns[0], code, literals, captureNames);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Create split chain for alternatives
|
|
303
|
+
const jumpAddrs: number[] = [];
|
|
304
|
+
for (let i = 0; i < patterns.length - 1; i++) {
|
|
305
|
+
const splitAddr = code.length;
|
|
306
|
+
code.push({ type: "Split", a: 0, b: 0 }); // Placeholder
|
|
307
|
+
|
|
308
|
+
// First alternative starts right after split
|
|
309
|
+
(code[splitAddr] as { type: "Split"; a: number; b: number }).a = code.length;
|
|
310
|
+
compilePatternToCode(patterns[i], code, literals, captureNames);
|
|
311
|
+
jumpAddrs.push(code.length);
|
|
312
|
+
code.push({ type: "Jump", address: 0 }); // Jump to end, placeholder
|
|
313
|
+
|
|
314
|
+
// Second alternative address
|
|
315
|
+
(code[splitAddr] as { type: "Split"; a: number; b: number }).b = code.length;
|
|
316
|
+
}
|
|
317
|
+
// Last pattern
|
|
318
|
+
compilePatternToCode(patterns[patterns.length - 1], code, literals, captureNames);
|
|
319
|
+
|
|
320
|
+
// Fix up jump addresses
|
|
321
|
+
const endAddr = code.length;
|
|
322
|
+
for (const addr of jumpAddrs) {
|
|
323
|
+
(code[addr] as { type: "Jump"; address: number }).address = endAddr;
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
case "Not": {
|
|
329
|
+
// Store the inner pattern and use NotMatch instruction
|
|
330
|
+
literals.push(pattern.pattern.pattern);
|
|
331
|
+
code.push({ type: "NotMatch", patternIndex: literals.length - 1 });
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
case "Repeat": {
|
|
336
|
+
literals.push(pattern.pattern.pattern);
|
|
337
|
+
code.push({
|
|
338
|
+
type: "Repeat",
|
|
339
|
+
patternIndex: literals.length - 1,
|
|
340
|
+
quantifier: pattern.pattern.quantifier,
|
|
341
|
+
});
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
case "Capture": {
|
|
346
|
+
const captureIndex = captureNames.indexOf(pattern.pattern.name);
|
|
347
|
+
code.push({ type: "CaptureStart", captureIndex });
|
|
348
|
+
compilePatternToCode(pattern.pattern.pattern, code, literals, captureNames);
|
|
349
|
+
code.push({ type: "CaptureEnd", captureIndex });
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
case "Search": {
|
|
354
|
+
// Build capture map for the search pattern
|
|
355
|
+
const captureMap: [string, number][] = [];
|
|
356
|
+
const innerNames: string[] = [];
|
|
357
|
+
collectPatternCaptureNames(pattern.pattern.pattern, innerNames);
|
|
358
|
+
for (const name of innerNames) {
|
|
359
|
+
const idx = captureNames.indexOf(name);
|
|
360
|
+
if (idx >= 0) {
|
|
361
|
+
captureMap.push([name, idx]);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
literals.push(pattern.pattern.pattern);
|
|
366
|
+
code.push({
|
|
367
|
+
type: "Search",
|
|
368
|
+
patternIndex: literals.length - 1,
|
|
369
|
+
captureMap,
|
|
370
|
+
});
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
case "Sequence": {
|
|
375
|
+
const patterns = pattern.pattern.patterns;
|
|
376
|
+
if (patterns.length === 0) break;
|
|
377
|
+
|
|
378
|
+
compilePatternToCode(patterns[0], code, literals, captureNames);
|
|
379
|
+
|
|
380
|
+
for (let i = 1; i < patterns.length; i++) {
|
|
381
|
+
code.push({ type: "ExtendSequence" });
|
|
382
|
+
compilePatternToCode(patterns[i], code, literals, captureNames);
|
|
383
|
+
code.push({ type: "CombineSequence" });
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* And pattern for dCBOR pattern matching.
|
|
3
|
+
* Matches if all contained patterns match.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/and-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
import { matchPattern } from "../match-registry";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A pattern that matches if all contained patterns match.
|
|
15
|
+
*/
|
|
16
|
+
export interface AndPattern {
|
|
17
|
+
readonly variant: "And";
|
|
18
|
+
readonly patterns: Pattern[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates an AndPattern with the given patterns.
|
|
23
|
+
*/
|
|
24
|
+
export const andPattern = (patterns: Pattern[]): AndPattern => ({
|
|
25
|
+
variant: "And",
|
|
26
|
+
patterns,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tests if a CBOR value matches this and pattern.
|
|
31
|
+
* All patterns must match.
|
|
32
|
+
*/
|
|
33
|
+
export const andPatternMatches = (pattern: AndPattern, haystack: Cbor): boolean => {
|
|
34
|
+
return pattern.patterns.every((p: Pattern) => matchPattern(p, haystack));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns paths to matching values.
|
|
39
|
+
*/
|
|
40
|
+
export const andPatternPaths = (pattern: AndPattern, haystack: Cbor): Path[] => {
|
|
41
|
+
if (andPatternMatches(pattern, haystack)) {
|
|
42
|
+
return [[haystack]];
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Formats an AndPattern as a string.
|
|
49
|
+
*/
|
|
50
|
+
export const andPatternDisplay = (
|
|
51
|
+
pattern: AndPattern,
|
|
52
|
+
patternDisplay: (p: Pattern) => string,
|
|
53
|
+
): string => {
|
|
54
|
+
const parts = pattern.patterns.map(patternDisplay);
|
|
55
|
+
return parts.join(" & ");
|
|
56
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Any pattern for dCBOR pattern matching.
|
|
3
|
+
* Always matches any CBOR value.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/any-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A pattern that always matches any CBOR value.
|
|
13
|
+
*/
|
|
14
|
+
export interface AnyPattern {
|
|
15
|
+
readonly variant: "Any";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates an AnyPattern.
|
|
20
|
+
*/
|
|
21
|
+
export const anyPattern = (): AnyPattern => ({ variant: "Any" });
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Tests if a CBOR value matches this any pattern.
|
|
25
|
+
* Always returns true.
|
|
26
|
+
*/
|
|
27
|
+
export const anyPatternMatches = (_pattern: AnyPattern, _haystack: Cbor): boolean => {
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns paths to matching values.
|
|
33
|
+
*/
|
|
34
|
+
export const anyPatternPaths = (_pattern: AnyPattern, haystack: Cbor): Path[] => {
|
|
35
|
+
return [[haystack]];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Formats an AnyPattern as a string.
|
|
40
|
+
*/
|
|
41
|
+
export const anyPatternDisplay = (_pattern: AnyPattern): string => {
|
|
42
|
+
return "*";
|
|
43
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture pattern for dCBOR pattern matching.
|
|
3
|
+
* Captures matched values with a name.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/capture-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
import { matchPattern } from "../match-registry";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A pattern that captures matched values with a name.
|
|
15
|
+
*/
|
|
16
|
+
export interface CapturePattern {
|
|
17
|
+
readonly variant: "Capture";
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly pattern: Pattern;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a CapturePattern with the given name and inner pattern.
|
|
24
|
+
*/
|
|
25
|
+
export const capturePattern = (name: string, pattern: Pattern): CapturePattern => ({
|
|
26
|
+
variant: "Capture",
|
|
27
|
+
name,
|
|
28
|
+
pattern,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tests if a CBOR value matches this capture pattern.
|
|
33
|
+
* Capture itself doesn't affect matching - it delegates to inner pattern.
|
|
34
|
+
*/
|
|
35
|
+
export const capturePatternMatches = (pattern: CapturePattern, haystack: Cbor): boolean => {
|
|
36
|
+
return matchPattern(pattern.pattern, haystack);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns paths to matching values.
|
|
41
|
+
*/
|
|
42
|
+
export const capturePatternPaths = (pattern: CapturePattern, haystack: Cbor): Path[] => {
|
|
43
|
+
if (capturePatternMatches(pattern, haystack)) {
|
|
44
|
+
return [[haystack]];
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Formats a CapturePattern as a string.
|
|
51
|
+
*/
|
|
52
|
+
export const capturePatternDisplay = (
|
|
53
|
+
pattern: CapturePattern,
|
|
54
|
+
patternDisplay: (p: Pattern) => string,
|
|
55
|
+
): string => {
|
|
56
|
+
return `@${pattern.name}(${patternDisplay(pattern.pattern)})`;
|
|
57
|
+
};
|