@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,165 @@
1
+ /**
2
+ * @bcts/envelope-pattern - Text pattern matching
3
+ *
4
+ * This is a 1:1 TypeScript port of bc-envelope-pattern-rust text_pattern.rs
5
+ *
6
+ * @module envelope-pattern/pattern/leaf/text-pattern
7
+ */
8
+
9
+ import type { Envelope } from "@bcts/envelope";
10
+ import {
11
+ type TextPattern as DCBORTextPattern,
12
+ textPatternAny,
13
+ textPatternValue,
14
+ textPatternRegex,
15
+ textPatternPaths as dcborTextPatternPaths,
16
+ textPatternDisplay,
17
+ } from "@bcts/dcbor-pattern";
18
+ import type { Path } from "../../format";
19
+ import type { Matcher } from "../matcher";
20
+ import { compileAsAtomic } from "../matcher";
21
+ import type { Instr } from "../vm";
22
+ import type { Pattern } from "../index";
23
+
24
+ // Forward declaration for Pattern factory
25
+ let createLeafTextPattern: ((pattern: TextPattern) => Pattern) | undefined;
26
+
27
+ export function registerTextPatternFactory(factory: (pattern: TextPattern) => Pattern): void {
28
+ createLeafTextPattern = factory;
29
+ }
30
+
31
+ /**
32
+ * Pattern for matching text values.
33
+ *
34
+ * This is a wrapper around dcbor_pattern::TextPattern that provides
35
+ * envelope-specific integration.
36
+ *
37
+ * Corresponds to the Rust `TextPattern` struct in text_pattern.rs
38
+ */
39
+ export class TextPattern implements Matcher {
40
+ readonly #inner: DCBORTextPattern;
41
+
42
+ private constructor(inner: DCBORTextPattern) {
43
+ this.#inner = inner;
44
+ }
45
+
46
+ /**
47
+ * Creates a new TextPattern that matches any text.
48
+ */
49
+ static any(): TextPattern {
50
+ return new TextPattern(textPatternAny());
51
+ }
52
+
53
+ /**
54
+ * Creates a new TextPattern that matches the specific text.
55
+ */
56
+ static value(value: string): TextPattern {
57
+ return new TextPattern(textPatternValue(value));
58
+ }
59
+
60
+ /**
61
+ * Creates a new TextPattern that matches text matching the regex.
62
+ */
63
+ static regex(pattern: RegExp): TextPattern {
64
+ return new TextPattern(textPatternRegex(pattern));
65
+ }
66
+
67
+ /**
68
+ * Creates a new TextPattern from a dcbor-pattern TextPattern.
69
+ */
70
+ static fromDcborPattern(dcborPattern: DCBORTextPattern): TextPattern {
71
+ return new TextPattern(dcborPattern);
72
+ }
73
+
74
+ /**
75
+ * Gets the underlying dcbor-pattern TextPattern.
76
+ */
77
+ get inner(): DCBORTextPattern {
78
+ return this.#inner;
79
+ }
80
+
81
+ pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
82
+ // For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
83
+ const cbor = haystack.asLeaf();
84
+ if (cbor !== undefined) {
85
+ // Delegate to dcbor-pattern for CBOR matching
86
+ const dcborPaths = dcborTextPatternPaths(this.#inner, cbor);
87
+
88
+ // For simple leaf patterns, if dcbor-pattern found matches, return the envelope
89
+ if (dcborPaths.length > 0) {
90
+ const envelopePaths: Path[] = [[haystack]];
91
+ return [envelopePaths, new Map<string, Path[]>()];
92
+ }
93
+ }
94
+
95
+ return [[], new Map<string, Path[]>()];
96
+ }
97
+
98
+ paths(haystack: Envelope): Path[] {
99
+ return this.pathsWithCaptures(haystack)[0];
100
+ }
101
+
102
+ matches(haystack: Envelope): boolean {
103
+ return this.paths(haystack).length > 0;
104
+ }
105
+
106
+ compile(code: Instr[], literals: Pattern[], captures: string[]): void {
107
+ if (createLeafTextPattern === undefined) {
108
+ throw new Error("TextPattern factory not registered");
109
+ }
110
+ compileAsAtomic(createLeafTextPattern(this), code, literals, captures);
111
+ }
112
+
113
+ isComplex(): boolean {
114
+ return false;
115
+ }
116
+
117
+ toString(): string {
118
+ return textPatternDisplay(this.#inner);
119
+ }
120
+
121
+ /**
122
+ * Equality comparison.
123
+ */
124
+ equals(other: TextPattern): boolean {
125
+ if (this.#inner.variant !== other.#inner.variant) {
126
+ return false;
127
+ }
128
+ switch (this.#inner.variant) {
129
+ case "Any":
130
+ return true;
131
+ case "Value":
132
+ return (
133
+ (this.#inner as { value: string }).value === (other.#inner as { value: string }).value
134
+ );
135
+ case "Regex":
136
+ return (
137
+ (this.#inner as { pattern: RegExp }).pattern.source ===
138
+ (other.#inner as { pattern: RegExp }).pattern.source
139
+ );
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Hash code for use in Maps/Sets.
145
+ */
146
+ hashCode(): number {
147
+ let hash = 0;
148
+ switch (this.#inner.variant) {
149
+ case "Any":
150
+ hash = 1;
151
+ break;
152
+ case "Value": {
153
+ const val = (this.#inner as { value: string }).value;
154
+ for (let i = 0; i < val.length; i++) {
155
+ hash = hash * 31 + val.charCodeAt(i);
156
+ }
157
+ break;
158
+ }
159
+ case "Regex":
160
+ hash = 3 * 31 + (this.#inner as { pattern: RegExp }).pattern.source.length;
161
+ break;
162
+ }
163
+ return hash;
164
+ }
165
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @bcts/envelope-pattern - Matcher interface
3
+ *
4
+ * This is a 1:1 TypeScript port of bc-envelope-pattern-rust matcher.rs
5
+ *
6
+ * @module envelope-pattern/pattern/matcher
7
+ */
8
+
9
+ import type { Envelope } from "@bcts/envelope";
10
+ import type { Path } from "../format";
11
+ import type { Pattern } from "./index";
12
+ import type { Instr } from "./vm";
13
+
14
+ /**
15
+ * Matcher interface for pattern matching against envelopes.
16
+ *
17
+ * Corresponds to the Rust `Matcher` trait in matcher.rs
18
+ */
19
+ export interface Matcher {
20
+ /**
21
+ * Return all matching paths along with any named captures.
22
+ */
23
+ pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>];
24
+
25
+ /**
26
+ * Return only the matching paths, discarding any captures.
27
+ */
28
+ paths(haystack: Envelope): Path[];
29
+
30
+ /**
31
+ * Returns true if the pattern matches the haystack.
32
+ */
33
+ matches(haystack: Envelope): boolean;
34
+
35
+ /**
36
+ * Compile this pattern to bytecode.
37
+ */
38
+ compile(code: Instr[], literals: Pattern[], captures: string[]): void;
39
+
40
+ /**
41
+ * Returns true if the Display of the matcher is complex,
42
+ * i.e. contains nested patterns or other complex structures
43
+ * that require its text rendering to be surrounded by grouping
44
+ * parentheses.
45
+ */
46
+ isComplex(): boolean;
47
+ }
48
+
49
+ /**
50
+ * Default implementations for Matcher methods.
51
+ */
52
+ export const MatcherDefaults = {
53
+ /**
54
+ * Default implementation of paths() - calls pathsWithCaptures and discards captures.
55
+ */
56
+ paths(matcher: Matcher, haystack: Envelope): Path[] {
57
+ return matcher.pathsWithCaptures(haystack)[0];
58
+ },
59
+
60
+ /**
61
+ * Default implementation of matches() - checks if paths() returns any results.
62
+ */
63
+ matches(matcher: Matcher, haystack: Envelope): boolean {
64
+ return matcher.paths(haystack).length > 0;
65
+ },
66
+
67
+ /**
68
+ * Default implementation of isComplex() - returns false.
69
+ */
70
+ isComplex(): boolean {
71
+ return false;
72
+ },
73
+ };
74
+
75
+ /**
76
+ * Helper to compile a pattern as an atomic predicate match.
77
+ * Pushes the pattern into literals and emits a single MatchPredicate instruction.
78
+ */
79
+ export function compileAsAtomic(
80
+ pat: Pattern,
81
+ code: Instr[],
82
+ literals: Pattern[],
83
+ _captures: string[],
84
+ ): void {
85
+ const idx = literals.length;
86
+ literals.push(pat);
87
+ code.push({ type: "MatchPredicate", literalIndex: idx });
88
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @bcts/envelope-pattern - And pattern matching
3
+ *
4
+ * This is a 1:1 TypeScript port of bc-envelope-pattern-rust and_pattern.rs
5
+ *
6
+ * @module envelope-pattern/pattern/meta/and-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 createMetaAndPattern: ((pattern: AndPattern) => Pattern) | undefined;
17
+
18
+ export function registerAndPatternFactory(factory: (pattern: AndPattern) => Pattern): void {
19
+ createMetaAndPattern = factory;
20
+ }
21
+
22
+ /**
23
+ * A pattern that matches if all contained patterns match.
24
+ *
25
+ * Corresponds to the Rust `AndPattern` struct in and_pattern.rs
26
+ */
27
+ export class AndPattern implements Matcher {
28
+ readonly #patterns: Pattern[];
29
+
30
+ private constructor(patterns: Pattern[]) {
31
+ this.#patterns = patterns;
32
+ }
33
+
34
+ /**
35
+ * Creates a new AndPattern with the given patterns.
36
+ */
37
+ static new(patterns: Pattern[]): AndPattern {
38
+ return new AndPattern(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 allMatch = this.#patterns.every((pattern) => {
50
+ const matcher = pattern as unknown as Matcher;
51
+ return matcher.matches(haystack);
52
+ });
53
+
54
+ const paths = allMatch ? [[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
+ // Each pattern must match at this position
68
+ for (const pattern of this.#patterns) {
69
+ const matcher = pattern as unknown as Matcher;
70
+ matcher.compile(code, literals, captures);
71
+ }
72
+ }
73
+
74
+ isComplex(): boolean {
75
+ // The pattern is complex if it contains more than one pattern, or if
76
+ // the one pattern is complex itself.
77
+ return (
78
+ this.#patterns.length > 1 || this.#patterns.some((p) => (p as unknown as Matcher).isComplex())
79
+ );
80
+ }
81
+
82
+ toString(): string {
83
+ return this.#patterns
84
+ .map((p) => (p as unknown as { toString(): string }).toString())
85
+ .join(" & ");
86
+ }
87
+
88
+ /**
89
+ * Equality comparison.
90
+ */
91
+ equals(other: AndPattern): boolean {
92
+ if (this.#patterns.length !== other.#patterns.length) {
93
+ return false;
94
+ }
95
+ for (let i = 0; i < this.#patterns.length; i++) {
96
+ if (this.#patterns[i] !== other.#patterns[i]) {
97
+ return false;
98
+ }
99
+ }
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ * Hash code for use in Maps/Sets.
105
+ */
106
+ hashCode(): number {
107
+ return this.#patterns.length;
108
+ }
109
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @bcts/envelope-pattern - Any pattern matching
3
+ *
4
+ * This is a 1:1 TypeScript port of bc-envelope-pattern-rust any_pattern.rs
5
+ *
6
+ * @module envelope-pattern/pattern/meta/any-pattern
7
+ */
8
+
9
+ import type { Envelope } from "@bcts/envelope";
10
+ import type { Path } from "../../format";
11
+ import type { Matcher } from "../matcher";
12
+ import { compileAsAtomic } from "../matcher";
13
+ import type { Instr } from "../vm";
14
+ import type { Pattern } from "../index";
15
+
16
+ // Forward declaration for Pattern factory
17
+ let createMetaAnyPattern: ((pattern: AnyPattern) => Pattern) | undefined;
18
+
19
+ export function registerAnyPatternFactory(factory: (pattern: AnyPattern) => Pattern): void {
20
+ createMetaAnyPattern = factory;
21
+ }
22
+
23
+ /**
24
+ * A pattern that matches any element.
25
+ *
26
+ * Corresponds to the Rust `AnyPattern` struct in any_pattern.rs
27
+ */
28
+ export class AnyPattern implements Matcher {
29
+ private constructor() {
30
+ // Empty constructor - AnyPattern is a singleton-like pattern with no state
31
+ }
32
+
33
+ /**
34
+ * Creates a new AnyPattern.
35
+ */
36
+ static new(): AnyPattern {
37
+ return new AnyPattern();
38
+ }
39
+
40
+ pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
41
+ // Always return a path containing the envelope itself.
42
+ return [[[haystack]], new Map<string, Path[]>()];
43
+ }
44
+
45
+ paths(haystack: Envelope): Path[] {
46
+ return this.pathsWithCaptures(haystack)[0];
47
+ }
48
+
49
+ matches(_haystack: Envelope): boolean {
50
+ return true;
51
+ }
52
+
53
+ compile(code: Instr[], literals: Pattern[], captures: string[]): void {
54
+ if (createMetaAnyPattern === undefined) {
55
+ throw new Error("AnyPattern factory not registered");
56
+ }
57
+ compileAsAtomic(createMetaAnyPattern(this), code, literals, captures);
58
+ }
59
+
60
+ isComplex(): boolean {
61
+ return false;
62
+ }
63
+
64
+ toString(): string {
65
+ return "*";
66
+ }
67
+
68
+ /**
69
+ * Equality comparison.
70
+ */
71
+ equals(_other: AnyPattern): boolean {
72
+ return true; // All AnyPattern instances are equal
73
+ }
74
+
75
+ /**
76
+ * Hash code for use in Maps/Sets.
77
+ */
78
+ hashCode(): number {
79
+ return 0;
80
+ }
81
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @bcts/envelope-pattern - Capture pattern matching
3
+ *
4
+ * This is a 1:1 TypeScript port of bc-envelope-pattern-rust capture_pattern.rs
5
+ *
6
+ * @module envelope-pattern/pattern/meta/capture-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 createMetaCapturePattern: ((pattern: CapturePattern) => Pattern) | undefined;
17
+
18
+ export function registerCapturePatternFactory(factory: (pattern: CapturePattern) => Pattern): void {
19
+ createMetaCapturePattern = factory;
20
+ }
21
+
22
+ /**
23
+ * A pattern that captures a match with a name.
24
+ *
25
+ * Corresponds to the Rust `CapturePattern` struct in capture_pattern.rs
26
+ */
27
+ export class CapturePattern implements Matcher {
28
+ readonly #name: string;
29
+ readonly #pattern: Pattern;
30
+
31
+ private constructor(name: string, pattern: Pattern) {
32
+ this.#name = name;
33
+ this.#pattern = pattern;
34
+ }
35
+
36
+ /**
37
+ * Creates a new CapturePattern with the given name and pattern.
38
+ */
39
+ static new(name: string, pattern: Pattern): CapturePattern {
40
+ return new CapturePattern(name, pattern);
41
+ }
42
+
43
+ /**
44
+ * Gets the name of the capture.
45
+ */
46
+ name(): string {
47
+ return this.#name;
48
+ }
49
+
50
+ /**
51
+ * Gets the inner pattern.
52
+ */
53
+ pattern(): Pattern {
54
+ return this.#pattern;
55
+ }
56
+
57
+ pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
58
+ const matcher = this.#pattern as unknown as Matcher;
59
+ const [paths, caps] = matcher.pathsWithCaptures(haystack);
60
+
61
+ if (paths.length > 0) {
62
+ const existing = caps.get(this.#name) ?? [];
63
+ caps.set(this.#name, [...existing, ...paths]);
64
+ }
65
+
66
+ return [paths, caps];
67
+ }
68
+
69
+ paths(haystack: Envelope): Path[] {
70
+ return this.pathsWithCaptures(haystack)[0];
71
+ }
72
+
73
+ matches(haystack: Envelope): boolean {
74
+ return this.paths(haystack).length > 0;
75
+ }
76
+
77
+ compile(code: Instr[], literals: Pattern[], captures: string[]): void {
78
+ const id = captures.length;
79
+ captures.push(this.#name);
80
+ code.push({ type: "CaptureStart", captureIndex: id });
81
+ const matcher = this.#pattern as unknown as Matcher;
82
+ matcher.compile(code, literals, captures);
83
+ code.push({ type: "CaptureEnd", captureIndex: id });
84
+ }
85
+
86
+ isComplex(): boolean {
87
+ return false;
88
+ }
89
+
90
+ toString(): string {
91
+ return `@${this.#name}(${(this.#pattern as unknown as { toString(): string }).toString()})`;
92
+ }
93
+
94
+ /**
95
+ * Equality comparison.
96
+ */
97
+ equals(other: CapturePattern): boolean {
98
+ return this.#name === other.#name && this.#pattern === other.#pattern;
99
+ }
100
+
101
+ /**
102
+ * Hash code for use in Maps/Sets.
103
+ */
104
+ hashCode(): number {
105
+ let hash = 0;
106
+ for (const char of this.#name) {
107
+ hash = (hash * 31 + char.charCodeAt(0)) | 0;
108
+ }
109
+ return hash;
110
+ }
111
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @bcts/envelope-pattern - Group/repeat pattern matching
3
+ *
4
+ * This is a 1:1 TypeScript port of bc-envelope-pattern-rust repeat_pattern.rs
5
+ *
6
+ * @module envelope-pattern/pattern/meta/group-pattern
7
+ */
8
+
9
+ import type { Envelope } from "@bcts/envelope";
10
+ import { Quantifier } from "@bcts/dcbor-pattern";
11
+ import type { Path } from "../../format";
12
+ import type { Matcher } from "../matcher";
13
+ import type { Instr } from "../vm";
14
+ import type { Pattern } from "../index";
15
+
16
+ // Forward declaration for Pattern factory (used for late binding)
17
+ export let createMetaGroupPattern: ((pattern: GroupPattern) => Pattern) | undefined;
18
+
19
+ export function registerGroupPatternFactory(factory: (pattern: GroupPattern) => Pattern): void {
20
+ createMetaGroupPattern = factory;
21
+ }
22
+
23
+ /**
24
+ * A pattern that matches with repetition.
25
+ *
26
+ * Corresponds to the Rust `GroupPattern` struct in repeat_pattern.rs
27
+ */
28
+ export class GroupPattern implements Matcher {
29
+ readonly #pattern: Pattern;
30
+ readonly #quantifier: Quantifier;
31
+
32
+ private constructor(pattern: Pattern, quantifier: Quantifier) {
33
+ this.#pattern = pattern;
34
+ this.#quantifier = quantifier;
35
+ }
36
+
37
+ /**
38
+ * Creates a new GroupPattern with the specified sub-pattern and quantifier.
39
+ */
40
+ static repeat(pattern: Pattern, quantifier: Quantifier): GroupPattern {
41
+ return new GroupPattern(pattern, quantifier);
42
+ }
43
+
44
+ /**
45
+ * Creates a new GroupPattern with a quantifier that matches exactly once.
46
+ */
47
+ static new(pattern: Pattern): GroupPattern {
48
+ return new GroupPattern(pattern, Quantifier.exactly(1));
49
+ }
50
+
51
+ /**
52
+ * Gets the sub-pattern of this group pattern.
53
+ */
54
+ pattern(): Pattern {
55
+ return this.#pattern;
56
+ }
57
+
58
+ /**
59
+ * Gets the quantifier of this group pattern.
60
+ */
61
+ quantifier(): Quantifier {
62
+ return this.#quantifier;
63
+ }
64
+
65
+ pathsWithCaptures(_haystack: Envelope): [Path[], Map<string, Path[]>] {
66
+ throw new Error(
67
+ "GroupPattern does not support pathsWithCaptures directly; use compile instead",
68
+ );
69
+ }
70
+
71
+ paths(haystack: Envelope): Path[] {
72
+ return this.pathsWithCaptures(haystack)[0];
73
+ }
74
+
75
+ matches(haystack: Envelope): boolean {
76
+ // GroupPattern needs VM execution
77
+ const matcher = this.#pattern as unknown as Matcher;
78
+ return matcher.matches(haystack);
79
+ }
80
+
81
+ compile(code: Instr[], literals: Pattern[], _captures: string[]): void {
82
+ const idx = literals.length;
83
+ literals.push(this.#pattern);
84
+ code.push({ type: "Repeat", patternIndex: idx, quantifier: this.#quantifier });
85
+ }
86
+
87
+ isComplex(): boolean {
88
+ return true;
89
+ }
90
+
91
+ toString(): string {
92
+ const formattedRange = this.#quantifier.toString();
93
+ return `(${(this.#pattern as unknown as { toString(): string }).toString()})${formattedRange}`;
94
+ }
95
+
96
+ /**
97
+ * Equality comparison.
98
+ */
99
+ equals(other: GroupPattern): boolean {
100
+ return this.#pattern === other.#pattern && this.#quantifier.equals(other.#quantifier);
101
+ }
102
+
103
+ /**
104
+ * Hash code for use in Maps/Sets.
105
+ */
106
+ hashCode(): number {
107
+ // Simple hash based on quantifier min/max
108
+ return this.#quantifier.min() * 31 + (this.#quantifier.max() ?? 0);
109
+ }
110
+ }