@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,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequence pattern for dCBOR pattern matching.
|
|
3
|
+
* Matches a sequence of patterns in order.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/sequence-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A pattern that matches a sequence of patterns in order.
|
|
14
|
+
* Used primarily for matching array elements.
|
|
15
|
+
*/
|
|
16
|
+
export interface SequencePattern {
|
|
17
|
+
readonly variant: "Sequence";
|
|
18
|
+
readonly patterns: Pattern[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a SequencePattern with the given patterns.
|
|
23
|
+
*/
|
|
24
|
+
export const sequencePattern = (patterns: Pattern[]): SequencePattern => ({
|
|
25
|
+
variant: "Sequence",
|
|
26
|
+
patterns,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tests if a CBOR value matches this sequence pattern.
|
|
31
|
+
*
|
|
32
|
+
* Note: Sequence patterns are used within array patterns for matching
|
|
33
|
+
* consecutive elements. When used standalone (not within an array),
|
|
34
|
+
* they return false/empty as the actual sequence matching logic is
|
|
35
|
+
* handled by the VM and array pattern matching.
|
|
36
|
+
*/
|
|
37
|
+
export const sequencePatternMatches = (_pattern: SequencePattern, _haystack: Cbor): boolean => {
|
|
38
|
+
// Sequence patterns are meant for array element matching.
|
|
39
|
+
// When used standalone, they don't match any single CBOR value.
|
|
40
|
+
// The VM handles actual sequence matching within arrays.
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns paths to matching values.
|
|
46
|
+
*
|
|
47
|
+
* Note: Sequence patterns return empty paths when used directly.
|
|
48
|
+
* The actual sequence matching is handled by the VM within array contexts.
|
|
49
|
+
*/
|
|
50
|
+
export const sequencePatternPaths = (_pattern: SequencePattern, _haystack: Cbor): Path[] => {
|
|
51
|
+
// Sequence patterns return empty paths when used directly.
|
|
52
|
+
// This matches Rust behavior where sequence matching is VM-based.
|
|
53
|
+
return [];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Formats a SequencePattern as a string.
|
|
58
|
+
*/
|
|
59
|
+
export const sequencePatternDisplay = (
|
|
60
|
+
pattern: SequencePattern,
|
|
61
|
+
patternDisplay: (p: Pattern) => string,
|
|
62
|
+
): string => {
|
|
63
|
+
const parts = pattern.patterns.map(patternDisplay);
|
|
64
|
+
return parts.join(", ");
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets the patterns in this sequence.
|
|
69
|
+
*/
|
|
70
|
+
export const sequencePatternPatterns = (pattern: SequencePattern): Pattern[] => {
|
|
71
|
+
return pattern.patterns;
|
|
72
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequence assigner for array pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* Handles element-to-pattern assignment logic, encapsulating the complex logic
|
|
5
|
+
* for mapping array elements to sequence patterns.
|
|
6
|
+
*
|
|
7
|
+
* @module pattern/structure/array-pattern/assigner
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
11
|
+
import type { Pattern } from "../../index";
|
|
12
|
+
import { hasRepeatPatternsInSlice } from "./helpers";
|
|
13
|
+
import { GenericBacktracker, BooleanBacktrackState, AssignmentBacktrackState } from "./backtrack";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Helper class for handling element-to-pattern assignment logic.
|
|
17
|
+
* Encapsulates the complex logic for mapping array elements to sequence
|
|
18
|
+
* patterns that was previously duplicated between matching and capture
|
|
19
|
+
* collection.
|
|
20
|
+
*/
|
|
21
|
+
export class SequenceAssigner {
|
|
22
|
+
readonly #patterns: Pattern[];
|
|
23
|
+
readonly #arr: Cbor[];
|
|
24
|
+
readonly #matchFn: (pattern: Pattern, value: Cbor) => boolean;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
patterns: Pattern[],
|
|
28
|
+
arr: Cbor[],
|
|
29
|
+
matchFn: (pattern: Pattern, value: Cbor) => boolean,
|
|
30
|
+
) {
|
|
31
|
+
this.#patterns = patterns;
|
|
32
|
+
this.#arr = arr;
|
|
33
|
+
this.#matchFn = matchFn;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if the sequence can match against the array elements (boolean result).
|
|
38
|
+
*/
|
|
39
|
+
canMatch(): boolean {
|
|
40
|
+
// Simple case: if no patterns, then empty array should match
|
|
41
|
+
if (this.#patterns.length === 0) {
|
|
42
|
+
return this.#arr.length === 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if we have any repeat patterns that require backtracking
|
|
46
|
+
const hasRepeatPatterns = hasRepeatPatternsInSlice(this.#patterns);
|
|
47
|
+
|
|
48
|
+
// Simple case: if pattern count equals element count AND no repeat patterns
|
|
49
|
+
if (this.#patterns.length === this.#arr.length && !hasRepeatPatterns) {
|
|
50
|
+
// Try one-to-one matching
|
|
51
|
+
return this.#patterns.every((pattern, i) => this.#matchFn(pattern, this.#arr[i]));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Complex case: use generic backtracking framework
|
|
55
|
+
const backtracker = new GenericBacktracker(this.#patterns, this.#arr, this.#matchFn);
|
|
56
|
+
const state = new BooleanBacktrackState();
|
|
57
|
+
return backtracker.backtrack(state, 0, 0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find the element-to-pattern assignments (returns assignment pairs).
|
|
62
|
+
*/
|
|
63
|
+
findAssignments(): [number, number][] | undefined {
|
|
64
|
+
// Simple case: if no patterns, then empty array should match
|
|
65
|
+
if (this.#patterns.length === 0) {
|
|
66
|
+
return this.#arr.length === 0 ? [] : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check if we have any repeat patterns that require backtracking
|
|
70
|
+
const hasRepeatPatterns = hasRepeatPatternsInSlice(this.#patterns);
|
|
71
|
+
|
|
72
|
+
// Simple case: if pattern count equals element count AND no repeat patterns
|
|
73
|
+
if (this.#patterns.length === this.#arr.length && !hasRepeatPatterns) {
|
|
74
|
+
const assignments: [number, number][] = [];
|
|
75
|
+
for (let patternIdx = 0; patternIdx < this.#patterns.length; patternIdx++) {
|
|
76
|
+
const pattern = this.#patterns[patternIdx];
|
|
77
|
+
const element = this.#arr[patternIdx];
|
|
78
|
+
if (this.#matchFn(pattern, element)) {
|
|
79
|
+
assignments.push([patternIdx, patternIdx]);
|
|
80
|
+
} else {
|
|
81
|
+
return undefined; // Pattern doesn't match its corresponding element
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return assignments;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Complex case: use generic backtracking framework
|
|
88
|
+
const backtracker = new GenericBacktracker(this.#patterns, this.#arr, this.#matchFn);
|
|
89
|
+
const state = new AssignmentBacktrackState();
|
|
90
|
+
if (backtracker.backtrack(state, 0, 0)) {
|
|
91
|
+
return state.assignments;
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic backtracking framework for array pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a generic backtracking algorithm that can work with
|
|
5
|
+
* different types of state management (boolean matching vs assignment tracking).
|
|
6
|
+
*
|
|
7
|
+
* @module pattern/structure/array-pattern/backtrack
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
11
|
+
import type { Pattern } from "../../index";
|
|
12
|
+
import type { RepeatPattern } from "../../meta/repeat-pattern";
|
|
13
|
+
import { extractCaptureWithRepeat, calculateRepeatBounds, canRepeatMatch } from "./helpers";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generic backtracking state interface.
|
|
17
|
+
* Abstracts the differences between boolean matching and assignment tracking.
|
|
18
|
+
*/
|
|
19
|
+
export interface BacktrackState<T> {
|
|
20
|
+
/**
|
|
21
|
+
* Try to advance the state with a new assignment and return true if successful.
|
|
22
|
+
*/
|
|
23
|
+
tryAdvance(patternIdx: number, elementIdx: number): boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Backtrack by removing the last state change.
|
|
27
|
+
*/
|
|
28
|
+
backtrack(): void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if we've reached a successful final state.
|
|
32
|
+
*/
|
|
33
|
+
isSuccess(
|
|
34
|
+
patternIdx: number,
|
|
35
|
+
elementIdx: number,
|
|
36
|
+
patternsLen: number,
|
|
37
|
+
elementsLen: number,
|
|
38
|
+
): boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the final result.
|
|
42
|
+
*/
|
|
43
|
+
getResult(): T;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Boolean backtracking state - just tracks success/failure.
|
|
48
|
+
*/
|
|
49
|
+
export class BooleanBacktrackState implements BacktrackState<boolean> {
|
|
50
|
+
tryAdvance(_patternIdx: number, _elementIdx: number): boolean {
|
|
51
|
+
return true; // Always allow advancement for boolean matching
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
backtrack(): void {
|
|
55
|
+
// Nothing to backtrack for boolean state
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
isSuccess(
|
|
59
|
+
patternIdx: number,
|
|
60
|
+
elementIdx: number,
|
|
61
|
+
patternsLen: number,
|
|
62
|
+
elementsLen: number,
|
|
63
|
+
): boolean {
|
|
64
|
+
return patternIdx >= patternsLen && elementIdx >= elementsLen;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getResult(): boolean {
|
|
68
|
+
return true; // If we get here, we succeeded
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Assignment tracking backtracking state - collects pattern-element pairs.
|
|
74
|
+
*/
|
|
75
|
+
export class AssignmentBacktrackState implements BacktrackState<[number, number][]> {
|
|
76
|
+
readonly assignments: [number, number][] = [];
|
|
77
|
+
|
|
78
|
+
tryAdvance(patternIdx: number, elementIdx: number): boolean {
|
|
79
|
+
this.assignments.push([patternIdx, elementIdx]);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
backtrack(): void {
|
|
84
|
+
this.assignments.pop();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
isSuccess(
|
|
88
|
+
patternIdx: number,
|
|
89
|
+
elementIdx: number,
|
|
90
|
+
patternsLen: number,
|
|
91
|
+
elementsLen: number,
|
|
92
|
+
): boolean {
|
|
93
|
+
return patternIdx >= patternsLen && elementIdx >= elementsLen;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getResult(): [number, number][] {
|
|
97
|
+
return this.assignments;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
len(): number {
|
|
101
|
+
return this.assignments.length;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
truncate(len: number): void {
|
|
105
|
+
this.assignments.length = len;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generic backtracking algorithm that works with any BacktrackState.
|
|
111
|
+
*/
|
|
112
|
+
export class GenericBacktracker {
|
|
113
|
+
readonly #patterns: Pattern[];
|
|
114
|
+
readonly #arr: Cbor[];
|
|
115
|
+
readonly #matchFn: (pattern: Pattern, value: Cbor) => boolean;
|
|
116
|
+
|
|
117
|
+
constructor(
|
|
118
|
+
patterns: Pattern[],
|
|
119
|
+
arr: Cbor[],
|
|
120
|
+
matchFn: (pattern: Pattern, value: Cbor) => boolean,
|
|
121
|
+
) {
|
|
122
|
+
this.#patterns = patterns;
|
|
123
|
+
this.#arr = arr;
|
|
124
|
+
this.#matchFn = matchFn;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generic backtracking algorithm that works with any state type.
|
|
129
|
+
*/
|
|
130
|
+
backtrack<T>(state: BacktrackState<T>, patternIdx: number, elementIdx: number): boolean {
|
|
131
|
+
// Base case: if we've matched all patterns
|
|
132
|
+
if (state.isSuccess(patternIdx, elementIdx, this.#patterns.length, this.#arr.length)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (patternIdx >= this.#patterns.length) {
|
|
137
|
+
return false; // No more patterns but still have elements
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const currentPattern = this.#patterns[patternIdx];
|
|
141
|
+
|
|
142
|
+
// Check if this is a repeat pattern
|
|
143
|
+
if (currentPattern.kind === "Meta" && currentPattern.pattern.type === "Repeat") {
|
|
144
|
+
const repeatPattern = currentPattern.pattern.pattern;
|
|
145
|
+
return this.tryRepeatBacktrack(repeatPattern, state, patternIdx, elementIdx);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check if this is a capture pattern
|
|
149
|
+
if (currentPattern.kind === "Meta" && currentPattern.pattern.type === "Capture") {
|
|
150
|
+
// Check if the capture pattern contains a repeat pattern
|
|
151
|
+
const repeatPattern = extractCaptureWithRepeat(currentPattern);
|
|
152
|
+
if (repeatPattern !== undefined) {
|
|
153
|
+
// Handle this like a repeat pattern
|
|
154
|
+
return this.tryRepeatBacktrack(repeatPattern, state, patternIdx, elementIdx);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle as a normal single-element capture
|
|
158
|
+
if (elementIdx < this.#arr.length) {
|
|
159
|
+
const element = this.#arr[elementIdx];
|
|
160
|
+
const matches = this.#matchFn(currentPattern, element);
|
|
161
|
+
|
|
162
|
+
if (matches && state.tryAdvance(patternIdx, elementIdx)) {
|
|
163
|
+
if (this.backtrack(state, patternIdx + 1, elementIdx + 1)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
// Backtracking is handled by the recursive call failing
|
|
167
|
+
state.backtrack();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Non-repeat pattern: must match exactly one element
|
|
174
|
+
if (elementIdx < this.#arr.length) {
|
|
175
|
+
const element = this.#arr[elementIdx];
|
|
176
|
+
const matches = this.#matchFn(currentPattern, element);
|
|
177
|
+
|
|
178
|
+
if (matches && state.tryAdvance(patternIdx, elementIdx)) {
|
|
179
|
+
if (this.backtrack(state, patternIdx + 1, elementIdx + 1)) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
// Backtracking is handled by the recursive call failing
|
|
183
|
+
state.backtrack();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Helper for repeat pattern backtracking with generic state.
|
|
191
|
+
*/
|
|
192
|
+
private tryRepeatBacktrack<T>(
|
|
193
|
+
repeatPattern: RepeatPattern,
|
|
194
|
+
state: BacktrackState<T>,
|
|
195
|
+
patternIdx: number,
|
|
196
|
+
elementIdx: number,
|
|
197
|
+
): boolean {
|
|
198
|
+
const quantifier = repeatPattern.quantifier;
|
|
199
|
+
const [minCount, maxCount] = calculateRepeatBounds(quantifier, elementIdx, this.#arr.length);
|
|
200
|
+
|
|
201
|
+
// Try different numbers of repetitions (greedy: start with max)
|
|
202
|
+
for (let repCount = maxCount; repCount >= minCount; repCount--) {
|
|
203
|
+
if (
|
|
204
|
+
elementIdx + repCount <= this.#arr.length &&
|
|
205
|
+
canRepeatMatch(repeatPattern, this.#arr, elementIdx, repCount, this.#matchFn)
|
|
206
|
+
) {
|
|
207
|
+
// Record state for all consumed elements
|
|
208
|
+
let advancedCount = 0;
|
|
209
|
+
let canAdvance = true;
|
|
210
|
+
for (let i = 0; i < repCount; i++) {
|
|
211
|
+
if (!state.tryAdvance(patternIdx, elementIdx + i)) {
|
|
212
|
+
// If we can't advance, backtrack what we've added
|
|
213
|
+
// and try next repCount
|
|
214
|
+
for (let j = 0; j < advancedCount; j++) {
|
|
215
|
+
state.backtrack();
|
|
216
|
+
}
|
|
217
|
+
canAdvance = false;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
advancedCount++;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!canAdvance) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Try to match the rest of the sequence recursively
|
|
228
|
+
if (this.backtrack(state, patternIdx + 1, elementIdx + repCount)) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Backtrack: undo all the advances we made for this repCount
|
|
233
|
+
for (let i = 0; i < repCount; i++) {
|
|
234
|
+
state.backtrack();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for array pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/structure/array-pattern/helpers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
8
|
+
import type { Pattern } from "../../index";
|
|
9
|
+
import type { RepeatPattern } from "../../meta/repeat-pattern";
|
|
10
|
+
import type { Quantifier } from "../../../quantifier";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a pattern is a repeat pattern.
|
|
14
|
+
*/
|
|
15
|
+
export const isRepeatPattern = (pattern: Pattern): boolean => {
|
|
16
|
+
return pattern.kind === "Meta" && pattern.pattern.type === "Repeat";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if a pattern is a capture pattern containing a repeat pattern.
|
|
21
|
+
* Returns the inner repeat pattern if found.
|
|
22
|
+
*/
|
|
23
|
+
export const extractCaptureWithRepeat = (pattern: Pattern): RepeatPattern | undefined => {
|
|
24
|
+
if (pattern.kind === "Meta" && pattern.pattern.type === "Capture") {
|
|
25
|
+
const capturePattern = pattern.pattern.pattern;
|
|
26
|
+
const innerPattern = capturePattern.pattern;
|
|
27
|
+
if (innerPattern.kind === "Meta" && innerPattern.pattern.type === "Repeat") {
|
|
28
|
+
return innerPattern.pattern.pattern;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extract any repeat pattern from a pattern, whether direct or within a capture.
|
|
36
|
+
*/
|
|
37
|
+
export const extractRepeatPattern = (pattern: Pattern): RepeatPattern | undefined => {
|
|
38
|
+
if (pattern.kind === "Meta") {
|
|
39
|
+
if (pattern.pattern.type === "Repeat") {
|
|
40
|
+
return pattern.pattern.pattern;
|
|
41
|
+
}
|
|
42
|
+
if (pattern.pattern.type === "Capture") {
|
|
43
|
+
const capturePattern = pattern.pattern.pattern;
|
|
44
|
+
const innerPattern = capturePattern.pattern;
|
|
45
|
+
if (innerPattern.kind === "Meta" && innerPattern.pattern.type === "Repeat") {
|
|
46
|
+
return innerPattern.pattern.pattern;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if a slice of patterns contains any repeat patterns (direct or in captures).
|
|
55
|
+
*/
|
|
56
|
+
export const hasRepeatPatternsInSlice = (patterns: Pattern[]): boolean => {
|
|
57
|
+
return patterns.some((p) => extractRepeatPattern(p) !== undefined);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Calculate the bounds for repeat pattern matching based on quantifier and
|
|
62
|
+
* available elements.
|
|
63
|
+
*/
|
|
64
|
+
export const calculateRepeatBounds = (
|
|
65
|
+
quantifier: Quantifier,
|
|
66
|
+
elementIdx: number,
|
|
67
|
+
arrLen: number,
|
|
68
|
+
): [number, number] => {
|
|
69
|
+
const minCount = quantifier.min();
|
|
70
|
+
const remainingElements = Math.max(0, arrLen - elementIdx);
|
|
71
|
+
const maxCount = Math.min(quantifier.max() ?? remainingElements, remainingElements);
|
|
72
|
+
return [minCount, maxCount];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if a repeat pattern can match a specific number of elements starting
|
|
77
|
+
* at elementIdx.
|
|
78
|
+
*/
|
|
79
|
+
export const canRepeatMatch = (
|
|
80
|
+
repeatPattern: RepeatPattern,
|
|
81
|
+
arr: Cbor[],
|
|
82
|
+
elementIdx: number,
|
|
83
|
+
repCount: number,
|
|
84
|
+
matchFn: (pattern: Pattern, value: Cbor) => boolean,
|
|
85
|
+
): boolean => {
|
|
86
|
+
if (repCount === 0) {
|
|
87
|
+
return true; // Zero repetitions always match
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i < repCount; i++) {
|
|
90
|
+
const element = arr[elementIdx + i];
|
|
91
|
+
if (!matchFn(repeatPattern.pattern, element)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a simple array context path: [arrayCbor, element]
|
|
100
|
+
*/
|
|
101
|
+
export const buildSimpleArrayContextPath = (arrayCbor: Cbor, element: Cbor): Cbor[] => {
|
|
102
|
+
return [arrayCbor, element];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build an extended array context path: [arrayCbor, element] + capturedPath
|
|
107
|
+
* (skip first element)
|
|
108
|
+
*/
|
|
109
|
+
export const buildExtendedArrayContextPath = (
|
|
110
|
+
arrayCbor: Cbor,
|
|
111
|
+
element: Cbor,
|
|
112
|
+
capturedPath: Cbor[],
|
|
113
|
+
): Cbor[] => {
|
|
114
|
+
const arrayPath: Cbor[] = [arrayCbor, element];
|
|
115
|
+
if (capturedPath.length > 1) {
|
|
116
|
+
arrayPath.push(...capturedPath.slice(1));
|
|
117
|
+
}
|
|
118
|
+
return arrayPath;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Transform nested captures to include array context, extending allCaptures.
|
|
123
|
+
*/
|
|
124
|
+
export const transformCapturesWithArrayContext = (
|
|
125
|
+
arrayCbor: Cbor,
|
|
126
|
+
element: Cbor,
|
|
127
|
+
nestedCaptures: Map<string, Cbor[][]>,
|
|
128
|
+
allCaptures: Map<string, Cbor[][]>,
|
|
129
|
+
): void => {
|
|
130
|
+
for (const [captureName, capturedPaths] of nestedCaptures) {
|
|
131
|
+
const arrayContextPaths: Cbor[][] = [];
|
|
132
|
+
for (const capturedPath of capturedPaths) {
|
|
133
|
+
const arrayPath = buildExtendedArrayContextPath(arrayCbor, element, capturedPath);
|
|
134
|
+
arrayContextPaths.push(arrayPath);
|
|
135
|
+
}
|
|
136
|
+
const existing = allCaptures.get(captureName) ?? [];
|
|
137
|
+
existing.push(...arrayContextPaths);
|
|
138
|
+
allCaptures.set(captureName, existing);
|
|
139
|
+
}
|
|
140
|
+
};
|