@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.
Files changed (53) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +13 -0
  3. package/dist/index.cjs +6781 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +2628 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +2628 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +6781 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +6545 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +77 -0
  14. package/src/error.ts +262 -0
  15. package/src/format.ts +375 -0
  16. package/src/index.ts +27 -0
  17. package/src/parse/index.ts +923 -0
  18. package/src/parse/token.ts +906 -0
  19. package/src/parse/utils.ts +339 -0
  20. package/src/pattern/index.ts +719 -0
  21. package/src/pattern/leaf/array-pattern.ts +273 -0
  22. package/src/pattern/leaf/bool-pattern.ts +140 -0
  23. package/src/pattern/leaf/byte-string-pattern.ts +172 -0
  24. package/src/pattern/leaf/cbor-pattern.ts +355 -0
  25. package/src/pattern/leaf/date-pattern.ts +178 -0
  26. package/src/pattern/leaf/index.ts +280 -0
  27. package/src/pattern/leaf/known-value-pattern.ts +192 -0
  28. package/src/pattern/leaf/map-pattern.ts +152 -0
  29. package/src/pattern/leaf/null-pattern.ts +110 -0
  30. package/src/pattern/leaf/number-pattern.ts +248 -0
  31. package/src/pattern/leaf/tagged-pattern.ts +228 -0
  32. package/src/pattern/leaf/text-pattern.ts +165 -0
  33. package/src/pattern/matcher.ts +88 -0
  34. package/src/pattern/meta/and-pattern.ts +109 -0
  35. package/src/pattern/meta/any-pattern.ts +81 -0
  36. package/src/pattern/meta/capture-pattern.ts +111 -0
  37. package/src/pattern/meta/group-pattern.ts +110 -0
  38. package/src/pattern/meta/index.ts +269 -0
  39. package/src/pattern/meta/not-pattern.ts +91 -0
  40. package/src/pattern/meta/or-pattern.ts +146 -0
  41. package/src/pattern/meta/search-pattern.ts +201 -0
  42. package/src/pattern/meta/traverse-pattern.ts +146 -0
  43. package/src/pattern/structure/assertions-pattern.ts +244 -0
  44. package/src/pattern/structure/digest-pattern.ts +225 -0
  45. package/src/pattern/structure/index.ts +272 -0
  46. package/src/pattern/structure/leaf-structure-pattern.ts +85 -0
  47. package/src/pattern/structure/node-pattern.ts +188 -0
  48. package/src/pattern/structure/object-pattern.ts +149 -0
  49. package/src/pattern/structure/obscured-pattern.ts +159 -0
  50. package/src/pattern/structure/predicate-pattern.ts +151 -0
  51. package/src/pattern/structure/subject-pattern.ts +152 -0
  52. package/src/pattern/structure/wrapped-pattern.ts +195 -0
  53. 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
+ }