@bcts/envelope-pattern 1.0.0-alpha.12
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 +13 -0
- package/dist/index.cjs +6781 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2628 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +2628 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +6781 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +6545 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
- package/src/error.ts +262 -0
- package/src/format.ts +375 -0
- package/src/index.ts +27 -0
- package/src/parse/index.ts +923 -0
- package/src/parse/token.ts +906 -0
- package/src/parse/utils.ts +339 -0
- package/src/pattern/index.ts +719 -0
- package/src/pattern/leaf/array-pattern.ts +273 -0
- package/src/pattern/leaf/bool-pattern.ts +140 -0
- package/src/pattern/leaf/byte-string-pattern.ts +172 -0
- package/src/pattern/leaf/cbor-pattern.ts +355 -0
- package/src/pattern/leaf/date-pattern.ts +178 -0
- package/src/pattern/leaf/index.ts +280 -0
- package/src/pattern/leaf/known-value-pattern.ts +192 -0
- package/src/pattern/leaf/map-pattern.ts +152 -0
- package/src/pattern/leaf/null-pattern.ts +110 -0
- package/src/pattern/leaf/number-pattern.ts +248 -0
- package/src/pattern/leaf/tagged-pattern.ts +228 -0
- package/src/pattern/leaf/text-pattern.ts +165 -0
- package/src/pattern/matcher.ts +88 -0
- package/src/pattern/meta/and-pattern.ts +109 -0
- package/src/pattern/meta/any-pattern.ts +81 -0
- package/src/pattern/meta/capture-pattern.ts +111 -0
- package/src/pattern/meta/group-pattern.ts +110 -0
- package/src/pattern/meta/index.ts +269 -0
- package/src/pattern/meta/not-pattern.ts +91 -0
- package/src/pattern/meta/or-pattern.ts +146 -0
- package/src/pattern/meta/search-pattern.ts +201 -0
- package/src/pattern/meta/traverse-pattern.ts +146 -0
- package/src/pattern/structure/assertions-pattern.ts +244 -0
- package/src/pattern/structure/digest-pattern.ts +225 -0
- package/src/pattern/structure/index.ts +272 -0
- package/src/pattern/structure/leaf-structure-pattern.ts +85 -0
- package/src/pattern/structure/node-pattern.ts +188 -0
- package/src/pattern/structure/object-pattern.ts +149 -0
- package/src/pattern/structure/obscured-pattern.ts +159 -0
- package/src/pattern/structure/predicate-pattern.ts +151 -0
- package/src/pattern/structure/subject-pattern.ts +152 -0
- package/src/pattern/structure/wrapped-pattern.ts +195 -0
- package/src/pattern/vm.ts +1021 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/envelope-pattern - Meta patterns module
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-envelope-pattern-rust pattern/meta/mod.rs
|
|
5
|
+
*
|
|
6
|
+
* @module envelope-pattern/pattern/meta
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Envelope } from "@bcts/envelope";
|
|
10
|
+
import type { Path } from "../../format";
|
|
11
|
+
import type { Instr } from "../vm";
|
|
12
|
+
import type { Pattern } from "../index";
|
|
13
|
+
|
|
14
|
+
// Re-export all meta pattern types
|
|
15
|
+
export { AnyPattern, registerAnyPatternFactory } from "./any-pattern";
|
|
16
|
+
export { AndPattern, registerAndPatternFactory } from "./and-pattern";
|
|
17
|
+
export { OrPattern, registerOrPatternFactory } from "./or-pattern";
|
|
18
|
+
export { NotPattern, registerNotPatternFactory } from "./not-pattern";
|
|
19
|
+
export { CapturePattern, registerCapturePatternFactory } from "./capture-pattern";
|
|
20
|
+
export { SearchPattern, registerSearchPatternFactory } from "./search-pattern";
|
|
21
|
+
export { TraversePattern, registerTraversePatternFactory } from "./traverse-pattern";
|
|
22
|
+
export { GroupPattern, registerGroupPatternFactory } from "./group-pattern";
|
|
23
|
+
|
|
24
|
+
// Import concrete types for use in MetaPattern
|
|
25
|
+
import { type AnyPattern } from "./any-pattern";
|
|
26
|
+
import { type AndPattern } from "./and-pattern";
|
|
27
|
+
import { type OrPattern } from "./or-pattern";
|
|
28
|
+
import { type NotPattern } from "./not-pattern";
|
|
29
|
+
import { type CapturePattern } from "./capture-pattern";
|
|
30
|
+
import { type SearchPattern } from "./search-pattern";
|
|
31
|
+
import { type TraversePattern } from "./traverse-pattern";
|
|
32
|
+
import { type GroupPattern } from "./group-pattern";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Union type for all meta patterns.
|
|
36
|
+
*
|
|
37
|
+
* Corresponds to the Rust `MetaPattern` enum in pattern/meta/mod.rs
|
|
38
|
+
*/
|
|
39
|
+
export type MetaPattern =
|
|
40
|
+
| { readonly type: "Any"; readonly pattern: AnyPattern }
|
|
41
|
+
| { readonly type: "And"; readonly pattern: AndPattern }
|
|
42
|
+
| { readonly type: "Or"; readonly pattern: OrPattern }
|
|
43
|
+
| { readonly type: "Not"; readonly pattern: NotPattern }
|
|
44
|
+
| { readonly type: "Capture"; readonly pattern: CapturePattern }
|
|
45
|
+
| { readonly type: "Search"; readonly pattern: SearchPattern }
|
|
46
|
+
| { readonly type: "Traverse"; readonly pattern: TraversePattern }
|
|
47
|
+
| { readonly type: "Group"; readonly pattern: GroupPattern };
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates an Any meta pattern.
|
|
51
|
+
*/
|
|
52
|
+
export function metaAny(pattern: AnyPattern): MetaPattern {
|
|
53
|
+
return { type: "Any", pattern };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates an And meta pattern.
|
|
58
|
+
*/
|
|
59
|
+
export function metaAnd(pattern: AndPattern): MetaPattern {
|
|
60
|
+
return { type: "And", pattern };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates an Or meta pattern.
|
|
65
|
+
*/
|
|
66
|
+
export function metaOr(pattern: OrPattern): MetaPattern {
|
|
67
|
+
return { type: "Or", pattern };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates a Not meta pattern.
|
|
72
|
+
*/
|
|
73
|
+
export function metaNot(pattern: NotPattern): MetaPattern {
|
|
74
|
+
return { type: "Not", pattern };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Creates a Capture meta pattern.
|
|
79
|
+
*/
|
|
80
|
+
export function metaCapture(pattern: CapturePattern): MetaPattern {
|
|
81
|
+
return { type: "Capture", pattern };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates a Search meta pattern.
|
|
86
|
+
*/
|
|
87
|
+
export function metaSearch(pattern: SearchPattern): MetaPattern {
|
|
88
|
+
return { type: "Search", pattern };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Creates a Traverse meta pattern.
|
|
93
|
+
*/
|
|
94
|
+
export function metaTraverse(pattern: TraversePattern): MetaPattern {
|
|
95
|
+
return { type: "Traverse", pattern };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Creates a Group meta pattern.
|
|
100
|
+
*/
|
|
101
|
+
export function metaGroup(pattern: GroupPattern): MetaPattern {
|
|
102
|
+
return { type: "Group", pattern };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Gets paths with captures for a meta pattern.
|
|
107
|
+
*/
|
|
108
|
+
export function metaPatternPathsWithCaptures(
|
|
109
|
+
pattern: MetaPattern,
|
|
110
|
+
haystack: Envelope,
|
|
111
|
+
): [Path[], Map<string, Path[]>] {
|
|
112
|
+
switch (pattern.type) {
|
|
113
|
+
case "Any":
|
|
114
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
115
|
+
case "And":
|
|
116
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
117
|
+
case "Or":
|
|
118
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
119
|
+
case "Not":
|
|
120
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
121
|
+
case "Capture":
|
|
122
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
123
|
+
case "Search":
|
|
124
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
125
|
+
case "Traverse":
|
|
126
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
127
|
+
case "Group":
|
|
128
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Compiles a meta pattern to bytecode.
|
|
134
|
+
*/
|
|
135
|
+
export function metaPatternCompile(
|
|
136
|
+
pattern: MetaPattern,
|
|
137
|
+
code: Instr[],
|
|
138
|
+
literals: Pattern[],
|
|
139
|
+
captures: string[],
|
|
140
|
+
): void {
|
|
141
|
+
switch (pattern.type) {
|
|
142
|
+
case "Any":
|
|
143
|
+
pattern.pattern.compile(code, literals, captures);
|
|
144
|
+
break;
|
|
145
|
+
case "And":
|
|
146
|
+
pattern.pattern.compile(code, literals, captures);
|
|
147
|
+
break;
|
|
148
|
+
case "Or":
|
|
149
|
+
pattern.pattern.compile(code, literals, captures);
|
|
150
|
+
break;
|
|
151
|
+
case "Not":
|
|
152
|
+
pattern.pattern.compile(code, literals, captures);
|
|
153
|
+
break;
|
|
154
|
+
case "Capture":
|
|
155
|
+
pattern.pattern.compile(code, literals, captures);
|
|
156
|
+
break;
|
|
157
|
+
case "Search":
|
|
158
|
+
pattern.pattern.compile(code, literals, captures);
|
|
159
|
+
break;
|
|
160
|
+
case "Traverse":
|
|
161
|
+
pattern.pattern.compile(code, literals, captures);
|
|
162
|
+
break;
|
|
163
|
+
case "Group":
|
|
164
|
+
pattern.pattern.compile(code, literals, captures);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Checks if a meta pattern is complex.
|
|
171
|
+
*/
|
|
172
|
+
export function metaPatternIsComplex(pattern: MetaPattern): boolean {
|
|
173
|
+
switch (pattern.type) {
|
|
174
|
+
case "Any":
|
|
175
|
+
return pattern.pattern.isComplex();
|
|
176
|
+
case "And":
|
|
177
|
+
return pattern.pattern.isComplex();
|
|
178
|
+
case "Or":
|
|
179
|
+
return pattern.pattern.isComplex();
|
|
180
|
+
case "Not":
|
|
181
|
+
return pattern.pattern.isComplex();
|
|
182
|
+
case "Capture":
|
|
183
|
+
return pattern.pattern.isComplex();
|
|
184
|
+
case "Search":
|
|
185
|
+
return pattern.pattern.isComplex();
|
|
186
|
+
case "Traverse":
|
|
187
|
+
return pattern.pattern.isComplex();
|
|
188
|
+
case "Group":
|
|
189
|
+
return pattern.pattern.isComplex();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Converts a meta pattern to string.
|
|
195
|
+
*/
|
|
196
|
+
export function metaPatternToString(pattern: MetaPattern): string {
|
|
197
|
+
switch (pattern.type) {
|
|
198
|
+
case "Any":
|
|
199
|
+
return pattern.pattern.toString();
|
|
200
|
+
case "And":
|
|
201
|
+
return pattern.pattern.toString();
|
|
202
|
+
case "Or":
|
|
203
|
+
return pattern.pattern.toString();
|
|
204
|
+
case "Not":
|
|
205
|
+
return pattern.pattern.toString();
|
|
206
|
+
case "Capture":
|
|
207
|
+
return pattern.pattern.toString();
|
|
208
|
+
case "Search":
|
|
209
|
+
return pattern.pattern.toString();
|
|
210
|
+
case "Traverse":
|
|
211
|
+
return pattern.pattern.toString();
|
|
212
|
+
case "Group":
|
|
213
|
+
return pattern.pattern.toString();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Collects capture names from a meta pattern.
|
|
219
|
+
*/
|
|
220
|
+
export function metaPatternCollectCaptureNames(pattern: MetaPattern, out: string[]): void {
|
|
221
|
+
switch (pattern.type) {
|
|
222
|
+
case "Any":
|
|
223
|
+
// No captures
|
|
224
|
+
break;
|
|
225
|
+
case "And":
|
|
226
|
+
for (const pat of pattern.pattern.patterns()) {
|
|
227
|
+
collectCaptureNamesFromPattern(pat, out);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
case "Or":
|
|
231
|
+
for (const pat of pattern.pattern.patterns()) {
|
|
232
|
+
collectCaptureNamesFromPattern(pat, out);
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
case "Not":
|
|
236
|
+
collectCaptureNamesFromPattern(pattern.pattern.pattern(), out);
|
|
237
|
+
break;
|
|
238
|
+
case "Capture": {
|
|
239
|
+
const name = pattern.pattern.name();
|
|
240
|
+
if (!out.includes(name)) {
|
|
241
|
+
out.push(name);
|
|
242
|
+
}
|
|
243
|
+
collectCaptureNamesFromPattern(pattern.pattern.pattern(), out);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case "Search":
|
|
247
|
+
collectCaptureNamesFromPattern(pattern.pattern.pattern(), out);
|
|
248
|
+
break;
|
|
249
|
+
case "Traverse":
|
|
250
|
+
for (const pat of pattern.pattern.patterns()) {
|
|
251
|
+
collectCaptureNamesFromPattern(pat, out);
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
case "Group":
|
|
255
|
+
collectCaptureNamesFromPattern(pattern.pattern.pattern(), out);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Helper to collect capture names from any Pattern.
|
|
262
|
+
*/
|
|
263
|
+
function collectCaptureNamesFromPattern(pattern: Pattern, out: string[]): void {
|
|
264
|
+
// This will be properly implemented when Pattern type is fully defined
|
|
265
|
+
const p = pattern as unknown as { collectCaptureNames?: (out: string[]) => void };
|
|
266
|
+
if (p.collectCaptureNames !== undefined) {
|
|
267
|
+
p.collectCaptureNames(out);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/envelope-pattern - Not pattern matching
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-envelope-pattern-rust not_pattern.rs
|
|
5
|
+
*
|
|
6
|
+
* @module envelope-pattern/pattern/meta/not-pattern
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Envelope } from "@bcts/envelope";
|
|
10
|
+
import type { Path } from "../../format";
|
|
11
|
+
import type { Matcher } from "../matcher";
|
|
12
|
+
import type { Instr } from "../vm";
|
|
13
|
+
import type { Pattern } from "../index";
|
|
14
|
+
|
|
15
|
+
// Forward declaration for Pattern factory (used for late binding)
|
|
16
|
+
export let createMetaNotPattern: ((pattern: NotPattern) => Pattern) | undefined;
|
|
17
|
+
|
|
18
|
+
export function registerNotPatternFactory(factory: (pattern: NotPattern) => Pattern): void {
|
|
19
|
+
createMetaNotPattern = factory;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A pattern that negates another pattern; matches when the inner pattern does not match.
|
|
24
|
+
*
|
|
25
|
+
* Corresponds to the Rust `NotPattern` struct in not_pattern.rs
|
|
26
|
+
*/
|
|
27
|
+
export class NotPattern implements Matcher {
|
|
28
|
+
readonly #pattern: Pattern;
|
|
29
|
+
|
|
30
|
+
private constructor(pattern: Pattern) {
|
|
31
|
+
this.#pattern = pattern;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new NotPattern with the given pattern.
|
|
36
|
+
*/
|
|
37
|
+
static new(pattern: Pattern): NotPattern {
|
|
38
|
+
return new NotPattern(pattern);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets the inner pattern.
|
|
43
|
+
*/
|
|
44
|
+
pattern(): Pattern {
|
|
45
|
+
return this.#pattern;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
49
|
+
// If the inner pattern doesn't match, then we return the current envelope as a match
|
|
50
|
+
const matcher = this.#pattern as unknown as Matcher;
|
|
51
|
+
const paths = !matcher.matches(haystack) ? [[haystack]] : [];
|
|
52
|
+
return [paths, new Map<string, Path[]>()];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
paths(haystack: Envelope): Path[] {
|
|
56
|
+
return this.pathsWithCaptures(haystack)[0];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
matches(haystack: Envelope): boolean {
|
|
60
|
+
return this.paths(haystack).length > 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
compile(code: Instr[], literals: Pattern[], _captures: string[]): void {
|
|
64
|
+
// NOT = check that pattern doesn't match
|
|
65
|
+
const idx = literals.length;
|
|
66
|
+
literals.push(this.#pattern);
|
|
67
|
+
code.push({ type: "NotMatch", patternIndex: idx });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isComplex(): boolean {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
toString(): string {
|
|
75
|
+
return `!${(this.#pattern as unknown as { toString(): string }).toString()}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Equality comparison.
|
|
80
|
+
*/
|
|
81
|
+
equals(other: NotPattern): boolean {
|
|
82
|
+
return this.#pattern === other.#pattern;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Hash code for use in Maps/Sets.
|
|
87
|
+
*/
|
|
88
|
+
hashCode(): number {
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/envelope-pattern - Or pattern matching
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-envelope-pattern-rust or_pattern.rs
|
|
5
|
+
*
|
|
6
|
+
* @module envelope-pattern/pattern/meta/or-pattern
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Envelope } from "@bcts/envelope";
|
|
10
|
+
import type { Path } from "../../format";
|
|
11
|
+
import type { Matcher } from "../matcher";
|
|
12
|
+
import type { Instr } from "../vm";
|
|
13
|
+
import type { Pattern } from "../index";
|
|
14
|
+
|
|
15
|
+
// Forward declaration for Pattern factory (used for late binding)
|
|
16
|
+
export let createMetaOrPattern: ((pattern: OrPattern) => Pattern) | undefined;
|
|
17
|
+
|
|
18
|
+
export function registerOrPatternFactory(factory: (pattern: OrPattern) => Pattern): void {
|
|
19
|
+
createMetaOrPattern = factory;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A pattern that matches if any contained pattern matches.
|
|
24
|
+
*
|
|
25
|
+
* Corresponds to the Rust `OrPattern` struct in or_pattern.rs
|
|
26
|
+
*/
|
|
27
|
+
export class OrPattern implements Matcher {
|
|
28
|
+
readonly #patterns: Pattern[];
|
|
29
|
+
|
|
30
|
+
private constructor(patterns: Pattern[]) {
|
|
31
|
+
this.#patterns = patterns;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new OrPattern with the given patterns.
|
|
36
|
+
*/
|
|
37
|
+
static new(patterns: Pattern[]): OrPattern {
|
|
38
|
+
return new OrPattern(patterns);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets the patterns.
|
|
43
|
+
*/
|
|
44
|
+
patterns(): Pattern[] {
|
|
45
|
+
return this.#patterns;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
49
|
+
const anyMatch = this.#patterns.some((pattern) => {
|
|
50
|
+
const matcher = pattern as unknown as Matcher;
|
|
51
|
+
return matcher.matches(haystack);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const paths = anyMatch ? [[haystack]] : [];
|
|
55
|
+
return [paths, new Map<string, Path[]>()];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
paths(haystack: Envelope): Path[] {
|
|
59
|
+
return this.pathsWithCaptures(haystack)[0];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
matches(haystack: Envelope): boolean {
|
|
63
|
+
return this.paths(haystack).length > 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
compile(code: Instr[], literals: Pattern[], captures: string[]): void {
|
|
67
|
+
if (this.#patterns.length === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// For N patterns: Split(p1, Split(p2, ... Split(pN-1, pN)))
|
|
72
|
+
const splits: number[] = [];
|
|
73
|
+
|
|
74
|
+
// Generate splits for all but the last pattern
|
|
75
|
+
for (let i = 0; i < this.#patterns.length - 1; i++) {
|
|
76
|
+
splits.push(code.length);
|
|
77
|
+
code.push({ type: "Split", a: 0, b: 0 }); // Placeholder
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Track jump instructions that need patching
|
|
81
|
+
const jumps: number[] = [];
|
|
82
|
+
|
|
83
|
+
// Now fill in the actual split targets
|
|
84
|
+
for (let i = 0; i < this.#patterns.length; i++) {
|
|
85
|
+
const patternStart = code.length;
|
|
86
|
+
|
|
87
|
+
// Compile this pattern
|
|
88
|
+
const pattern = this.#patterns[i];
|
|
89
|
+
const matcher = pattern as unknown as Matcher;
|
|
90
|
+
matcher.compile(code, literals, captures);
|
|
91
|
+
|
|
92
|
+
// This pattern will jump to the end if it matches
|
|
93
|
+
const jumpPastAll = code.length;
|
|
94
|
+
code.push({ type: "Jump", address: 0 }); // Placeholder
|
|
95
|
+
jumps.push(jumpPastAll);
|
|
96
|
+
|
|
97
|
+
// If there's a next pattern, update the split to point here
|
|
98
|
+
if (i < this.#patterns.length - 1) {
|
|
99
|
+
const nextPattern = code.length;
|
|
100
|
+
code[splits[i]] = { type: "Split", a: patternStart, b: nextPattern };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Now patch all the jumps to point past all the patterns
|
|
105
|
+
const pastAll = code.length;
|
|
106
|
+
for (const jumpIdx of jumps) {
|
|
107
|
+
code[jumpIdx] = { type: "Jump", address: pastAll };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
isComplex(): boolean {
|
|
112
|
+
// The pattern is complex if it contains more than one pattern, or if
|
|
113
|
+
// the one pattern is complex itself.
|
|
114
|
+
return (
|
|
115
|
+
this.#patterns.length > 1 || this.#patterns.some((p) => (p as unknown as Matcher).isComplex())
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
toString(): string {
|
|
120
|
+
return this.#patterns
|
|
121
|
+
.map((p) => (p as unknown as { toString(): string }).toString())
|
|
122
|
+
.join(" | ");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Equality comparison.
|
|
127
|
+
*/
|
|
128
|
+
equals(other: OrPattern): boolean {
|
|
129
|
+
if (this.#patterns.length !== other.#patterns.length) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
for (let i = 0; i < this.#patterns.length; i++) {
|
|
133
|
+
if (this.#patterns[i] !== other.#patterns[i]) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Hash code for use in Maps/Sets.
|
|
142
|
+
*/
|
|
143
|
+
hashCode(): number {
|
|
144
|
+
return this.#patterns.length;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/envelope-pattern - Search pattern matching
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-envelope-pattern-rust search_pattern.rs
|
|
5
|
+
*
|
|
6
|
+
* @module envelope-pattern/pattern/meta/search-pattern
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Envelope } from "@bcts/envelope";
|
|
10
|
+
import type { Path } from "../../format";
|
|
11
|
+
import type { Matcher } from "../matcher";
|
|
12
|
+
import type { Instr } from "../vm";
|
|
13
|
+
import type { Pattern } from "../index";
|
|
14
|
+
|
|
15
|
+
// Forward declaration for Pattern factory (used for late binding)
|
|
16
|
+
export let createMetaSearchPattern: ((pattern: SearchPattern) => Pattern) | undefined;
|
|
17
|
+
|
|
18
|
+
export function registerSearchPatternFactory(factory: (pattern: SearchPattern) => Pattern): void {
|
|
19
|
+
createMetaSearchPattern = factory;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A pattern that searches the entire envelope tree for matches.
|
|
24
|
+
*
|
|
25
|
+
* Corresponds to the Rust `SearchPattern` struct in search_pattern.rs
|
|
26
|
+
*/
|
|
27
|
+
export class SearchPattern implements Matcher {
|
|
28
|
+
readonly #pattern: Pattern;
|
|
29
|
+
|
|
30
|
+
private constructor(pattern: Pattern) {
|
|
31
|
+
this.#pattern = pattern;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new SearchPattern with the given pattern.
|
|
36
|
+
*/
|
|
37
|
+
static new(pattern: Pattern): SearchPattern {
|
|
38
|
+
return new SearchPattern(pattern);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets the inner pattern.
|
|
43
|
+
*/
|
|
44
|
+
pattern(): Pattern {
|
|
45
|
+
return this.#pattern;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
49
|
+
const resultPaths: Path[] = [];
|
|
50
|
+
const matcher = this.#pattern as unknown as Matcher;
|
|
51
|
+
|
|
52
|
+
// Walk the envelope tree
|
|
53
|
+
this.#walkEnvelope(haystack, [], (currentEnvelope, pathToCurrent) => {
|
|
54
|
+
// Create the path to this node
|
|
55
|
+
const newPath: Envelope[] = [...pathToCurrent, currentEnvelope];
|
|
56
|
+
|
|
57
|
+
// Test the pattern against this node
|
|
58
|
+
const patternPaths = matcher.paths(currentEnvelope);
|
|
59
|
+
|
|
60
|
+
// If the pattern matches, emit the full paths
|
|
61
|
+
for (const patternPath of patternPaths) {
|
|
62
|
+
const fullPath = [...newPath];
|
|
63
|
+
// If the pattern path has elements beyond just the current envelope,
|
|
64
|
+
// extend with those additional elements.
|
|
65
|
+
if (patternPath.length > 1) {
|
|
66
|
+
fullPath.push(...patternPath.slice(1));
|
|
67
|
+
} else if (patternPath.length === 1) {
|
|
68
|
+
const firstEnv = patternPath[0];
|
|
69
|
+
if (firstEnv !== undefined && !firstEnv.digest().equals(currentEnvelope.digest())) {
|
|
70
|
+
// Pattern found a different element, add it to the path
|
|
71
|
+
fullPath.push(...patternPath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
resultPaths.push(fullPath);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Deduplicate paths by digest
|
|
79
|
+
const seen = new Set<string>();
|
|
80
|
+
const uniquePaths: Path[] = [];
|
|
81
|
+
for (const path of resultPaths) {
|
|
82
|
+
const digestPath = path.map((e) => e.digest().hex()).join(",");
|
|
83
|
+
if (!seen.has(digestPath)) {
|
|
84
|
+
seen.add(digestPath);
|
|
85
|
+
uniquePaths.push(path);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [uniquePaths, new Map<string, Path[]>()];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Walk the envelope tree recursively.
|
|
94
|
+
*/
|
|
95
|
+
#walkEnvelope(
|
|
96
|
+
envelope: Envelope,
|
|
97
|
+
pathToCurrent: Envelope[],
|
|
98
|
+
visitor: (envelope: Envelope, path: Envelope[]) => void,
|
|
99
|
+
): void {
|
|
100
|
+
// Visit this node
|
|
101
|
+
visitor(envelope, pathToCurrent);
|
|
102
|
+
|
|
103
|
+
// Get the subject
|
|
104
|
+
const subject = envelope.subject();
|
|
105
|
+
const newPath = [...pathToCurrent, envelope];
|
|
106
|
+
|
|
107
|
+
// Walk subject if it's different from this envelope
|
|
108
|
+
if (!subject.digest().equals(envelope.digest())) {
|
|
109
|
+
this.#walkEnvelope(subject, newPath, visitor);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Walk assertions
|
|
113
|
+
for (const assertion of envelope.assertions()) {
|
|
114
|
+
this.#walkEnvelope(assertion, newPath, visitor);
|
|
115
|
+
|
|
116
|
+
// Walk predicate and object if available
|
|
117
|
+
const predicate = assertion.asPredicate?.();
|
|
118
|
+
if (predicate !== undefined) {
|
|
119
|
+
const assertionPath = [...newPath, assertion];
|
|
120
|
+
this.#walkEnvelope(predicate, assertionPath, visitor);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const object = assertion.asObject?.();
|
|
124
|
+
if (object !== undefined) {
|
|
125
|
+
const assertionPath = [...newPath, assertion];
|
|
126
|
+
this.#walkEnvelope(object, assertionPath, visitor);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Walk wrapped content if present
|
|
131
|
+
if (subject.isWrapped()) {
|
|
132
|
+
const unwrapped = subject.tryUnwrap?.();
|
|
133
|
+
if (unwrapped !== undefined) {
|
|
134
|
+
this.#walkEnvelope(unwrapped, newPath, visitor);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
paths(haystack: Envelope): Path[] {
|
|
140
|
+
return this.pathsWithCaptures(haystack)[0];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
matches(haystack: Envelope): boolean {
|
|
144
|
+
return this.paths(haystack).length > 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
compile(code: Instr[], literals: Pattern[], captures: string[]): void {
|
|
148
|
+
const idx = literals.length;
|
|
149
|
+
literals.push(this.#pattern);
|
|
150
|
+
|
|
151
|
+
// Collect capture names from inner pattern
|
|
152
|
+
const innerNames: string[] = [];
|
|
153
|
+
collectCaptureNames(this.#pattern, innerNames);
|
|
154
|
+
|
|
155
|
+
const captureMap: [string, number][] = [];
|
|
156
|
+
for (const name of innerNames) {
|
|
157
|
+
let pos = captures.indexOf(name);
|
|
158
|
+
if (pos === -1) {
|
|
159
|
+
pos = captures.length;
|
|
160
|
+
captures.push(name);
|
|
161
|
+
}
|
|
162
|
+
captureMap.push([name, pos]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
code.push({ type: "Search", patternIndex: idx, captureMap });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
isComplex(): boolean {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
toString(): string {
|
|
173
|
+
return `search(${(this.#pattern as unknown as { toString(): string }).toString()})`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Equality comparison.
|
|
178
|
+
*/
|
|
179
|
+
equals(other: SearchPattern): boolean {
|
|
180
|
+
return this.#pattern === other.#pattern;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Hash code for use in Maps/Sets.
|
|
185
|
+
*/
|
|
186
|
+
hashCode(): number {
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Collect capture names from a pattern.
|
|
193
|
+
*/
|
|
194
|
+
function collectCaptureNames(pattern: Pattern, out: string[]): void {
|
|
195
|
+
// This will be properly implemented when Pattern type is fully defined
|
|
196
|
+
// For now, we check if it has a collectCaptureNames method
|
|
197
|
+
const p = pattern as unknown as { collectCaptureNames?: (out: string[]) => void };
|
|
198
|
+
if (p.collectCaptureNames !== undefined) {
|
|
199
|
+
p.collectCaptureNames(out);
|
|
200
|
+
}
|
|
201
|
+
}
|