@bcts/envelope-pattern 1.0.0-alpha.23 → 1.0.0-beta.0
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/dist/index.cjs +1302 -766
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -59
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +102 -60
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +1299 -763
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +1299 -766
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
- package/src/format.ts +19 -31
- package/src/parse/index.ts +16 -1009
- package/src/parse/leaf/array-parser.ts +36 -0
- package/src/parse/leaf/cbor-parser.ts +43 -0
- package/src/parse/leaf/date-parser.ts +81 -0
- package/src/parse/leaf/known-value-parser.ts +73 -0
- package/src/parse/leaf/null-parser.ts +16 -0
- package/src/parse/leaf/number-parser.ts +90 -0
- package/src/parse/leaf/tag-parser.ts +160 -0
- package/src/parse/meta/and-parser.ts +40 -0
- package/src/parse/meta/capture-parser.ts +50 -0
- package/src/parse/meta/group-parser.ts +77 -0
- package/src/parse/meta/not-parser.ts +30 -0
- package/src/parse/meta/or-parser.ts +36 -0
- package/src/parse/meta/primary-parser.ts +234 -0
- package/src/parse/meta/search-parser.ts +41 -0
- package/src/parse/meta/traverse-parser.ts +42 -0
- package/src/parse/structure/assertion-obj-parser.ts +44 -0
- package/src/parse/structure/assertion-parser.ts +22 -0
- package/src/parse/structure/assertion-pred-parser.ts +45 -0
- package/src/parse/structure/compressed-parser.ts +17 -0
- package/src/parse/structure/digest-parser.ts +132 -0
- package/src/parse/structure/elided-parser.ts +17 -0
- package/src/parse/structure/encrypted-parser.ts +17 -0
- package/src/parse/structure/node-parser.ts +54 -0
- package/src/parse/structure/object-parser.ts +32 -0
- package/src/parse/structure/obscured-parser.ts +17 -0
- package/src/parse/structure/predicate-parser.ts +32 -0
- package/src/parse/structure/subject-parser.ts +32 -0
- package/src/parse/structure/wrapped-parser.ts +36 -0
- package/src/pattern/dcbor-integration.ts +40 -8
- package/src/pattern/index.ts +29 -0
- package/src/pattern/leaf/array-pattern.ts +67 -169
- package/src/pattern/leaf/cbor-pattern.ts +37 -23
- package/src/pattern/leaf/index.ts +1 -1
- package/src/pattern/leaf/map-pattern.ts +21 -2
- package/src/pattern/leaf/tagged-pattern.ts +6 -1
- package/src/pattern/meta/search-pattern.ts +13 -38
- package/src/pattern/meta/traverse-pattern.ts +2 -2
- package/src/pattern/structure/assertions-pattern.ts +19 -53
- package/src/pattern/structure/digest-pattern.ts +18 -22
- package/src/pattern/structure/index.ts +3 -0
- package/src/pattern/structure/node-pattern.ts +10 -29
- package/src/pattern/structure/object-pattern.ts +2 -2
- package/src/pattern/structure/predicate-pattern.ts +2 -2
- package/src/pattern/structure/subject-pattern.ts +31 -4
- package/src/pattern/structure/wrapped-pattern.ts +28 -9
- package/src/pattern/vm.ts +4 -4
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { Envelope } from "@bcts/envelope";
|
|
14
14
|
import type { Cbor } from "@bcts/dcbor";
|
|
15
|
-
import { cbor as toCbor, type CborInput } from "@bcts/dcbor";
|
|
15
|
+
import { cbor as toCbor, cborData, cborEquals, type CborInput } from "@bcts/dcbor";
|
|
16
16
|
import {
|
|
17
17
|
type Pattern as DCBORPattern,
|
|
18
18
|
patternPathsWithCaptures as dcborPatternPathsWithCaptures,
|
|
@@ -110,6 +110,13 @@ export class CBORPattern implements Matcher {
|
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Convert a single dcbor path to an envelope path.
|
|
113
|
+
*
|
|
114
|
+
* Uses canonical CBOR-byte equality (`cborEquals`) for the "skip the
|
|
115
|
+
* dcbor root if it duplicates our base envelope" check, mirroring
|
|
116
|
+
* Rust's `dcbor_path.first().map(|first| first == &base_cbor)`. The
|
|
117
|
+
* earlier port compared diagnostic strings, which collapses values
|
|
118
|
+
* that share a textual representation but differ structurally
|
|
119
|
+
* (e.g. NaN payloads).
|
|
113
120
|
*/
|
|
114
121
|
private _convertDcborPathToEnvelopePath(
|
|
115
122
|
dcborPath: Cbor[],
|
|
@@ -118,14 +125,12 @@ export class CBORPattern implements Matcher {
|
|
|
118
125
|
): Envelope[] {
|
|
119
126
|
const envelopePath: Envelope[] = [baseEnvelope];
|
|
120
127
|
|
|
121
|
-
|
|
122
|
-
const skipFirst =
|
|
123
|
-
dcborPath.length > 0 && dcborPath[0]?.toDiagnostic() === baseCbor.toDiagnostic();
|
|
128
|
+
const first = dcborPath[0];
|
|
129
|
+
const skipFirst = first !== undefined && cborEquals(first, baseCbor);
|
|
124
130
|
|
|
125
131
|
const elementsToAdd = skipFirst ? dcborPath.slice(1) : dcborPath;
|
|
126
132
|
|
|
127
133
|
for (const cborElement of elementsToAdd) {
|
|
128
|
-
// Use newLeaf to create envelope from CBOR value
|
|
129
134
|
envelopePath.push(Envelope.newLeaf(cborElement));
|
|
130
135
|
}
|
|
131
136
|
|
|
@@ -162,7 +167,10 @@ export class CBORPattern implements Matcher {
|
|
|
162
167
|
pathsWithCaptures(haystack: Envelope): [Path[], Map<string, Path[]>] {
|
|
163
168
|
const envCase = haystack.subject().case();
|
|
164
169
|
|
|
165
|
-
// Special case for KnownValue envelope
|
|
170
|
+
// Special case for KnownValue envelope. Rust uses `known_value.to_cbor()`,
|
|
171
|
+
// and the `From<KnownValue> for CBOR` impl returns
|
|
172
|
+
// `tagged_cbor()` — i.e. the same tagged form `taggedCbor()` produces
|
|
173
|
+
// here.
|
|
166
174
|
if (envCase.type === "knownValue") {
|
|
167
175
|
const knownValue = envCase.value;
|
|
168
176
|
const knownValueCbor = knownValue.taggedCbor();
|
|
@@ -171,8 +179,8 @@ export class CBORPattern implements Matcher {
|
|
|
171
179
|
case "Any":
|
|
172
180
|
return [[[haystack]], new Map<string, Path[]>()];
|
|
173
181
|
case "Value": {
|
|
174
|
-
//
|
|
175
|
-
if (knownValueCbor
|
|
182
|
+
// Use canonical CBOR equality (mirrors Rust `==`).
|
|
183
|
+
if (cborEquals(knownValueCbor, this._pattern.cbor)) {
|
|
176
184
|
return [[[haystack]], new Map<string, Path[]>()];
|
|
177
185
|
}
|
|
178
186
|
return [[], new Map<string, Path[]>()];
|
|
@@ -220,8 +228,8 @@ export class CBORPattern implements Matcher {
|
|
|
220
228
|
return [[[haystack]], new Map<string, Path[]>()];
|
|
221
229
|
|
|
222
230
|
case "Value":
|
|
223
|
-
//
|
|
224
|
-
if (leafCbor
|
|
231
|
+
// Canonical CBOR-byte equality, mirroring Rust `==`.
|
|
232
|
+
if (cborEquals(leafCbor, this._pattern.cbor)) {
|
|
225
233
|
return [[[haystack]], new Map<string, Path[]>()];
|
|
226
234
|
}
|
|
227
235
|
return [[], new Map<string, Path[]>()];
|
|
@@ -237,9 +245,8 @@ export class CBORPattern implements Matcher {
|
|
|
237
245
|
|
|
238
246
|
const envelopePaths: Path[] = dcborPaths.map((dcborPath: Cbor[]) => {
|
|
239
247
|
const extendedPath = [...basePath];
|
|
240
|
-
|
|
241
|
-
const skipFirst =
|
|
242
|
-
dcborPath.length > 0 && dcborPath[0]?.toDiagnostic() === leafCbor.toDiagnostic();
|
|
248
|
+
const first = dcborPath[0];
|
|
249
|
+
const skipFirst = first !== undefined && cborEquals(first, leafCbor);
|
|
243
250
|
|
|
244
251
|
const elementsToAdd = skipFirst ? dcborPath.slice(1) : dcborPath;
|
|
245
252
|
|
|
@@ -303,7 +310,10 @@ export class CBORPattern implements Matcher {
|
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
/**
|
|
306
|
-
* Equality comparison.
|
|
313
|
+
* Equality comparison. `Value` variants compare by canonical CBOR
|
|
314
|
+
* byte sequence (mirrors Rust `==` on `CBOR`); `Pattern` variants fall
|
|
315
|
+
* back to display-string compare since `DCBORPattern` doesn't expose
|
|
316
|
+
* structural equality outside the crate.
|
|
307
317
|
*/
|
|
308
318
|
equals(other: CBORPattern): boolean {
|
|
309
319
|
if (this._pattern.type !== other._pattern.type) {
|
|
@@ -313,13 +323,11 @@ export class CBORPattern implements Matcher {
|
|
|
313
323
|
case "Any":
|
|
314
324
|
return true;
|
|
315
325
|
case "Value":
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
(other._pattern as { type: "Value"; cbor: Cbor }).cbor.toDiagnostic()
|
|
326
|
+
return cborEquals(
|
|
327
|
+
this._pattern.cbor,
|
|
328
|
+
(other._pattern as { type: "Value"; cbor: Cbor }).cbor,
|
|
320
329
|
);
|
|
321
330
|
case "Pattern":
|
|
322
|
-
// Compare using display representation
|
|
323
331
|
return (
|
|
324
332
|
dcborPatternDisplay(this._pattern.pattern) ===
|
|
325
333
|
dcborPatternDisplay(
|
|
@@ -336,11 +344,17 @@ export class CBORPattern implements Matcher {
|
|
|
336
344
|
switch (this._pattern.type) {
|
|
337
345
|
case "Any":
|
|
338
346
|
return 0;
|
|
339
|
-
case "Value":
|
|
340
|
-
//
|
|
341
|
-
|
|
347
|
+
case "Value": {
|
|
348
|
+
// Hash the canonical CBOR-byte representation so two values
|
|
349
|
+
// that compare equal under `cborEquals` always hash the same.
|
|
350
|
+
const bytes = cborData(this._pattern.cbor);
|
|
351
|
+
let hash = 0;
|
|
352
|
+
for (const byte of bytes) {
|
|
353
|
+
hash = (hash * 31 + byte) | 0;
|
|
354
|
+
}
|
|
355
|
+
return hash;
|
|
356
|
+
}
|
|
342
357
|
case "Pattern":
|
|
343
|
-
// Simple hash based on display string
|
|
344
358
|
return simpleStringHash(dcborPatternDisplay(this._pattern.pattern));
|
|
345
359
|
}
|
|
346
360
|
}
|
|
@@ -22,7 +22,7 @@ export { NumberPattern, registerNumberPatternFactory } from "./number-pattern";
|
|
|
22
22
|
export { TextPattern, registerTextPatternFactory } from "./text-pattern";
|
|
23
23
|
export { ByteStringPattern, registerByteStringPatternFactory } from "./byte-string-pattern";
|
|
24
24
|
export { DatePattern, registerDatePatternFactory } from "./date-pattern";
|
|
25
|
-
export { ArrayPattern,
|
|
25
|
+
export { ArrayPattern, registerArrayPatternFactory } from "./array-pattern";
|
|
26
26
|
export { MapPattern, type MapPatternType, registerMapPatternFactory } from "./map-pattern";
|
|
27
27
|
export { KnownValuePattern, registerKnownValuePatternFactory } from "./known-value-pattern";
|
|
28
28
|
export { TaggedPattern, registerTaggedPatternFactory } from "./tagged-pattern";
|
|
@@ -62,6 +62,17 @@ export class MapPattern implements Matcher {
|
|
|
62
62
|
return new MapPattern({ type: "Interval", interval });
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new MapPattern from a length Interval.
|
|
67
|
+
*
|
|
68
|
+
* Mirrors Rust `MapPattern::from_interval`. Used by the
|
|
69
|
+
* dcbor-pattern → envelope-pattern bridge to preserve `{{n,m}}`
|
|
70
|
+
* length info.
|
|
71
|
+
*/
|
|
72
|
+
static fromInterval(interval: Interval): MapPattern {
|
|
73
|
+
return new MapPattern({ type: "Interval", interval });
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
/**
|
|
66
77
|
* Gets the pattern type.
|
|
67
78
|
*/
|
|
@@ -118,9 +129,17 @@ export class MapPattern implements Matcher {
|
|
|
118
129
|
toString(): string {
|
|
119
130
|
switch (this._pattern.type) {
|
|
120
131
|
case "Any":
|
|
121
|
-
return "
|
|
132
|
+
return "map";
|
|
122
133
|
case "Interval":
|
|
123
|
-
|
|
134
|
+
// Mirror Rust `bc-dcbor-pattern-rust/src/pattern/structure/
|
|
135
|
+
// map_pattern.rs` `MapPattern::Length` Display:
|
|
136
|
+
// `write!(f, "{{{}}}", interval)` → `{<interval>}`.
|
|
137
|
+
// The interval's own `Display` already produces `{n}` /
|
|
138
|
+
// `{n,m}` / `{n,}`, so the final output is `{{n}}` (double
|
|
139
|
+
// brace from the outer literal `{` + interval's `{n}` + `}`).
|
|
140
|
+
// Earlier this port wrapped with `{{...}}` and produced
|
|
141
|
+
// `{{{n}}}` (triple brace), breaking parser parity.
|
|
142
|
+
return `{${this._pattern.interval.toString()}}`;
|
|
124
143
|
}
|
|
125
144
|
}
|
|
126
145
|
|
|
@@ -159,7 +159,12 @@ export class TaggedPattern implements Matcher {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
toString(): string {
|
|
162
|
-
|
|
162
|
+
// Mirror Rust `bc-envelope-pattern-rust/src/pattern/leaf/tagged_pattern.rs`'s
|
|
163
|
+
// `Display`: delegate to the underlying dcbor-pattern `Display`, then
|
|
164
|
+
// normalize the regex-variant's `, ` (comma + two spaces — see
|
|
165
|
+
// `bc-dcbor-pattern-rust/src/pattern/structure/tagged_pattern.rs:239`)
|
|
166
|
+
// back to `, ` so envelope-pattern's surface API stays consistent.
|
|
167
|
+
return taggedPatternDisplay(this._inner, patternDisplay).replace(", ", ", ");
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
/**
|
|
@@ -94,50 +94,25 @@ export class SearchPattern implements Matcher {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* Walk the envelope tree
|
|
97
|
+
* Walk the envelope tree using the canonical `Envelope.walk` traversal.
|
|
98
|
+
*
|
|
99
|
+
* Mirrors Rust `bc_envelope::Envelope::walk(false, vec![], visitor)`
|
|
100
|
+
* which is what `SearchPattern::paths_with_captures` uses. The earlier
|
|
101
|
+
* port hand-rolled a recursion that double-recursed assertions and
|
|
102
|
+
* stepped through wrapped subjects manually, producing a different
|
|
103
|
+
* path order (and extra duplicates that the digest-set deduplication
|
|
104
|
+
* would partially mask).
|
|
98
105
|
*/
|
|
99
106
|
private _walkEnvelope(
|
|
100
107
|
envelope: Envelope,
|
|
101
108
|
pathToCurrent: Envelope[],
|
|
102
109
|
visitor: (envelope: Envelope, path: Envelope[]) => void,
|
|
103
110
|
): void {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const newPath = [...pathToCurrent, envelope];
|
|
110
|
-
|
|
111
|
-
// Walk subject if it's different from this envelope
|
|
112
|
-
if (!subject.digest().equals(envelope.digest())) {
|
|
113
|
-
this._walkEnvelope(subject, newPath, visitor);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Walk assertions
|
|
117
|
-
for (const assertion of envelope.assertions()) {
|
|
118
|
-
this._walkEnvelope(assertion, newPath, visitor);
|
|
119
|
-
|
|
120
|
-
// Walk predicate and object if available
|
|
121
|
-
const predicate = assertion.asPredicate?.();
|
|
122
|
-
if (predicate !== undefined) {
|
|
123
|
-
const assertionPath = [...newPath, assertion];
|
|
124
|
-
this._walkEnvelope(predicate, assertionPath, visitor);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const object = assertion.asObject?.();
|
|
128
|
-
if (object !== undefined) {
|
|
129
|
-
const assertionPath = [...newPath, assertion];
|
|
130
|
-
this._walkEnvelope(object, assertionPath, visitor);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Walk wrapped content if present
|
|
135
|
-
if (subject.isWrapped()) {
|
|
136
|
-
const unwrapped = subject.tryUnwrap?.();
|
|
137
|
-
if (unwrapped !== undefined) {
|
|
138
|
-
this._walkEnvelope(unwrapped, newPath, visitor);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
111
|
+
envelope.walk<Envelope[]>(false, pathToCurrent, (current, _level, _edge, state) => {
|
|
112
|
+
visitor(current, state);
|
|
113
|
+
// Children inherit a path that includes the current node.
|
|
114
|
+
return [[...state, current], false];
|
|
115
|
+
});
|
|
141
116
|
}
|
|
142
117
|
|
|
143
118
|
paths(haystack: Envelope): Path[] {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { Envelope } from "@bcts/envelope";
|
|
14
14
|
import type { Path } from "../../format";
|
|
15
|
-
import type
|
|
15
|
+
import { dispatchPatternToString, type Matcher } from "../matcher";
|
|
16
16
|
import type { Instr } from "../vm";
|
|
17
17
|
import type { Pattern } from "../index";
|
|
18
18
|
|
|
@@ -144,7 +144,7 @@ export class TraversePattern implements Matcher {
|
|
|
144
144
|
|
|
145
145
|
toString(): string {
|
|
146
146
|
return this.patterns()
|
|
147
|
-
.map((p) => (p
|
|
147
|
+
.map((p) => dispatchPatternToString(p))
|
|
148
148
|
.join(" -> ");
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -18,6 +18,7 @@ import type { Pattern } from "../index";
|
|
|
18
18
|
|
|
19
19
|
// Forward declaration for Pattern factory
|
|
20
20
|
let createStructureAssertionsPattern: ((pattern: AssertionsPattern) => Pattern) | undefined;
|
|
21
|
+
let dispatchPatternToString: ((pattern: Pattern) => string) | undefined;
|
|
21
22
|
|
|
22
23
|
export function registerAssertionsPatternFactory(
|
|
23
24
|
factory: (pattern: AssertionsPattern) => Pattern,
|
|
@@ -25,20 +26,22 @@ export function registerAssertionsPatternFactory(
|
|
|
25
26
|
createStructureAssertionsPattern = factory;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export function registerAssertionsPatternToStringDispatch(fn: (pattern: Pattern) => string): void {
|
|
30
|
+
dispatchPatternToString = fn;
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
/**
|
|
29
34
|
* Pattern type for assertions pattern matching.
|
|
30
35
|
*
|
|
31
|
-
* Corresponds to the Rust `AssertionsPattern` enum in assertions_pattern.rs
|
|
36
|
+
* Corresponds to the Rust `AssertionsPattern` enum in assertions_pattern.rs:
|
|
37
|
+
* - `Any` matches any assertion.
|
|
38
|
+
* - `WithPredicate` matches assertions whose predicate matches a sub-pattern.
|
|
39
|
+
* - `WithObject` matches assertions whose object matches a sub-pattern.
|
|
32
40
|
*/
|
|
33
41
|
export type AssertionsPatternType =
|
|
34
42
|
| { readonly type: "Any" }
|
|
35
43
|
| { readonly type: "WithPredicate"; readonly pattern: Pattern }
|
|
36
|
-
| { readonly type: "WithObject"; readonly pattern: Pattern }
|
|
37
|
-
| {
|
|
38
|
-
readonly type: "WithBoth";
|
|
39
|
-
readonly predicatePattern: Pattern;
|
|
40
|
-
readonly objectPattern: Pattern;
|
|
41
|
-
};
|
|
44
|
+
| { readonly type: "WithObject"; readonly pattern: Pattern };
|
|
42
45
|
|
|
43
46
|
/**
|
|
44
47
|
* Pattern for matching assertions in envelopes.
|
|
@@ -75,14 +78,6 @@ export class AssertionsPattern implements Matcher {
|
|
|
75
78
|
return new AssertionsPattern({ type: "WithObject", pattern });
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
/**
|
|
79
|
-
* Creates a new AssertionsPattern that matches assertions with both
|
|
80
|
-
* predicate and object patterns.
|
|
81
|
-
*/
|
|
82
|
-
static withBoth(predicatePattern: Pattern, objectPattern: Pattern): AssertionsPattern {
|
|
83
|
-
return new AssertionsPattern({ type: "WithBoth", predicatePattern, objectPattern });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
81
|
/**
|
|
87
82
|
* Gets the pattern type.
|
|
88
83
|
*/
|
|
@@ -97,9 +92,6 @@ export class AssertionsPattern implements Matcher {
|
|
|
97
92
|
if (this._pattern.type === "WithPredicate") {
|
|
98
93
|
return this._pattern.pattern;
|
|
99
94
|
}
|
|
100
|
-
if (this._pattern.type === "WithBoth") {
|
|
101
|
-
return this._pattern.predicatePattern;
|
|
102
|
-
}
|
|
103
95
|
return undefined;
|
|
104
96
|
}
|
|
105
97
|
|
|
@@ -110,9 +102,6 @@ export class AssertionsPattern implements Matcher {
|
|
|
110
102
|
if (this._pattern.type === "WithObject") {
|
|
111
103
|
return this._pattern.pattern;
|
|
112
104
|
}
|
|
113
|
-
if (this._pattern.type === "WithBoth") {
|
|
114
|
-
return this._pattern.objectPattern;
|
|
115
|
-
}
|
|
116
105
|
return undefined;
|
|
117
106
|
}
|
|
118
107
|
|
|
@@ -142,19 +131,6 @@ export class AssertionsPattern implements Matcher {
|
|
|
142
131
|
}
|
|
143
132
|
break;
|
|
144
133
|
}
|
|
145
|
-
case "WithBoth": {
|
|
146
|
-
const predicate = assertion.asPredicate?.();
|
|
147
|
-
const object = assertion.asObject?.();
|
|
148
|
-
if (predicate !== undefined && object !== undefined) {
|
|
149
|
-
if (
|
|
150
|
-
matchPattern(this._pattern.predicatePattern, predicate) &&
|
|
151
|
-
matchPattern(this._pattern.objectPattern, object)
|
|
152
|
-
) {
|
|
153
|
-
paths.push([assertion]);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
134
|
}
|
|
159
135
|
}
|
|
160
136
|
|
|
@@ -183,15 +159,18 @@ export class AssertionsPattern implements Matcher {
|
|
|
183
159
|
}
|
|
184
160
|
|
|
185
161
|
toString(): string {
|
|
162
|
+
const fmt = dispatchPatternToString;
|
|
186
163
|
switch (this._pattern.type) {
|
|
187
164
|
case "Any":
|
|
188
165
|
return "assert";
|
|
189
|
-
case "WithPredicate":
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
case "
|
|
194
|
-
|
|
166
|
+
case "WithPredicate": {
|
|
167
|
+
const inner = fmt !== undefined ? fmt(this._pattern.pattern) : "?";
|
|
168
|
+
return `assertpred(${inner})`;
|
|
169
|
+
}
|
|
170
|
+
case "WithObject": {
|
|
171
|
+
const inner = fmt !== undefined ? fmt(this._pattern.pattern) : "?";
|
|
172
|
+
return `assertobj(${inner})`;
|
|
173
|
+
}
|
|
195
174
|
}
|
|
196
175
|
}
|
|
197
176
|
|
|
@@ -215,17 +194,6 @@ export class AssertionsPattern implements Matcher {
|
|
|
215
194
|
).pattern;
|
|
216
195
|
return thisPattern === otherPattern;
|
|
217
196
|
}
|
|
218
|
-
case "WithBoth": {
|
|
219
|
-
const otherBoth = other._pattern as {
|
|
220
|
-
type: "WithBoth";
|
|
221
|
-
predicatePattern: Pattern;
|
|
222
|
-
objectPattern: Pattern;
|
|
223
|
-
};
|
|
224
|
-
return (
|
|
225
|
-
this._pattern.predicatePattern === otherBoth.predicatePattern &&
|
|
226
|
-
this._pattern.objectPattern === otherBoth.objectPattern
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
197
|
}
|
|
230
198
|
}
|
|
231
199
|
|
|
@@ -240,8 +208,6 @@ export class AssertionsPattern implements Matcher {
|
|
|
240
208
|
return 1;
|
|
241
209
|
case "WithObject":
|
|
242
210
|
return 2;
|
|
243
|
-
case "WithBoth":
|
|
244
|
-
return 3;
|
|
245
211
|
}
|
|
246
212
|
}
|
|
247
213
|
}
|
|
@@ -39,10 +39,11 @@ function bytesToLatin1(bytes: Uint8Array): string {
|
|
|
39
39
|
/**
|
|
40
40
|
* Pattern type for digest pattern matching.
|
|
41
41
|
*
|
|
42
|
-
* Corresponds to the Rust `DigestPattern` enum in digest_pattern.rs
|
|
42
|
+
* Corresponds to the Rust `DigestPattern` enum in digest_pattern.rs.
|
|
43
|
+
* Rust has only `Digest`, `Prefix`, and `BinaryRegex` variants — there is
|
|
44
|
+
* no `Any` here. To match "any digest", use the meta `any()` pattern.
|
|
43
45
|
*/
|
|
44
46
|
export type DigestPatternType =
|
|
45
|
-
| { readonly type: "Any" }
|
|
46
47
|
| { readonly type: "Digest"; readonly digest: Digest }
|
|
47
48
|
| { readonly type: "Prefix"; readonly prefix: Uint8Array }
|
|
48
49
|
| { readonly type: "BinaryRegex"; readonly regex: RegExp };
|
|
@@ -59,13 +60,6 @@ export class DigestPattern implements Matcher {
|
|
|
59
60
|
this._pattern = pattern;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
/**
|
|
63
|
-
* Creates a new DigestPattern that matches any digest.
|
|
64
|
-
*/
|
|
65
|
-
static any(): DigestPattern {
|
|
66
|
-
return new DigestPattern({ type: "Any" });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
63
|
/**
|
|
70
64
|
* Creates a new DigestPattern that matches the exact digest.
|
|
71
65
|
*/
|
|
@@ -100,10 +94,6 @@ export class DigestPattern implements Matcher {
|
|
|
100
94
|
let isHit = false;
|
|
101
95
|
|
|
102
96
|
switch (this._pattern.type) {
|
|
103
|
-
case "Any":
|
|
104
|
-
// Any digest matches - every envelope has a digest
|
|
105
|
-
isHit = true;
|
|
106
|
-
break;
|
|
107
97
|
case "Digest":
|
|
108
98
|
isHit = digest.equals(this._pattern.digest);
|
|
109
99
|
break;
|
|
@@ -152,8 +142,6 @@ export class DigestPattern implements Matcher {
|
|
|
152
142
|
|
|
153
143
|
toString(): string {
|
|
154
144
|
switch (this._pattern.type) {
|
|
155
|
-
case "Any":
|
|
156
|
-
return "digest";
|
|
157
145
|
case "Digest":
|
|
158
146
|
return `digest(${this._pattern.digest.hex()})`;
|
|
159
147
|
case "Prefix":
|
|
@@ -165,14 +153,17 @@ export class DigestPattern implements Matcher {
|
|
|
165
153
|
|
|
166
154
|
/**
|
|
167
155
|
* Equality comparison.
|
|
156
|
+
*
|
|
157
|
+
* `Prefix` comparison is case-insensitive on the *hex representation* to
|
|
158
|
+
* mirror Rust's `eq_ignore_ascii_case` (which compares the underlying
|
|
159
|
+
* `Vec<u8>` of hex bytes byte-for-byte modulo ASCII case). For raw byte
|
|
160
|
+
* prefixes that happen to be ASCII, this is an ordinary byte compare.
|
|
168
161
|
*/
|
|
169
162
|
equals(other: DigestPattern): boolean {
|
|
170
163
|
if (this._pattern.type !== other._pattern.type) {
|
|
171
164
|
return false;
|
|
172
165
|
}
|
|
173
166
|
switch (this._pattern.type) {
|
|
174
|
-
case "Any":
|
|
175
|
-
return true;
|
|
176
167
|
case "Digest":
|
|
177
168
|
return this._pattern.digest.equals(
|
|
178
169
|
(other._pattern as { type: "Digest"; digest: Digest }).digest,
|
|
@@ -182,7 +173,13 @@ export class DigestPattern implements Matcher {
|
|
|
182
173
|
const otherPrefix = (other._pattern as { type: "Prefix"; prefix: Uint8Array }).prefix;
|
|
183
174
|
if (thisPrefix.length !== otherPrefix.length) return false;
|
|
184
175
|
for (let i = 0; i < thisPrefix.length; i++) {
|
|
185
|
-
|
|
176
|
+
const a = thisPrefix[i];
|
|
177
|
+
const b = otherPrefix[i];
|
|
178
|
+
if (a === b) continue;
|
|
179
|
+
// ASCII case-insensitive compare ('A'..='Z' ↔ 'a'..='z')
|
|
180
|
+
const aLower = a >= 0x41 && a <= 0x5a ? a + 0x20 : a;
|
|
181
|
+
const bLower = b >= 0x41 && b <= 0x5a ? b + 0x20 : b;
|
|
182
|
+
if (aLower !== bLower) return false;
|
|
186
183
|
}
|
|
187
184
|
return true;
|
|
188
185
|
}
|
|
@@ -199,10 +196,7 @@ export class DigestPattern implements Matcher {
|
|
|
199
196
|
*/
|
|
200
197
|
hashCode(): number {
|
|
201
198
|
switch (this._pattern.type) {
|
|
202
|
-
case "Any":
|
|
203
|
-
return 0;
|
|
204
199
|
case "Digest": {
|
|
205
|
-
// Hash based on first few bytes of digest
|
|
206
200
|
const data = this._pattern.digest.data().slice(0, 8);
|
|
207
201
|
let hash = 0;
|
|
208
202
|
for (const byte of data) {
|
|
@@ -213,7 +207,9 @@ export class DigestPattern implements Matcher {
|
|
|
213
207
|
case "Prefix": {
|
|
214
208
|
let hash = 0;
|
|
215
209
|
for (const byte of this._pattern.prefix) {
|
|
216
|
-
|
|
210
|
+
// Fold ASCII case to match equality semantics.
|
|
211
|
+
const folded = byte >= 0x41 && byte <= 0x5a ? byte + 0x20 : byte;
|
|
212
|
+
hash = (hash * 31 + folded) | 0;
|
|
217
213
|
}
|
|
218
214
|
return hash;
|
|
219
215
|
}
|
|
@@ -24,6 +24,7 @@ export {
|
|
|
24
24
|
SubjectPattern,
|
|
25
25
|
type SubjectPatternType,
|
|
26
26
|
registerSubjectPatternFactory,
|
|
27
|
+
registerSubjectPatternDispatch,
|
|
27
28
|
} from "./subject-pattern";
|
|
28
29
|
export {
|
|
29
30
|
PredicatePattern,
|
|
@@ -39,6 +40,7 @@ export {
|
|
|
39
40
|
AssertionsPattern,
|
|
40
41
|
type AssertionsPatternType,
|
|
41
42
|
registerAssertionsPatternFactory,
|
|
43
|
+
registerAssertionsPatternToStringDispatch,
|
|
42
44
|
} from "./assertions-pattern";
|
|
43
45
|
export {
|
|
44
46
|
DigestPattern,
|
|
@@ -56,6 +58,7 @@ export {
|
|
|
56
58
|
type WrappedPatternType,
|
|
57
59
|
registerWrappedPatternFactory,
|
|
58
60
|
registerWrappedPatternDispatch,
|
|
61
|
+
registerWrappedPatternAny,
|
|
59
62
|
} from "./wrapped-pattern";
|
|
60
63
|
|
|
61
64
|
// Import concrete types for use in StructurePattern
|
|
@@ -28,12 +28,14 @@ export function registerNodePatternFactory(factory: (pattern: NodePattern) => Pa
|
|
|
28
28
|
/**
|
|
29
29
|
* Pattern type for node pattern matching.
|
|
30
30
|
*
|
|
31
|
-
* Corresponds to the Rust `NodePattern` enum in node_pattern.rs
|
|
31
|
+
* Corresponds to the Rust `NodePattern` enum in node_pattern.rs:
|
|
32
|
+
* - `Any` matches any node.
|
|
33
|
+
* - `AssertionsInterval` matches a node whose number of assertions falls in
|
|
34
|
+
* the given interval (e.g., `node({2,5})`).
|
|
32
35
|
*/
|
|
33
36
|
export type NodePatternType =
|
|
34
37
|
| { readonly type: "Any" }
|
|
35
|
-
| { readonly type: "AssertionsInterval"; readonly interval: Interval }
|
|
36
|
-
| { readonly type: "WithSubject"; readonly subjectPattern: Pattern };
|
|
38
|
+
| { readonly type: "AssertionsInterval"; readonly interval: Interval };
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Pattern for matching node envelopes.
|
|
@@ -69,13 +71,6 @@ export class NodePattern implements Matcher {
|
|
|
69
71
|
return new NodePattern({ type: "AssertionsInterval", interval });
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
/**
|
|
73
|
-
* Creates a new NodePattern with a subject pattern constraint.
|
|
74
|
-
*/
|
|
75
|
-
static withSubject(subjectPattern: Pattern): NodePattern {
|
|
76
|
-
return new NodePattern({ type: "WithSubject", subjectPattern });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
74
|
/**
|
|
80
75
|
* Gets the pattern type.
|
|
81
76
|
*/
|
|
@@ -84,17 +79,18 @@ export class NodePattern implements Matcher {
|
|
|
84
79
|
}
|
|
85
80
|
|
|
86
81
|
/**
|
|
87
|
-
*
|
|
82
|
+
* Returns the subject pattern, if any. Rust's `NodePattern` does not carry
|
|
83
|
+
* subject patterns, so this always returns `undefined`.
|
|
88
84
|
*/
|
|
89
85
|
subjectPattern(): Pattern | undefined {
|
|
90
|
-
return
|
|
86
|
+
return undefined;
|
|
91
87
|
}
|
|
92
88
|
|
|
93
89
|
/**
|
|
94
|
-
*
|
|
90
|
+
* Returns the assertion patterns. Rust's `NodePattern` does not carry
|
|
91
|
+
* assertion sub-patterns, so this always returns an empty array.
|
|
95
92
|
*/
|
|
96
93
|
assertionPatterns(): Pattern[] {
|
|
97
|
-
// NodePattern doesn't support assertion patterns directly; return empty array
|
|
98
94
|
return [];
|
|
99
95
|
}
|
|
100
96
|
|
|
@@ -112,10 +108,6 @@ export class NodePattern implements Matcher {
|
|
|
112
108
|
case "AssertionsInterval":
|
|
113
109
|
isHit = this._pattern.interval.contains(haystack.assertions().length);
|
|
114
110
|
break;
|
|
115
|
-
case "WithSubject":
|
|
116
|
-
// For WithSubject, we match if the node exists (subject pattern matching done at higher level)
|
|
117
|
-
isHit = true;
|
|
118
|
-
break;
|
|
119
111
|
}
|
|
120
112
|
|
|
121
113
|
const paths = isHit ? [[haystack]] : [];
|
|
@@ -147,8 +139,6 @@ export class NodePattern implements Matcher {
|
|
|
147
139
|
return "node";
|
|
148
140
|
case "AssertionsInterval":
|
|
149
141
|
return `node(${this._pattern.interval.toString()})`;
|
|
150
|
-
case "WithSubject":
|
|
151
|
-
return `node(${(this._pattern.subjectPattern as unknown as { toString(): string }).toString()})`;
|
|
152
142
|
}
|
|
153
143
|
}
|
|
154
144
|
|
|
@@ -166,12 +156,6 @@ export class NodePattern implements Matcher {
|
|
|
166
156
|
return this._pattern.interval.equals(
|
|
167
157
|
(other._pattern as { type: "AssertionsInterval"; interval: Interval }).interval,
|
|
168
158
|
);
|
|
169
|
-
case "WithSubject":
|
|
170
|
-
// Simple reference equality for pattern (could be improved with deep equality)
|
|
171
|
-
return (
|
|
172
|
-
this._pattern.subjectPattern ===
|
|
173
|
-
(other._pattern as { type: "WithSubject"; subjectPattern: Pattern }).subjectPattern
|
|
174
|
-
);
|
|
175
159
|
}
|
|
176
160
|
}
|
|
177
161
|
|
|
@@ -183,10 +167,7 @@ export class NodePattern implements Matcher {
|
|
|
183
167
|
case "Any":
|
|
184
168
|
return 0;
|
|
185
169
|
case "AssertionsInterval":
|
|
186
|
-
// Simple hash based on interval min/max
|
|
187
170
|
return this._pattern.interval.min() * 31 + (this._pattern.interval.max() ?? 0);
|
|
188
|
-
case "WithSubject":
|
|
189
|
-
return 1;
|
|
190
171
|
}
|
|
191
172
|
}
|
|
192
173
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { Envelope } from "@bcts/envelope";
|
|
14
14
|
import type { Path } from "../../format";
|
|
15
|
-
import { matchPattern, type Matcher } from "../matcher";
|
|
15
|
+
import { dispatchPatternToString, matchPattern, type Matcher } from "../matcher";
|
|
16
16
|
import type { Instr } from "../vm";
|
|
17
17
|
import type { Pattern } from "../index";
|
|
18
18
|
|
|
@@ -124,7 +124,7 @@ export class ObjectPattern implements Matcher {
|
|
|
124
124
|
case "Any":
|
|
125
125
|
return "obj";
|
|
126
126
|
case "Pattern":
|
|
127
|
-
return `obj(${(this._pattern.pattern
|
|
127
|
+
return `obj(${dispatchPatternToString(this._pattern.pattern)})`;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|