@bcts/envelope-pattern 1.0.0-alpha.17 → 1.0.0-alpha.18
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/README.md +1 -1
- package/dist/index.cjs +1679 -1401
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +54 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +1680 -1403
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +1670 -1402
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/format.ts +23 -4
- package/src/parse/index.ts +138 -5
- package/src/parse/token.ts +4 -3
- package/src/pattern/index.ts +110 -2
- package/src/pattern/leaf/array-pattern.ts +1 -1
- package/src/pattern/leaf/bool-pattern.ts +1 -1
- package/src/pattern/leaf/byte-string-pattern.ts +1 -1
- package/src/pattern/leaf/cbor-pattern.ts +2 -2
- package/src/pattern/leaf/date-pattern.ts +1 -1
- package/src/pattern/leaf/index.ts +1 -2
- package/src/pattern/leaf/known-value-pattern.ts +4 -3
- package/src/pattern/leaf/map-pattern.ts +1 -1
- package/src/pattern/leaf/null-pattern.ts +1 -1
- package/src/pattern/leaf/number-pattern.ts +1 -1
- package/src/pattern/leaf/text-pattern.ts +1 -1
- package/src/pattern/matcher.ts +88 -3
- package/src/pattern/meta/and-pattern.ts +10 -9
- package/src/pattern/meta/capture-pattern.ts +5 -6
- package/src/pattern/meta/group-pattern.ts +9 -6
- package/src/pattern/meta/not-pattern.ts +3 -2
- package/src/pattern/meta/or-pattern.ts +18 -13
- package/src/pattern/meta/search-pattern.ts +4 -4
- package/src/pattern/meta/traverse-pattern.ts +31 -7
- package/src/pattern/structure/assertions-pattern.ts +7 -8
- package/src/pattern/structure/index.ts +1 -0
- package/src/pattern/structure/object-pattern.ts +2 -3
- package/src/pattern/structure/predicate-pattern.ts +2 -3
- package/src/pattern/structure/subject-pattern.ts +2 -3
- package/src/pattern/structure/wrapped-pattern.ts +28 -7
- package/src/pattern/vm.ts +12 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bcts/envelope-pattern",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pattern matching for Gordian Envelope structures",
|
|
6
6
|
"license": "BSD-2-Clause-Patent",
|
|
@@ -66,10 +66,10 @@
|
|
|
66
66
|
"vitest": "^4.0.18"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@bcts/dcbor": "^1.0.0-alpha.
|
|
70
|
-
"@bcts/dcbor-parse": "^1.0.0-alpha.
|
|
71
|
-
"@bcts/dcbor-pattern": "^1.0.0-alpha.
|
|
72
|
-
"@bcts/envelope": "^1.0.0-alpha.
|
|
73
|
-
"@bcts/known-values": "^1.0.0-alpha.
|
|
69
|
+
"@bcts/dcbor": "^1.0.0-alpha.18",
|
|
70
|
+
"@bcts/dcbor-parse": "^1.0.0-alpha.18",
|
|
71
|
+
"@bcts/dcbor-pattern": "^1.0.0-alpha.18",
|
|
72
|
+
"@bcts/envelope": "^1.0.0-alpha.18",
|
|
73
|
+
"@bcts/known-values": "^1.0.0-alpha.18"
|
|
74
74
|
}
|
|
75
75
|
}
|
package/src/format.ts
CHANGED
|
@@ -156,18 +156,37 @@ export function envelopeSummary(env: Envelope): string {
|
|
|
156
156
|
|
|
157
157
|
let summary: string;
|
|
158
158
|
switch (c.type) {
|
|
159
|
-
case "node":
|
|
160
|
-
|
|
159
|
+
case "node": {
|
|
160
|
+
const subjectSummary = env.subject().summary(Number.MAX_SAFE_INTEGER);
|
|
161
|
+
const assertions = env.assertions();
|
|
162
|
+
if (assertions.length > 0) {
|
|
163
|
+
const assertionSummaries = assertions.map((a) => {
|
|
164
|
+
const ac = a.case();
|
|
165
|
+
if (ac.type === "assertion") {
|
|
166
|
+
const pred = ac.assertion.predicate().summary(Number.MAX_SAFE_INTEGER);
|
|
167
|
+
const obj = ac.assertion.object().summary(Number.MAX_SAFE_INTEGER);
|
|
168
|
+
return `${pred}: ${obj}`;
|
|
169
|
+
}
|
|
170
|
+
return a.summary(Number.MAX_SAFE_INTEGER);
|
|
171
|
+
});
|
|
172
|
+
summary = `NODE ${subjectSummary} [ ${assertionSummaries.join(", ")} ]`;
|
|
173
|
+
} else {
|
|
174
|
+
summary = `NODE ${subjectSummary}`;
|
|
175
|
+
}
|
|
161
176
|
break;
|
|
177
|
+
}
|
|
162
178
|
case "leaf":
|
|
163
179
|
summary = `LEAF ${env.summary(Number.MAX_SAFE_INTEGER)}`;
|
|
164
180
|
break;
|
|
165
181
|
case "wrapped":
|
|
166
182
|
summary = `WRAPPED ${env.summary(Number.MAX_SAFE_INTEGER)}`;
|
|
167
183
|
break;
|
|
168
|
-
case "assertion":
|
|
169
|
-
|
|
184
|
+
case "assertion": {
|
|
185
|
+
const pred = c.assertion.predicate().summary(Number.MAX_SAFE_INTEGER);
|
|
186
|
+
const obj = c.assertion.object().summary(Number.MAX_SAFE_INTEGER);
|
|
187
|
+
summary = `ASSERTION ${pred}: ${obj}`;
|
|
170
188
|
break;
|
|
189
|
+
}
|
|
171
190
|
case "elided":
|
|
172
191
|
summary = "ELIDED";
|
|
173
192
|
break;
|
package/src/parse/index.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { parse as parseDcborPattern } from "@bcts/dcbor-pattern";
|
|
11
|
+
import { parseDcborItemPartial } from "@bcts/dcbor-parse";
|
|
11
12
|
import { Lexer } from "./token";
|
|
12
13
|
import {
|
|
13
14
|
type Result,
|
|
@@ -37,10 +38,17 @@ import {
|
|
|
37
38
|
byteString,
|
|
38
39
|
anyDate,
|
|
39
40
|
date,
|
|
41
|
+
dateRange,
|
|
42
|
+
dateEarliest,
|
|
43
|
+
dateLatest,
|
|
44
|
+
dateRegex,
|
|
40
45
|
anyKnownValue,
|
|
41
46
|
knownValue,
|
|
42
47
|
anyArray,
|
|
43
48
|
anyTag,
|
|
49
|
+
anyCbor,
|
|
50
|
+
cborValue,
|
|
51
|
+
cborPattern,
|
|
44
52
|
nullPattern,
|
|
45
53
|
// Structure pattern constructors
|
|
46
54
|
leaf,
|
|
@@ -79,7 +87,6 @@ import {
|
|
|
79
87
|
ByteStringPattern,
|
|
80
88
|
KnownValuePattern,
|
|
81
89
|
ArrayPattern,
|
|
82
|
-
TaggedPattern,
|
|
83
90
|
DigestPattern,
|
|
84
91
|
NodePattern,
|
|
85
92
|
AssertionsPattern,
|
|
@@ -87,7 +94,6 @@ import {
|
|
|
87
94
|
leafByteString,
|
|
88
95
|
leafKnownValue,
|
|
89
96
|
leafArray,
|
|
90
|
-
leafTag,
|
|
91
97
|
structureDigest,
|
|
92
98
|
structureNode,
|
|
93
99
|
structureAssertions,
|
|
@@ -469,6 +475,7 @@ function parsePrimary(lexer: Lexer): Result<Pattern> {
|
|
|
469
475
|
case "Comma":
|
|
470
476
|
case "Ellipsis":
|
|
471
477
|
case "Range":
|
|
478
|
+
case "Identifier":
|
|
472
479
|
return err(unexpectedToken(token, span));
|
|
473
480
|
}
|
|
474
481
|
}
|
|
@@ -685,7 +692,50 @@ function parseTag(lexer: Lexer): Result<Pattern> {
|
|
|
685
692
|
* Parse date content from date'...' pattern.
|
|
686
693
|
*/
|
|
687
694
|
function parseDateContent(content: string, span: Span): Result<Pattern> {
|
|
688
|
-
//
|
|
695
|
+
// Check for regex syntax: /pattern/
|
|
696
|
+
if (content.startsWith("/") && content.endsWith("/")) {
|
|
697
|
+
const regexStr = content.slice(1, -1);
|
|
698
|
+
try {
|
|
699
|
+
return ok(dateRegex(new RegExp(regexStr)));
|
|
700
|
+
} catch {
|
|
701
|
+
return err(invalidRegex(span));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Check for range syntax: date1...date2, date1..., ...date2
|
|
706
|
+
const rangeIdx = content.indexOf("...");
|
|
707
|
+
if (rangeIdx !== -1) {
|
|
708
|
+
const left = content.slice(0, rangeIdx).trim();
|
|
709
|
+
const right = content.slice(rangeIdx + 3).trim();
|
|
710
|
+
|
|
711
|
+
if (left.length === 0 && right.length > 0) {
|
|
712
|
+
// ...date2 → latest
|
|
713
|
+
const parsed = Date.parse(right);
|
|
714
|
+
if (isNaN(parsed)) return err({ type: "InvalidDateFormat", span });
|
|
715
|
+
return ok(dateLatest(CborDate.fromDatetime(new Date(parsed))));
|
|
716
|
+
}
|
|
717
|
+
if (left.length > 0 && right.length === 0) {
|
|
718
|
+
// date1... → earliest
|
|
719
|
+
const parsed = Date.parse(left);
|
|
720
|
+
if (isNaN(parsed)) return err({ type: "InvalidDateFormat", span });
|
|
721
|
+
return ok(dateEarliest(CborDate.fromDatetime(new Date(parsed))));
|
|
722
|
+
}
|
|
723
|
+
if (left.length > 0 && right.length > 0) {
|
|
724
|
+
// date1...date2 → range
|
|
725
|
+
const parsedStart = Date.parse(left);
|
|
726
|
+
const parsedEnd = Date.parse(right);
|
|
727
|
+
if (isNaN(parsedStart) || isNaN(parsedEnd)) return err({ type: "InvalidDateFormat", span });
|
|
728
|
+
return ok(
|
|
729
|
+
dateRange(
|
|
730
|
+
CborDate.fromDatetime(new Date(parsedStart)),
|
|
731
|
+
CborDate.fromDatetime(new Date(parsedEnd)),
|
|
732
|
+
),
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
return err({ type: "InvalidDateFormat", span });
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Simple exact date
|
|
689
739
|
const parsed = Date.parse(content);
|
|
690
740
|
if (isNaN(parsed)) {
|
|
691
741
|
return err({ type: "InvalidDateFormat", span });
|
|
@@ -713,17 +763,64 @@ function parseKnownValueContent(content: string): Result<Pattern> {
|
|
|
713
763
|
|
|
714
764
|
/**
|
|
715
765
|
* Parse CBOR pattern.
|
|
766
|
+
*
|
|
767
|
+
* Matches Rust parse_cbor: tries dcbor-pattern regex first (/keyword/),
|
|
768
|
+
* then CBOR diagnostic notation via parseDcborItemPartial, then falls
|
|
769
|
+
* back to parseOr for envelope pattern expressions.
|
|
716
770
|
*/
|
|
717
771
|
function parseCbor(lexer: Lexer): Result<Pattern> {
|
|
718
772
|
// Check for optional content in parentheses
|
|
719
773
|
const next = lexer.peekToken();
|
|
720
774
|
if (next?.token.type !== "ParenOpen") {
|
|
721
|
-
return ok(
|
|
775
|
+
return ok(anyCbor()); // cbor matches any CBOR value
|
|
722
776
|
}
|
|
723
777
|
|
|
724
778
|
lexer.next(); // consume (
|
|
725
779
|
|
|
726
|
-
//
|
|
780
|
+
// Check for dcbor-pattern regex syntax: cbor(/keyword/)
|
|
781
|
+
// Use peek() (character-level, non-destructive) instead of peekToken()
|
|
782
|
+
// to avoid the lexer advancing past the CBOR content.
|
|
783
|
+
if (lexer.peek() === "/") {
|
|
784
|
+
const regexTokenResult = lexer.next(); // tokenize /pattern/
|
|
785
|
+
if (regexTokenResult?.token.type === "Regex") {
|
|
786
|
+
const regexToken = regexTokenResult.token;
|
|
787
|
+
if (!regexToken.value.ok) return err(regexToken.value.error);
|
|
788
|
+
const keyword = regexToken.value.value;
|
|
789
|
+
|
|
790
|
+
// Parse the keyword as a dcbor-pattern expression
|
|
791
|
+
const dcborResult = parseDcborPattern(keyword);
|
|
792
|
+
if (!dcborResult.ok) {
|
|
793
|
+
return err(unexpectedToken(regexToken, regexTokenResult.span));
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const close = lexer.next();
|
|
797
|
+
if (close?.token.type !== "ParenClose") {
|
|
798
|
+
return err({ type: "ExpectedCloseParen", span: lexer.span() });
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return ok(cborPattern(dcborResult.value));
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Try to parse inner content as CBOR diagnostic notation
|
|
806
|
+
// (matching Rust utils::parse_cbor_inner which calls parse_dcbor_item_partial)
|
|
807
|
+
const remaining = lexer.remainder();
|
|
808
|
+
const cborResult = parseDcborItemPartial(remaining);
|
|
809
|
+
if (cborResult.ok) {
|
|
810
|
+
const [cborData, consumed] = cborResult.value;
|
|
811
|
+
lexer.bump(consumed);
|
|
812
|
+
// Skip whitespace before closing paren
|
|
813
|
+
while (lexer.peek() === " " || lexer.peek() === "\t" || lexer.peek() === "\n") {
|
|
814
|
+
lexer.bump(1);
|
|
815
|
+
}
|
|
816
|
+
const close = lexer.next();
|
|
817
|
+
if (close?.token.type !== "ParenClose") {
|
|
818
|
+
return err({ type: "ExpectedCloseParen", span: lexer.span() });
|
|
819
|
+
}
|
|
820
|
+
return ok(cborValue(cborData));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Fallback: try parsing as a regular pattern expression
|
|
727
824
|
const inner = parseOr(lexer);
|
|
728
825
|
if (!inner.ok) return inner;
|
|
729
826
|
|
|
@@ -746,6 +843,24 @@ function parseNode(lexer: Lexer): Result<Pattern> {
|
|
|
746
843
|
}
|
|
747
844
|
|
|
748
845
|
lexer.next(); // consume (
|
|
846
|
+
|
|
847
|
+
// Check for assertion count range: node({n,m}), node({n}), node({n,})
|
|
848
|
+
const afterParen = lexer.peekToken();
|
|
849
|
+
if (afterParen?.token.type === "Range") {
|
|
850
|
+
lexer.next(); // consume Range token
|
|
851
|
+
const rangeToken = afterParen.token;
|
|
852
|
+
if (!rangeToken.value.ok) return err(rangeToken.value.error);
|
|
853
|
+
const quantifier = rangeToken.value.value;
|
|
854
|
+
const interval = quantifier.interval();
|
|
855
|
+
|
|
856
|
+
const close = lexer.next();
|
|
857
|
+
if (close?.token.type !== "ParenClose") {
|
|
858
|
+
return err({ type: "ExpectedCloseParen", span: lexer.span() });
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return ok(patternStructure(structureNode(NodePattern.fromInterval(interval))));
|
|
862
|
+
}
|
|
863
|
+
|
|
749
864
|
const inner = parseOr(lexer);
|
|
750
865
|
if (!inner.ok) return inner;
|
|
751
866
|
|
|
@@ -847,6 +962,24 @@ function parseDigest(lexer: Lexer): Result<Pattern> {
|
|
|
847
962
|
return ok(digestPrefix(digestToken.token.value.value));
|
|
848
963
|
}
|
|
849
964
|
|
|
965
|
+
// Accept raw hex string identifiers: digest(a1b2c3)
|
|
966
|
+
if (digestToken.token.type === "Identifier") {
|
|
967
|
+
const hexStr = digestToken.token.value;
|
|
968
|
+
// Validate hex string: must be even length and all hex digits
|
|
969
|
+
if (hexStr.length === 0 || hexStr.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hexStr)) {
|
|
970
|
+
return err({ type: "InvalidHexString", span: digestToken.span });
|
|
971
|
+
}
|
|
972
|
+
const bytes = new Uint8Array(hexStr.length / 2);
|
|
973
|
+
for (let i = 0; i < hexStr.length; i += 2) {
|
|
974
|
+
bytes[i / 2] = Number.parseInt(hexStr.slice(i, i + 2), 16);
|
|
975
|
+
}
|
|
976
|
+
const close = lexer.next();
|
|
977
|
+
if (close?.token.type !== "ParenClose") {
|
|
978
|
+
return err({ type: "ExpectedCloseParen", span: lexer.span() });
|
|
979
|
+
}
|
|
980
|
+
return ok(digestPrefix(bytes));
|
|
981
|
+
}
|
|
982
|
+
|
|
850
983
|
return err(unexpectedToken(digestToken.token, digestToken.span));
|
|
851
984
|
}
|
|
852
985
|
|
package/src/parse/token.ts
CHANGED
|
@@ -99,7 +99,8 @@ export type Token =
|
|
|
99
99
|
| { readonly type: "DatePattern"; readonly value: Result<string> }
|
|
100
100
|
| { readonly type: "Range"; readonly value: Result<Quantifier> }
|
|
101
101
|
| { readonly type: "SingleQuotedPattern"; readonly value: Result<string> }
|
|
102
|
-
| { readonly type: "SingleQuotedRegex"; readonly value: Result<string> }
|
|
102
|
+
| { readonly type: "SingleQuotedRegex"; readonly value: Result<string> }
|
|
103
|
+
| { readonly type: "Identifier"; readonly value: string };
|
|
103
104
|
|
|
104
105
|
/**
|
|
105
106
|
* Keyword to token type mapping.
|
|
@@ -875,8 +876,8 @@ export class Lexer {
|
|
|
875
876
|
return { token: keyword, span: this.span() };
|
|
876
877
|
}
|
|
877
878
|
|
|
878
|
-
// Unknown identifier -
|
|
879
|
-
return
|
|
879
|
+
// Unknown identifier - return as Identifier token
|
|
880
|
+
return { token: { type: "Identifier", value: ident }, span: this.span() };
|
|
880
881
|
}
|
|
881
882
|
|
|
882
883
|
// Unknown character
|
package/src/pattern/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
|
|
20
20
|
import type { Path } from "../format";
|
|
21
21
|
import type { Instr } from "./vm";
|
|
22
|
+
import { compile, run } from "./vm";
|
|
22
23
|
|
|
23
24
|
// Re-export sub-modules
|
|
24
25
|
export * from "./leaf";
|
|
@@ -104,6 +105,7 @@ import {
|
|
|
104
105
|
registerNodePatternFactory,
|
|
105
106
|
registerObscuredPatternFactory,
|
|
106
107
|
registerWrappedPatternFactory,
|
|
108
|
+
registerWrappedPatternDispatch,
|
|
107
109
|
} from "./structure";
|
|
108
110
|
|
|
109
111
|
// Import meta patterns
|
|
@@ -176,13 +178,57 @@ export function patternMeta(meta: MetaPattern): Pattern {
|
|
|
176
178
|
// Pattern Matcher Implementation
|
|
177
179
|
// ============================================================================
|
|
178
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Check if a pattern requires the VM for execution.
|
|
183
|
+
* Patterns containing Group (repeat), Traverse, or Search require compilation.
|
|
184
|
+
*/
|
|
185
|
+
function patternNeedsVM(pattern: Pattern): boolean {
|
|
186
|
+
switch (pattern.type) {
|
|
187
|
+
case "Leaf":
|
|
188
|
+
case "Structure":
|
|
189
|
+
return false;
|
|
190
|
+
case "Meta":
|
|
191
|
+
return metaPatternNeedsVM(pattern.pattern);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function metaPatternNeedsVM(pattern: MetaPattern): boolean {
|
|
196
|
+
switch (pattern.type) {
|
|
197
|
+
case "Any":
|
|
198
|
+
return false;
|
|
199
|
+
case "And":
|
|
200
|
+
return pattern.pattern.patterns().some(patternNeedsVM);
|
|
201
|
+
case "Or":
|
|
202
|
+
return pattern.pattern.patterns().some(patternNeedsVM);
|
|
203
|
+
case "Not":
|
|
204
|
+
return patternNeedsVM(pattern.pattern.pattern());
|
|
205
|
+
case "Capture":
|
|
206
|
+
return patternNeedsVM(pattern.pattern.pattern());
|
|
207
|
+
case "Search":
|
|
208
|
+
case "Traverse":
|
|
209
|
+
return true;
|
|
210
|
+
case "Group": {
|
|
211
|
+
// Group with exactly(1) is simple pass-through, doesn't need VM
|
|
212
|
+
const q = pattern.pattern.quantifier();
|
|
213
|
+
if (q.min() === 1 && q.max() === 1) {
|
|
214
|
+
return patternNeedsVM(pattern.pattern.pattern());
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
179
221
|
/**
|
|
180
222
|
* Gets paths with captures for a pattern.
|
|
223
|
+
* Routes through the VM for complex patterns that require compilation.
|
|
181
224
|
*/
|
|
182
225
|
export function patternPathsWithCaptures(
|
|
183
226
|
pattern: Pattern,
|
|
184
227
|
haystack: Envelope,
|
|
185
228
|
): [Path[], Map<string, Path[]>] {
|
|
229
|
+
if (patternNeedsVM(pattern)) {
|
|
230
|
+
return patternPathsWithCapturesViaVM(pattern, haystack);
|
|
231
|
+
}
|
|
186
232
|
switch (pattern.type) {
|
|
187
233
|
case "Leaf":
|
|
188
234
|
return leafPatternPathsWithCaptures(pattern.pattern, haystack);
|
|
@@ -193,6 +239,31 @@ export function patternPathsWithCaptures(
|
|
|
193
239
|
}
|
|
194
240
|
}
|
|
195
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Execute a pattern through the VM, merging results.
|
|
244
|
+
*/
|
|
245
|
+
function patternPathsWithCapturesViaVM(
|
|
246
|
+
pattern: Pattern,
|
|
247
|
+
haystack: Envelope,
|
|
248
|
+
): [Path[], Map<string, Path[]>] {
|
|
249
|
+
const prog = compile(pattern);
|
|
250
|
+
const results = run(prog, haystack);
|
|
251
|
+
|
|
252
|
+
const allPaths: Path[] = [];
|
|
253
|
+
const mergedCaptures = new Map<string, Path[]>();
|
|
254
|
+
|
|
255
|
+
for (const [path, captures] of results) {
|
|
256
|
+
allPaths.push(path);
|
|
257
|
+
for (const [name, paths] of captures) {
|
|
258
|
+
const existing = mergedCaptures.get(name) ?? [];
|
|
259
|
+
existing.push(...paths);
|
|
260
|
+
mergedCaptures.set(name, existing);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return [allPaths, mergedCaptures];
|
|
265
|
+
}
|
|
266
|
+
|
|
196
267
|
/**
|
|
197
268
|
* Gets paths for a pattern.
|
|
198
269
|
*/
|
|
@@ -347,6 +418,27 @@ export function dateRange(earliest: CborDate, latest: CborDate): Pattern {
|
|
|
347
418
|
return patternLeaf(leafDate(DatePattern.range(earliest, latest)));
|
|
348
419
|
}
|
|
349
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Creates a new Pattern that matches Date values on or after the specified date.
|
|
423
|
+
*/
|
|
424
|
+
export function dateEarliest(d: CborDate): Pattern {
|
|
425
|
+
return patternLeaf(leafDate(DatePattern.earliest(d)));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Creates a new Pattern that matches Date values on or before the specified date.
|
|
430
|
+
*/
|
|
431
|
+
export function dateLatest(d: CborDate): Pattern {
|
|
432
|
+
return patternLeaf(leafDate(DatePattern.latest(d)));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Creates a new Pattern that matches Date values whose ISO-8601 string matches the regex.
|
|
437
|
+
*/
|
|
438
|
+
export function dateRegex(pattern: RegExp): Pattern {
|
|
439
|
+
return patternLeaf(leafDate(DatePattern.regex(pattern)));
|
|
440
|
+
}
|
|
441
|
+
|
|
350
442
|
/**
|
|
351
443
|
* Creates a new Pattern that matches any number value.
|
|
352
444
|
*/
|
|
@@ -700,6 +792,11 @@ function registerAllFactories(): void {
|
|
|
700
792
|
registerNodePatternFactory((p) => patternStructure(structureNode(p)));
|
|
701
793
|
registerObscuredPatternFactory((p) => patternStructure(structureObscured(p)));
|
|
702
794
|
registerWrappedPatternFactory((p) => patternStructure(structureWrapped(p)));
|
|
795
|
+
registerWrappedPatternDispatch({
|
|
796
|
+
pathsWithCaptures: patternPathsWithCaptures,
|
|
797
|
+
compile: patternCompile,
|
|
798
|
+
toString: patternToString,
|
|
799
|
+
});
|
|
703
800
|
|
|
704
801
|
// Meta pattern factories
|
|
705
802
|
registerAnyPatternFactory((p) => patternMeta(metaAny(p)));
|
|
@@ -719,6 +816,17 @@ registerAllFactories();
|
|
|
719
816
|
import { registerVMPatternFunctions } from "./vm";
|
|
720
817
|
registerVMPatternFunctions(patternPathsWithCaptures, patternMatches, patternPaths);
|
|
721
818
|
|
|
722
|
-
// Register pattern
|
|
723
|
-
import { registerPatternMatchFn } from "./matcher";
|
|
819
|
+
// Register pattern dispatch functions for meta patterns
|
|
820
|
+
import { registerPatternMatchFn, registerPatternDispatchFns } from "./matcher";
|
|
724
821
|
registerPatternMatchFn(patternMatches);
|
|
822
|
+
registerPatternDispatchFns({
|
|
823
|
+
pathsWithCaptures: patternPathsWithCaptures,
|
|
824
|
+
paths: patternPaths,
|
|
825
|
+
compile: patternCompile,
|
|
826
|
+
isComplex: patternIsComplex,
|
|
827
|
+
toString: patternToString,
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// Register traverse dispatch functions to resolve circular dependencies
|
|
831
|
+
import { registerTraverseDispatchFunctions } from "./meta/traverse-pattern";
|
|
832
|
+
registerTraverseDispatchFunctions(patternPathsWithCaptures, patternCompile, patternIsComplex);
|
|
@@ -98,7 +98,7 @@ export class ArrayPattern implements Matcher {
|
|
|
98
98
|
|
|
99
99
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
100
100
|
// Try to extract CBOR from the envelope
|
|
101
|
-
const cbor = haystack.asLeaf();
|
|
101
|
+
const cbor = haystack.subject().asLeaf();
|
|
102
102
|
if (cbor === undefined) {
|
|
103
103
|
return [[], new Map<string, Path[]>()];
|
|
104
104
|
}
|
|
@@ -72,7 +72,7 @@ export class BoolPattern implements Matcher {
|
|
|
72
72
|
|
|
73
73
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
74
74
|
// For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
|
|
75
|
-
const cbor = haystack.asLeaf();
|
|
75
|
+
const cbor = haystack.subject().asLeaf();
|
|
76
76
|
if (cbor !== undefined) {
|
|
77
77
|
// Delegate to dcbor-pattern for CBOR matching
|
|
78
78
|
const dcborPaths = dcborBoolPatternPaths(this._inner, cbor);
|
|
@@ -82,7 +82,7 @@ export class ByteStringPattern implements Matcher {
|
|
|
82
82
|
|
|
83
83
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
84
84
|
// For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
|
|
85
|
-
const cbor = haystack.asLeaf();
|
|
85
|
+
const cbor = haystack.subject().asLeaf();
|
|
86
86
|
if (cbor !== undefined) {
|
|
87
87
|
// Delegate to dcbor-pattern for CBOR matching
|
|
88
88
|
const dcborPaths = dcborByteStringPatternPaths(this._inner, cbor);
|
|
@@ -156,7 +156,7 @@ export class CBORPattern implements Matcher {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
159
|
-
const envCase = haystack.case();
|
|
159
|
+
const envCase = haystack.subject().case();
|
|
160
160
|
|
|
161
161
|
// Special case for KnownValue envelope
|
|
162
162
|
if (envCase.type === "knownValue") {
|
|
@@ -206,7 +206,7 @@ export class CBORPattern implements Matcher {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// Standard case for CBOR leaf
|
|
209
|
-
const leafCbor = haystack.asLeaf();
|
|
209
|
+
const leafCbor = haystack.subject().asLeaf();
|
|
210
210
|
if (leafCbor === undefined) {
|
|
211
211
|
return [[], new Map<string, Path[]>()];
|
|
212
212
|
}
|
|
@@ -114,7 +114,7 @@ export class DatePattern implements Matcher {
|
|
|
114
114
|
|
|
115
115
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
116
116
|
// For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
|
|
117
|
-
const cbor = haystack.asLeaf();
|
|
117
|
+
const cbor = haystack.subject().asLeaf();
|
|
118
118
|
if (cbor !== undefined) {
|
|
119
119
|
// Delegate to dcbor-pattern for CBOR matching
|
|
120
120
|
const dcborPaths = dcborDatePatternPaths(this._inner, cbor);
|
|
@@ -141,8 +141,7 @@ export function leafPatternPathsWithCaptures(
|
|
|
141
141
|
): [Path[], Map<string, Path[]>] {
|
|
142
142
|
switch (pattern.type) {
|
|
143
143
|
case "Cbor":
|
|
144
|
-
|
|
145
|
-
return [[], new Map<string, Path[]>()];
|
|
144
|
+
return pattern.pattern.pathsWithCaptures(haystack);
|
|
146
145
|
case "Number":
|
|
147
146
|
return pattern.pattern.pathsWithCaptures(haystack);
|
|
148
147
|
case "Text":
|
|
@@ -90,8 +90,9 @@ export class KnownValuePattern implements Matcher {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
93
|
-
// Check if the envelope is a known value via case()
|
|
94
|
-
const
|
|
93
|
+
// Check if the envelope subject is a known value via case()
|
|
94
|
+
const subject = haystack.subject();
|
|
95
|
+
const envCase = subject.case();
|
|
95
96
|
|
|
96
97
|
if (envCase.type === "knownValue") {
|
|
97
98
|
// Get the KnownValue and create CBOR for pattern matching
|
|
@@ -102,7 +103,7 @@ export class KnownValuePattern implements Matcher {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
// Also try matching as a leaf (for tagged CBOR containing known values)
|
|
105
|
-
const leafCbor =
|
|
106
|
+
const leafCbor = subject.asLeaf();
|
|
106
107
|
if (leafCbor !== undefined) {
|
|
107
108
|
if (knownValuePatternMatches(this._inner, leafCbor)) {
|
|
108
109
|
return [[[haystack]], new Map<string, Path[]>()];
|
|
@@ -67,7 +67,7 @@ export class MapPattern implements Matcher {
|
|
|
67
67
|
|
|
68
68
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
69
69
|
// Try to extract CBOR from the envelope
|
|
70
|
-
const cbor = haystack.asLeaf();
|
|
70
|
+
const cbor = haystack.subject().asLeaf();
|
|
71
71
|
if (cbor === undefined) {
|
|
72
72
|
return [[], new Map<string, Path[]>()];
|
|
73
73
|
}
|
|
@@ -58,7 +58,7 @@ export class NullPattern implements Matcher {
|
|
|
58
58
|
|
|
59
59
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
60
60
|
// For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
|
|
61
|
-
const cbor = haystack.asLeaf();
|
|
61
|
+
const cbor = haystack.subject().asLeaf();
|
|
62
62
|
if (cbor !== undefined) {
|
|
63
63
|
// Delegate to dcbor-pattern for CBOR matching
|
|
64
64
|
const dcborPaths = dcborNullPatternPaths(this._inner, cbor);
|
|
@@ -136,7 +136,7 @@ export class NumberPattern implements Matcher {
|
|
|
136
136
|
|
|
137
137
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
138
138
|
// For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
|
|
139
|
-
const cbor = haystack.asLeaf();
|
|
139
|
+
const cbor = haystack.subject().asLeaf();
|
|
140
140
|
if (cbor !== undefined) {
|
|
141
141
|
// Delegate to dcbor-pattern for CBOR matching
|
|
142
142
|
const dcborPaths = dcborNumberPatternPaths(this._inner, cbor);
|
|
@@ -80,7 +80,7 @@ export class TextPattern implements Matcher {
|
|
|
80
80
|
|
|
81
81
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
82
82
|
// For leaf envelopes, extract the CBOR and delegate to dcbor-pattern
|
|
83
|
-
const cbor = haystack.asLeaf();
|
|
83
|
+
const cbor = haystack.subject().asLeaf();
|
|
84
84
|
if (cbor !== undefined) {
|
|
85
85
|
// Delegate to dcbor-pattern for CBOR matching
|
|
86
86
|
const dcborPaths = dcborTextPatternPaths(this._inner, cbor);
|