@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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta patterns for dCBOR pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* @module pattern/meta
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from "./any-pattern";
|
|
8
|
+
export * from "./and-pattern";
|
|
9
|
+
export * from "./or-pattern";
|
|
10
|
+
export * from "./not-pattern";
|
|
11
|
+
export * from "./repeat-pattern";
|
|
12
|
+
export * from "./capture-pattern";
|
|
13
|
+
export * from "./search-pattern";
|
|
14
|
+
export * from "./sequence-pattern";
|
|
15
|
+
|
|
16
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
17
|
+
import type { Path } from "../../format";
|
|
18
|
+
import type { Pattern } from "../index";
|
|
19
|
+
|
|
20
|
+
import { type AnyPattern, anyPatternPaths, anyPatternDisplay } from "./any-pattern";
|
|
21
|
+
import { type AndPattern, andPatternPaths, andPatternDisplay } from "./and-pattern";
|
|
22
|
+
import { type OrPattern, orPatternPaths, orPatternDisplay } from "./or-pattern";
|
|
23
|
+
import { type NotPattern, notPatternPaths, notPatternDisplay } from "./not-pattern";
|
|
24
|
+
import { type RepeatPattern, repeatPatternPaths, repeatPatternDisplay } from "./repeat-pattern";
|
|
25
|
+
import { type CapturePattern, capturePatternPaths, capturePatternDisplay } from "./capture-pattern";
|
|
26
|
+
import { type SearchPattern, searchPatternPaths, searchPatternDisplay } from "./search-pattern";
|
|
27
|
+
import {
|
|
28
|
+
type SequencePattern,
|
|
29
|
+
sequencePatternPaths,
|
|
30
|
+
sequencePatternDisplay,
|
|
31
|
+
} from "./sequence-pattern";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Union of all meta pattern types.
|
|
35
|
+
*/
|
|
36
|
+
export type MetaPattern =
|
|
37
|
+
| { readonly type: "Any"; readonly pattern: AnyPattern }
|
|
38
|
+
| { readonly type: "And"; readonly pattern: AndPattern }
|
|
39
|
+
| { readonly type: "Or"; readonly pattern: OrPattern }
|
|
40
|
+
| { readonly type: "Not"; readonly pattern: NotPattern }
|
|
41
|
+
| { readonly type: "Repeat"; readonly pattern: RepeatPattern }
|
|
42
|
+
| { readonly type: "Capture"; readonly pattern: CapturePattern }
|
|
43
|
+
| { readonly type: "Search"; readonly pattern: SearchPattern }
|
|
44
|
+
| { readonly type: "Sequence"; readonly pattern: SequencePattern };
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns paths to matching values for a MetaPattern.
|
|
48
|
+
*/
|
|
49
|
+
export const metaPatternPaths = (pattern: MetaPattern, haystack: Cbor): Path[] => {
|
|
50
|
+
switch (pattern.type) {
|
|
51
|
+
case "Any":
|
|
52
|
+
return anyPatternPaths(pattern.pattern, haystack);
|
|
53
|
+
case "And":
|
|
54
|
+
return andPatternPaths(pattern.pattern, haystack);
|
|
55
|
+
case "Or":
|
|
56
|
+
return orPatternPaths(pattern.pattern, haystack);
|
|
57
|
+
case "Not":
|
|
58
|
+
return notPatternPaths(pattern.pattern, haystack);
|
|
59
|
+
case "Repeat":
|
|
60
|
+
return repeatPatternPaths(pattern.pattern, haystack);
|
|
61
|
+
case "Capture":
|
|
62
|
+
return capturePatternPaths(pattern.pattern, haystack);
|
|
63
|
+
case "Search":
|
|
64
|
+
return searchPatternPaths(pattern.pattern, haystack);
|
|
65
|
+
case "Sequence":
|
|
66
|
+
return sequencePatternPaths(pattern.pattern, haystack);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Tests if a CBOR value matches a MetaPattern.
|
|
72
|
+
*/
|
|
73
|
+
export const metaPatternMatches = (pattern: MetaPattern, haystack: Cbor): boolean => {
|
|
74
|
+
return metaPatternPaths(pattern, haystack).length > 0;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Formats a MetaPattern as a string.
|
|
79
|
+
*/
|
|
80
|
+
export const metaPatternDisplay = (
|
|
81
|
+
pattern: MetaPattern,
|
|
82
|
+
patternDisplay: (p: Pattern) => string,
|
|
83
|
+
): string => {
|
|
84
|
+
switch (pattern.type) {
|
|
85
|
+
case "Any":
|
|
86
|
+
return anyPatternDisplay(pattern.pattern);
|
|
87
|
+
case "And":
|
|
88
|
+
return andPatternDisplay(pattern.pattern, patternDisplay);
|
|
89
|
+
case "Or":
|
|
90
|
+
return orPatternDisplay(pattern.pattern, patternDisplay);
|
|
91
|
+
case "Not":
|
|
92
|
+
return notPatternDisplay(pattern.pattern, patternDisplay);
|
|
93
|
+
case "Repeat":
|
|
94
|
+
return repeatPatternDisplay(pattern.pattern, patternDisplay);
|
|
95
|
+
case "Capture":
|
|
96
|
+
return capturePatternDisplay(pattern.pattern, patternDisplay);
|
|
97
|
+
case "Search":
|
|
98
|
+
return searchPatternDisplay(pattern.pattern, patternDisplay);
|
|
99
|
+
case "Sequence":
|
|
100
|
+
return sequencePatternDisplay(pattern.pattern, patternDisplay);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Convenience constructors for MetaPattern
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Creates an Any MetaPattern.
|
|
108
|
+
*/
|
|
109
|
+
export const metaAny = (pattern: AnyPattern): MetaPattern => ({
|
|
110
|
+
type: "Any",
|
|
111
|
+
pattern,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Creates an And MetaPattern.
|
|
116
|
+
*/
|
|
117
|
+
export const metaAnd = (pattern: AndPattern): MetaPattern => ({
|
|
118
|
+
type: "And",
|
|
119
|
+
pattern,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Creates an Or MetaPattern.
|
|
124
|
+
*/
|
|
125
|
+
export const metaOr = (pattern: OrPattern): MetaPattern => ({
|
|
126
|
+
type: "Or",
|
|
127
|
+
pattern,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates a Not MetaPattern.
|
|
132
|
+
*/
|
|
133
|
+
export const metaNot = (pattern: NotPattern): MetaPattern => ({
|
|
134
|
+
type: "Not",
|
|
135
|
+
pattern,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Creates a Repeat MetaPattern.
|
|
140
|
+
*/
|
|
141
|
+
export const metaRepeat = (pattern: RepeatPattern): MetaPattern => ({
|
|
142
|
+
type: "Repeat",
|
|
143
|
+
pattern,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Creates a Capture MetaPattern.
|
|
148
|
+
*/
|
|
149
|
+
export const metaCapture = (pattern: CapturePattern): MetaPattern => ({
|
|
150
|
+
type: "Capture",
|
|
151
|
+
pattern,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a Search MetaPattern.
|
|
156
|
+
*/
|
|
157
|
+
export const metaSearch = (pattern: SearchPattern): MetaPattern => ({
|
|
158
|
+
type: "Search",
|
|
159
|
+
pattern,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Creates a Sequence MetaPattern.
|
|
164
|
+
*/
|
|
165
|
+
export const metaSequence = (pattern: SequencePattern): MetaPattern => ({
|
|
166
|
+
type: "Sequence",
|
|
167
|
+
pattern,
|
|
168
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Not pattern for dCBOR pattern matching.
|
|
3
|
+
* Matches if the inner pattern does NOT match.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/not-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
import { matchPattern } from "../match-registry";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A pattern that matches if the inner pattern does NOT match.
|
|
15
|
+
*/
|
|
16
|
+
export interface NotPattern {
|
|
17
|
+
readonly variant: "Not";
|
|
18
|
+
readonly pattern: Pattern;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a NotPattern with the given inner pattern.
|
|
23
|
+
*/
|
|
24
|
+
export const notPattern = (pattern: Pattern): NotPattern => ({
|
|
25
|
+
variant: "Not",
|
|
26
|
+
pattern,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tests if a CBOR value matches this not pattern.
|
|
31
|
+
* Returns true if the inner pattern does NOT match.
|
|
32
|
+
*/
|
|
33
|
+
export const notPatternMatches = (pattern: NotPattern, haystack: Cbor): boolean => {
|
|
34
|
+
return !matchPattern(pattern.pattern, haystack);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns paths to matching values.
|
|
39
|
+
*/
|
|
40
|
+
export const notPatternPaths = (pattern: NotPattern, haystack: Cbor): Path[] => {
|
|
41
|
+
if (notPatternMatches(pattern, haystack)) {
|
|
42
|
+
return [[haystack]];
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if a pattern is complex for display purposes.
|
|
49
|
+
* Complex patterns need parentheses when inside a NOT pattern.
|
|
50
|
+
*/
|
|
51
|
+
const isComplex = (pattern: Pattern): boolean => {
|
|
52
|
+
if (pattern.kind === "Meta") {
|
|
53
|
+
// AND, OR, NOT, Sequence are complex
|
|
54
|
+
return ["And", "Or", "Not", "Sequence"].includes(pattern.pattern.type);
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Formats a NotPattern as a string.
|
|
61
|
+
*/
|
|
62
|
+
export const notPatternDisplay = (
|
|
63
|
+
pattern: NotPattern,
|
|
64
|
+
patternDisplay: (p: Pattern) => string,
|
|
65
|
+
): string => {
|
|
66
|
+
if (isComplex(pattern.pattern)) {
|
|
67
|
+
return `!(${patternDisplay(pattern.pattern)})`;
|
|
68
|
+
}
|
|
69
|
+
return `!${patternDisplay(pattern.pattern)}`;
|
|
70
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Or pattern for dCBOR pattern matching.
|
|
3
|
+
* Matches if any contained pattern matches.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/or-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
import { matchPattern } from "../match-registry";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A pattern that matches if any contained pattern matches.
|
|
15
|
+
*/
|
|
16
|
+
export interface OrPattern {
|
|
17
|
+
readonly variant: "Or";
|
|
18
|
+
readonly patterns: Pattern[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates an OrPattern with the given patterns.
|
|
23
|
+
*/
|
|
24
|
+
export const orPattern = (patterns: Pattern[]): OrPattern => ({
|
|
25
|
+
variant: "Or",
|
|
26
|
+
patterns,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tests if a CBOR value matches this or pattern.
|
|
31
|
+
* At least one pattern must match.
|
|
32
|
+
*/
|
|
33
|
+
export const orPatternMatches = (pattern: OrPattern, haystack: Cbor): boolean => {
|
|
34
|
+
return pattern.patterns.some((p: Pattern) => matchPattern(p, haystack));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns paths to matching values.
|
|
39
|
+
*/
|
|
40
|
+
export const orPatternPaths = (pattern: OrPattern, haystack: Cbor): Path[] => {
|
|
41
|
+
if (orPatternMatches(pattern, haystack)) {
|
|
42
|
+
return [[haystack]];
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Formats an OrPattern as a string.
|
|
49
|
+
*/
|
|
50
|
+
export const orPatternDisplay = (
|
|
51
|
+
pattern: OrPattern,
|
|
52
|
+
patternDisplay: (p: Pattern) => string,
|
|
53
|
+
): string => {
|
|
54
|
+
const parts = pattern.patterns.map(patternDisplay);
|
|
55
|
+
return parts.join(" | ");
|
|
56
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeat pattern for dCBOR pattern matching.
|
|
3
|
+
* Matches with repetition based on a quantifier.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/repeat-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
9
|
+
import type { Path } from "../../format";
|
|
10
|
+
import type { Pattern } from "../index";
|
|
11
|
+
import { Quantifier } from "../../quantifier";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A pattern that matches with repetition.
|
|
15
|
+
*/
|
|
16
|
+
export interface RepeatPattern {
|
|
17
|
+
readonly variant: "Repeat";
|
|
18
|
+
readonly pattern: Pattern;
|
|
19
|
+
readonly quantifier: Quantifier;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a RepeatPattern with the given pattern and quantifier.
|
|
24
|
+
*/
|
|
25
|
+
export const repeatPattern = (pattern: Pattern, quantifier: Quantifier): RepeatPattern => ({
|
|
26
|
+
variant: "Repeat",
|
|
27
|
+
pattern,
|
|
28
|
+
quantifier,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a RepeatPattern that matches zero or more times (greedy).
|
|
33
|
+
*/
|
|
34
|
+
export const repeatZeroOrMore = (pattern: Pattern): RepeatPattern => ({
|
|
35
|
+
variant: "Repeat",
|
|
36
|
+
pattern,
|
|
37
|
+
quantifier: Quantifier.zeroOrMore(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a RepeatPattern that matches one or more times (greedy).
|
|
42
|
+
*/
|
|
43
|
+
export const repeatOneOrMore = (pattern: Pattern): RepeatPattern => ({
|
|
44
|
+
variant: "Repeat",
|
|
45
|
+
pattern,
|
|
46
|
+
quantifier: Quantifier.oneOrMore(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a RepeatPattern that matches zero or one time (greedy).
|
|
51
|
+
*/
|
|
52
|
+
export const repeatOptional = (pattern: Pattern): RepeatPattern => ({
|
|
53
|
+
variant: "Repeat",
|
|
54
|
+
pattern,
|
|
55
|
+
quantifier: Quantifier.zeroOrOne(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a RepeatPattern that matches exactly n times.
|
|
60
|
+
*/
|
|
61
|
+
export const repeatExact = (pattern: Pattern, n: number): RepeatPattern => ({
|
|
62
|
+
variant: "Repeat",
|
|
63
|
+
pattern,
|
|
64
|
+
quantifier: Quantifier.exactly(n),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates a RepeatPattern that matches between min and max times.
|
|
69
|
+
*/
|
|
70
|
+
export const repeatRange = (pattern: Pattern, min: number, max?: number): RepeatPattern => ({
|
|
71
|
+
variant: "Repeat",
|
|
72
|
+
pattern,
|
|
73
|
+
quantifier: max !== undefined ? Quantifier.between(min, max) : Quantifier.atLeast(min),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
import { matchPattern } from "../match-registry";
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Tests if a CBOR value matches this repeat pattern.
|
|
80
|
+
* Note: This is a simplified implementation. Complex matching
|
|
81
|
+
* will be implemented with the VM.
|
|
82
|
+
*/
|
|
83
|
+
export const repeatPatternMatches = (pattern: RepeatPattern, haystack: Cbor): boolean => {
|
|
84
|
+
// Simple case: check if the inner pattern matches at least once
|
|
85
|
+
// and the quantifier allows it
|
|
86
|
+
const innerMatches = matchPattern(pattern.pattern, haystack);
|
|
87
|
+
const min = pattern.quantifier.min();
|
|
88
|
+
const max = pattern.quantifier.max();
|
|
89
|
+
|
|
90
|
+
if (innerMatches) {
|
|
91
|
+
// If pattern matches once, check if 1 is in valid range
|
|
92
|
+
return min <= 1 && (max === undefined || max >= 1);
|
|
93
|
+
}
|
|
94
|
+
// If pattern doesn't match, only valid if min is 0
|
|
95
|
+
return min === 0;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns paths to matching values.
|
|
100
|
+
*/
|
|
101
|
+
export const repeatPatternPaths = (pattern: RepeatPattern, haystack: Cbor): Path[] => {
|
|
102
|
+
if (repeatPatternMatches(pattern, haystack)) {
|
|
103
|
+
return [[haystack]];
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Formats a RepeatPattern as a string.
|
|
110
|
+
* Always wraps the inner pattern in parentheses to match Rust behavior.
|
|
111
|
+
*/
|
|
112
|
+
export const repeatPatternDisplay = (
|
|
113
|
+
pattern: RepeatPattern,
|
|
114
|
+
patternDisplay: (p: Pattern) => string,
|
|
115
|
+
): string => {
|
|
116
|
+
return `(${patternDisplay(pattern.pattern)})${pattern.quantifier.toString()}`;
|
|
117
|
+
};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search pattern for dCBOR pattern matching.
|
|
3
|
+
* Searches the entire CBOR tree for matches.
|
|
4
|
+
*
|
|
5
|
+
* @module pattern/meta/search-pattern
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Cbor, CborInput } from "@bcts/dcbor";
|
|
9
|
+
import {
|
|
10
|
+
isArray,
|
|
11
|
+
isMap,
|
|
12
|
+
isTagged,
|
|
13
|
+
arrayLength,
|
|
14
|
+
arrayItem,
|
|
15
|
+
mapKeys,
|
|
16
|
+
mapValue,
|
|
17
|
+
tagContent,
|
|
18
|
+
cbor,
|
|
19
|
+
} from "@bcts/dcbor";
|
|
20
|
+
import type { Path } from "../../format";
|
|
21
|
+
import type { Pattern } from "../index";
|
|
22
|
+
import { matchPattern } from "../match-registry";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A pattern that searches the entire CBOR tree for matches.
|
|
26
|
+
*/
|
|
27
|
+
export interface SearchPattern {
|
|
28
|
+
readonly variant: "Search";
|
|
29
|
+
readonly pattern: Pattern;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a SearchPattern with the given inner pattern.
|
|
34
|
+
*/
|
|
35
|
+
export const searchPattern = (pattern: Pattern): SearchPattern => ({
|
|
36
|
+
variant: "Search",
|
|
37
|
+
pattern,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Recursively searches the CBOR tree and collects all matching paths.
|
|
42
|
+
*/
|
|
43
|
+
const searchRecursive = (
|
|
44
|
+
pattern: Pattern,
|
|
45
|
+
haystack: Cbor,
|
|
46
|
+
currentPath: Cbor[],
|
|
47
|
+
results: Path[],
|
|
48
|
+
): void => {
|
|
49
|
+
// Check if current node matches
|
|
50
|
+
if (matchPattern(pattern, haystack)) {
|
|
51
|
+
results.push([...currentPath, haystack]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Recursively search children
|
|
55
|
+
if (isArray(haystack)) {
|
|
56
|
+
const len = arrayLength(haystack);
|
|
57
|
+
if (len !== undefined) {
|
|
58
|
+
for (let i = 0; i < len; i++) {
|
|
59
|
+
const item = arrayItem(haystack, i);
|
|
60
|
+
if (item !== undefined) {
|
|
61
|
+
searchRecursive(pattern, item, [...currentPath, haystack], results);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else if (isMap(haystack)) {
|
|
66
|
+
const keys = mapKeys(haystack);
|
|
67
|
+
if (keys !== undefined) {
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
// Search in keys
|
|
70
|
+
searchRecursive(pattern, key, [...currentPath, haystack], results);
|
|
71
|
+
// Search in values
|
|
72
|
+
const rawValue = mapValue(haystack, key);
|
|
73
|
+
if (rawValue !== undefined && rawValue !== null) {
|
|
74
|
+
// Wrap raw JavaScript value in CBOR if needed
|
|
75
|
+
const value = (rawValue as Cbor)?.isCbor
|
|
76
|
+
? (rawValue as Cbor)
|
|
77
|
+
: cbor(rawValue as CborInput);
|
|
78
|
+
searchRecursive(pattern, value, [...currentPath, haystack], results);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else if (isTagged(haystack)) {
|
|
83
|
+
const content = tagContent(haystack);
|
|
84
|
+
if (content !== undefined) {
|
|
85
|
+
searchRecursive(pattern, content, [...currentPath, haystack], results);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Tests if a CBOR value matches this search pattern.
|
|
92
|
+
* Returns true if any node in the tree matches.
|
|
93
|
+
*/
|
|
94
|
+
export const searchPatternMatches = (pattern: SearchPattern, haystack: Cbor): boolean => {
|
|
95
|
+
const paths = searchPatternPaths(pattern, haystack);
|
|
96
|
+
return paths.length > 0;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns paths to all matching values in the tree.
|
|
101
|
+
*/
|
|
102
|
+
export const searchPatternPaths = (pattern: SearchPattern, haystack: Cbor): Path[] => {
|
|
103
|
+
const results: Path[] = [];
|
|
104
|
+
searchRecursive(pattern.pattern, haystack, [], results);
|
|
105
|
+
return results;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Result type for paths with captures from search operations.
|
|
110
|
+
*/
|
|
111
|
+
export interface SearchWithCaptures {
|
|
112
|
+
readonly paths: Path[];
|
|
113
|
+
readonly captures: Map<string, Path[]>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Recursively searches the CBOR tree, collecting paths and captures.
|
|
118
|
+
*/
|
|
119
|
+
const searchRecursiveWithCaptures = (
|
|
120
|
+
pattern: Pattern,
|
|
121
|
+
haystack: Cbor,
|
|
122
|
+
currentPath: Cbor[],
|
|
123
|
+
results: Path[],
|
|
124
|
+
captures: Map<string, Path[]>,
|
|
125
|
+
collectCapture: (p: Pattern, h: Cbor, path: Cbor[]) => void,
|
|
126
|
+
): void => {
|
|
127
|
+
// Check if current node matches
|
|
128
|
+
if (matchPattern(pattern, haystack)) {
|
|
129
|
+
const matchPath = [...currentPath, haystack];
|
|
130
|
+
results.push(matchPath);
|
|
131
|
+
// Collect captures for this match
|
|
132
|
+
collectCapture(pattern, haystack, matchPath);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Recursively search children
|
|
136
|
+
if (isArray(haystack)) {
|
|
137
|
+
const len = arrayLength(haystack);
|
|
138
|
+
if (len !== undefined) {
|
|
139
|
+
for (let i = 0; i < len; i++) {
|
|
140
|
+
const item = arrayItem(haystack, i);
|
|
141
|
+
if (item !== undefined) {
|
|
142
|
+
searchRecursiveWithCaptures(
|
|
143
|
+
pattern,
|
|
144
|
+
item,
|
|
145
|
+
[...currentPath, haystack],
|
|
146
|
+
results,
|
|
147
|
+
captures,
|
|
148
|
+
collectCapture,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else if (isMap(haystack)) {
|
|
154
|
+
const keys = mapKeys(haystack);
|
|
155
|
+
if (keys !== undefined) {
|
|
156
|
+
for (const key of keys) {
|
|
157
|
+
// Search in keys
|
|
158
|
+
searchRecursiveWithCaptures(
|
|
159
|
+
pattern,
|
|
160
|
+
key,
|
|
161
|
+
[...currentPath, haystack],
|
|
162
|
+
results,
|
|
163
|
+
captures,
|
|
164
|
+
collectCapture,
|
|
165
|
+
);
|
|
166
|
+
// Search in values
|
|
167
|
+
const rawValue = mapValue(haystack, key);
|
|
168
|
+
if (rawValue !== undefined && rawValue !== null) {
|
|
169
|
+
// Wrap raw JavaScript value in CBOR if needed
|
|
170
|
+
const value = (rawValue as Cbor)?.isCbor
|
|
171
|
+
? (rawValue as Cbor)
|
|
172
|
+
: cbor(rawValue as CborInput);
|
|
173
|
+
searchRecursiveWithCaptures(
|
|
174
|
+
pattern,
|
|
175
|
+
value,
|
|
176
|
+
[...currentPath, haystack],
|
|
177
|
+
results,
|
|
178
|
+
captures,
|
|
179
|
+
collectCapture,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else if (isTagged(haystack)) {
|
|
185
|
+
const content = tagContent(haystack);
|
|
186
|
+
if (content !== undefined) {
|
|
187
|
+
searchRecursiveWithCaptures(
|
|
188
|
+
pattern,
|
|
189
|
+
content,
|
|
190
|
+
[...currentPath, haystack],
|
|
191
|
+
results,
|
|
192
|
+
captures,
|
|
193
|
+
collectCapture,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Extract capture from a pattern at a given match location.
|
|
201
|
+
* Recursively searches for all capture patterns.
|
|
202
|
+
*/
|
|
203
|
+
const extractCaptures = (
|
|
204
|
+
pattern: Pattern,
|
|
205
|
+
matchPath: Cbor[],
|
|
206
|
+
captures: Map<string, Path[]>,
|
|
207
|
+
): void => {
|
|
208
|
+
if (pattern.kind === "Meta") {
|
|
209
|
+
switch (pattern.pattern.type) {
|
|
210
|
+
case "Capture": {
|
|
211
|
+
const captureName = pattern.pattern.pattern.name;
|
|
212
|
+
const existing = captures.get(captureName) ?? [];
|
|
213
|
+
existing.push(matchPath);
|
|
214
|
+
captures.set(captureName, existing);
|
|
215
|
+
// Also extract from inner pattern
|
|
216
|
+
extractCaptures(pattern.pattern.pattern.pattern, matchPath, captures);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "And":
|
|
220
|
+
for (const p of pattern.pattern.pattern.patterns) {
|
|
221
|
+
extractCaptures(p, matchPath, captures);
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
case "Or":
|
|
225
|
+
for (const p of pattern.pattern.pattern.patterns) {
|
|
226
|
+
extractCaptures(p, matchPath, captures);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
case "Sequence":
|
|
230
|
+
for (const p of pattern.pattern.pattern.patterns) {
|
|
231
|
+
extractCaptures(p, matchPath, captures);
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
case "Not":
|
|
235
|
+
extractCaptures(pattern.pattern.pattern.pattern, matchPath, captures);
|
|
236
|
+
break;
|
|
237
|
+
case "Repeat":
|
|
238
|
+
extractCaptures(pattern.pattern.pattern.pattern, matchPath, captures);
|
|
239
|
+
break;
|
|
240
|
+
case "Search":
|
|
241
|
+
extractCaptures(pattern.pattern.pattern.pattern, matchPath, captures);
|
|
242
|
+
break;
|
|
243
|
+
case "Any":
|
|
244
|
+
// No captures
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
} else if (pattern.kind === "Structure") {
|
|
248
|
+
switch (pattern.pattern.type) {
|
|
249
|
+
case "Array":
|
|
250
|
+
if (pattern.pattern.pattern.variant === "Elements") {
|
|
251
|
+
extractCaptures(pattern.pattern.pattern.pattern, matchPath, captures);
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
case "Map":
|
|
255
|
+
if (pattern.pattern.pattern.variant === "Constraints") {
|
|
256
|
+
for (const [keyPattern, valuePattern] of pattern.pattern.pattern.constraints) {
|
|
257
|
+
extractCaptures(keyPattern, matchPath, captures);
|
|
258
|
+
extractCaptures(valuePattern, matchPath, captures);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
case "Tagged":
|
|
263
|
+
if (pattern.pattern.pattern.variant !== "Any") {
|
|
264
|
+
extractCaptures(pattern.pattern.pattern.pattern, matchPath, captures);
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Returns paths with captures for all matching values in the tree.
|
|
273
|
+
*/
|
|
274
|
+
export const searchPatternPathsWithCaptures = (
|
|
275
|
+
pattern: SearchPattern,
|
|
276
|
+
haystack: Cbor,
|
|
277
|
+
): SearchWithCaptures => {
|
|
278
|
+
const results: Path[] = [];
|
|
279
|
+
const captures = new Map<string, Path[]>();
|
|
280
|
+
|
|
281
|
+
const collectCapture = (p: Pattern, _h: Cbor, path: Cbor[]): void => {
|
|
282
|
+
extractCaptures(p, path, captures);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
searchRecursiveWithCaptures(pattern.pattern, haystack, [], results, captures, collectCapture);
|
|
286
|
+
|
|
287
|
+
return { paths: results, captures };
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Formats a SearchPattern as a string.
|
|
292
|
+
*/
|
|
293
|
+
export const searchPatternDisplay = (
|
|
294
|
+
pattern: SearchPattern,
|
|
295
|
+
patternDisplay: (p: Pattern) => string,
|
|
296
|
+
): string => {
|
|
297
|
+
return `search(${patternDisplay(pattern.pattern)})`;
|
|
298
|
+
};
|