@bcts/envelope-pattern 1.0.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +48 -0
- package/README.md +13 -0
- package/dist/index.cjs +6781 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2628 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +2628 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +6781 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +6545 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
- package/src/error.ts +262 -0
- package/src/format.ts +375 -0
- package/src/index.ts +27 -0
- package/src/parse/index.ts +923 -0
- package/src/parse/token.ts +906 -0
- package/src/parse/utils.ts +339 -0
- package/src/pattern/index.ts +719 -0
- package/src/pattern/leaf/array-pattern.ts +273 -0
- package/src/pattern/leaf/bool-pattern.ts +140 -0
- package/src/pattern/leaf/byte-string-pattern.ts +172 -0
- package/src/pattern/leaf/cbor-pattern.ts +355 -0
- package/src/pattern/leaf/date-pattern.ts +178 -0
- package/src/pattern/leaf/index.ts +280 -0
- package/src/pattern/leaf/known-value-pattern.ts +192 -0
- package/src/pattern/leaf/map-pattern.ts +152 -0
- package/src/pattern/leaf/null-pattern.ts +110 -0
- package/src/pattern/leaf/number-pattern.ts +248 -0
- package/src/pattern/leaf/tagged-pattern.ts +228 -0
- package/src/pattern/leaf/text-pattern.ts +165 -0
- package/src/pattern/matcher.ts +88 -0
- package/src/pattern/meta/and-pattern.ts +109 -0
- package/src/pattern/meta/any-pattern.ts +81 -0
- package/src/pattern/meta/capture-pattern.ts +111 -0
- package/src/pattern/meta/group-pattern.ts +110 -0
- package/src/pattern/meta/index.ts +269 -0
- package/src/pattern/meta/not-pattern.ts +91 -0
- package/src/pattern/meta/or-pattern.ts +146 -0
- package/src/pattern/meta/search-pattern.ts +201 -0
- package/src/pattern/meta/traverse-pattern.ts +146 -0
- package/src/pattern/structure/assertions-pattern.ts +244 -0
- package/src/pattern/structure/digest-pattern.ts +225 -0
- package/src/pattern/structure/index.ts +272 -0
- package/src/pattern/structure/leaf-structure-pattern.ts +85 -0
- package/src/pattern/structure/node-pattern.ts +188 -0
- package/src/pattern/structure/object-pattern.ts +149 -0
- package/src/pattern/structure/obscured-pattern.ts +159 -0
- package/src/pattern/structure/predicate-pattern.ts +151 -0
- package/src/pattern/structure/subject-pattern.ts +152 -0
- package/src/pattern/structure/wrapped-pattern.ts +195 -0
- package/src/pattern/vm.ts +1021 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/envelope-pattern - Parser utility functions
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-envelope-pattern-rust utils.rs
|
|
5
|
+
*
|
|
6
|
+
* @module envelope-pattern/parse/utils
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
10
|
+
import { type Pattern as DCBORPattern, parse as parseDcborPattern } from "@bcts/dcbor-pattern";
|
|
11
|
+
|
|
12
|
+
// Stub for cborFromDiagnostic - not implemented in dcbor yet
|
|
13
|
+
function cborFromDiagnostic(_src: string): { ok: true; value: Cbor } | { ok: false } {
|
|
14
|
+
// TODO: Implement when dcbor adds diagnostic notation parsing
|
|
15
|
+
return { ok: false };
|
|
16
|
+
}
|
|
17
|
+
import {
|
|
18
|
+
type Result,
|
|
19
|
+
ok,
|
|
20
|
+
err,
|
|
21
|
+
unterminatedRegex,
|
|
22
|
+
invalidRegex,
|
|
23
|
+
invalidRange,
|
|
24
|
+
invalidNumberFormat,
|
|
25
|
+
unexpectedEndOfInput,
|
|
26
|
+
invalidPattern,
|
|
27
|
+
unknown,
|
|
28
|
+
} from "../error";
|
|
29
|
+
import type { Pattern } from "../pattern";
|
|
30
|
+
|
|
31
|
+
// Forward declaration - will be imported from pattern module
|
|
32
|
+
// Using a registry pattern to avoid circular dependencies
|
|
33
|
+
let createCborPattern: ((cbor: Cbor) => Pattern) | undefined;
|
|
34
|
+
let createCborPatternFromDcbor: ((pattern: DCBORPattern) => Pattern) | undefined;
|
|
35
|
+
let createAnyArray: (() => Pattern) | undefined;
|
|
36
|
+
let createArrayWithCount: ((count: number) => Pattern) | undefined;
|
|
37
|
+
let createArrayWithRange: ((min: number, max?: number) => Pattern) | undefined;
|
|
38
|
+
let createArrayFromDcborPattern: ((pattern: DCBORPattern) => Pattern) | undefined;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register pattern factory functions.
|
|
42
|
+
* This is called by the pattern module to avoid circular dependencies.
|
|
43
|
+
*/
|
|
44
|
+
export function registerPatternFactories(factories: {
|
|
45
|
+
cborPattern: (cbor: Cbor) => Pattern;
|
|
46
|
+
cborPatternFromDcbor: (pattern: DCBORPattern) => Pattern;
|
|
47
|
+
anyArray: () => Pattern;
|
|
48
|
+
arrayWithCount: (count: number) => Pattern;
|
|
49
|
+
arrayWithRange: (min: number, max?: number) => Pattern;
|
|
50
|
+
arrayFromDcborPattern: (pattern: DCBORPattern) => Pattern;
|
|
51
|
+
}): void {
|
|
52
|
+
createCborPattern = factories.cborPattern;
|
|
53
|
+
createCborPatternFromDcbor = factories.cborPatternFromDcbor;
|
|
54
|
+
createAnyArray = factories.anyArray;
|
|
55
|
+
createArrayWithCount = factories.arrayWithCount;
|
|
56
|
+
createArrayWithRange = factories.arrayWithRange;
|
|
57
|
+
createArrayFromDcborPattern = factories.arrayFromDcborPattern;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Skips whitespace in the source string.
|
|
62
|
+
*
|
|
63
|
+
* @param src - The source string
|
|
64
|
+
* @param pos - The current position (modified in place)
|
|
65
|
+
*/
|
|
66
|
+
export function skipWs(src: string, pos: { value: number }): void {
|
|
67
|
+
while (pos.value < src.length) {
|
|
68
|
+
const ch = src[pos.value];
|
|
69
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r" || ch === "\f") {
|
|
70
|
+
pos.value++;
|
|
71
|
+
} else {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parses a text regex from the source.
|
|
79
|
+
*
|
|
80
|
+
* @param src - The source string (starting after initial whitespace)
|
|
81
|
+
* @returns The parsed regex and consumed character count, or an error
|
|
82
|
+
*/
|
|
83
|
+
export function parseTextRegex(src: string): Result<[RegExp, number]> {
|
|
84
|
+
const pos = { value: 0 };
|
|
85
|
+
skipWs(src, pos);
|
|
86
|
+
|
|
87
|
+
if (pos.value >= src.length || src[pos.value] !== "/") {
|
|
88
|
+
return err(unterminatedRegex({ start: pos.value, end: pos.value }));
|
|
89
|
+
}
|
|
90
|
+
pos.value++; // skip opening '/'
|
|
91
|
+
|
|
92
|
+
const start = pos.value;
|
|
93
|
+
let escape = false;
|
|
94
|
+
|
|
95
|
+
while (pos.value < src.length) {
|
|
96
|
+
const b = src[pos.value];
|
|
97
|
+
pos.value++;
|
|
98
|
+
|
|
99
|
+
if (escape) {
|
|
100
|
+
escape = false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (b === "\\") {
|
|
105
|
+
escape = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (b === "/") {
|
|
110
|
+
const inner = src.slice(start, pos.value - 1);
|
|
111
|
+
try {
|
|
112
|
+
const regex = new RegExp(inner);
|
|
113
|
+
skipWs(src, pos);
|
|
114
|
+
return ok([regex, pos.value]);
|
|
115
|
+
} catch {
|
|
116
|
+
return err(invalidRegex({ start: pos.value, end: pos.value }));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return err(unterminatedRegex({ start: pos.value, end: pos.value }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parses a CBOR value or dcbor-pattern expression.
|
|
126
|
+
*
|
|
127
|
+
* @param src - The source string
|
|
128
|
+
* @returns The parsed pattern and consumed character count, or an error
|
|
129
|
+
*/
|
|
130
|
+
export function parseCborInner(src: string): Result<[Pattern, number]> {
|
|
131
|
+
if (createCborPattern === undefined || createCborPatternFromDcbor === undefined) {
|
|
132
|
+
return err(unknown());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const pos = { value: 0 };
|
|
136
|
+
skipWs(src, pos);
|
|
137
|
+
|
|
138
|
+
// Check if this is a dcbor-pattern expression (/patex/)
|
|
139
|
+
if (src[pos.value] === "/") {
|
|
140
|
+
pos.value++; // skip opening '/'
|
|
141
|
+
const start = pos.value;
|
|
142
|
+
let escape = false;
|
|
143
|
+
|
|
144
|
+
// Find the closing '/'
|
|
145
|
+
while (pos.value < src.length) {
|
|
146
|
+
const b = src[pos.value];
|
|
147
|
+
pos.value++;
|
|
148
|
+
|
|
149
|
+
if (escape) {
|
|
150
|
+
escape = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (b === "\\") {
|
|
155
|
+
escape = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (b === "/") {
|
|
160
|
+
const patternStr = src.slice(start, pos.value - 1);
|
|
161
|
+
|
|
162
|
+
// Parse the dcbor-pattern expression
|
|
163
|
+
const parseResult = parseDcborPattern(patternStr);
|
|
164
|
+
if (!parseResult.ok) {
|
|
165
|
+
return err(invalidPattern({ start, end: pos.value - 1 }));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
skipWs(src, pos);
|
|
169
|
+
return ok([createCborPatternFromDcbor(parseResult.value), pos.value]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return err(unterminatedRegex({ start: start - 1, end: pos.value }));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check if this is a UR (ur:type/value)
|
|
177
|
+
if (src.slice(pos.value, pos.value + 3) === "ur:") {
|
|
178
|
+
// For now, parse as CBOR diagnostic notation
|
|
179
|
+
// TODO: Add proper UR parsing when available
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Default: parse as CBOR diagnostic notation
|
|
183
|
+
const remaining = src.slice(pos.value);
|
|
184
|
+
const cborResult = cborFromDiagnostic(remaining);
|
|
185
|
+
if (!cborResult.ok) {
|
|
186
|
+
return err(unknown());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Count consumed characters (approximation - full string was consumed)
|
|
190
|
+
const consumed = remaining.length;
|
|
191
|
+
return ok([createCborPattern(cborResult.value), pos.value + consumed]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Parses an array pattern inner content.
|
|
196
|
+
*
|
|
197
|
+
* @param src - The source string (content between [ and ])
|
|
198
|
+
* @returns The parsed pattern and consumed character count, or an error
|
|
199
|
+
*/
|
|
200
|
+
export function parseArrayInner(src: string): Result<[Pattern, number]> {
|
|
201
|
+
if (
|
|
202
|
+
createAnyArray === undefined ||
|
|
203
|
+
createArrayWithCount === undefined ||
|
|
204
|
+
createArrayWithRange === undefined ||
|
|
205
|
+
createArrayFromDcborPattern === undefined
|
|
206
|
+
) {
|
|
207
|
+
return err(unknown());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const pos = { value: 0 };
|
|
211
|
+
skipWs(src, pos);
|
|
212
|
+
|
|
213
|
+
// Check for the simple "*" pattern first - matches any array
|
|
214
|
+
if (src[pos.value] === "*") {
|
|
215
|
+
pos.value++;
|
|
216
|
+
skipWs(src, pos);
|
|
217
|
+
return ok([createAnyArray(), pos.value]);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for length patterns like {n}, {n,m}, {n,}
|
|
221
|
+
if (src[pos.value] === "{") {
|
|
222
|
+
pos.value++;
|
|
223
|
+
skipWs(src, pos);
|
|
224
|
+
|
|
225
|
+
// Parse the first number
|
|
226
|
+
const startPos = pos.value;
|
|
227
|
+
while (pos.value < src.length && src[pos.value] !== undefined && /\d/.test(src[pos.value])) {
|
|
228
|
+
pos.value++;
|
|
229
|
+
}
|
|
230
|
+
if (startPos === pos.value) {
|
|
231
|
+
return err(invalidRange({ start: pos.value, end: pos.value }));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const firstNum = parseInt(src.slice(startPos, pos.value), 10);
|
|
235
|
+
if (Number.isNaN(firstNum)) {
|
|
236
|
+
return err(invalidNumberFormat({ start: startPos, end: pos.value }));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
skipWs(src, pos);
|
|
240
|
+
|
|
241
|
+
if (pos.value >= src.length) {
|
|
242
|
+
return err(unexpectedEndOfInput());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const ch = src[pos.value];
|
|
246
|
+
|
|
247
|
+
if (ch === "}") {
|
|
248
|
+
// {n} - exact count
|
|
249
|
+
pos.value++;
|
|
250
|
+
skipWs(src, pos);
|
|
251
|
+
return ok([createArrayWithCount(firstNum), pos.value]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (ch === ",") {
|
|
255
|
+
pos.value++;
|
|
256
|
+
skipWs(src, pos);
|
|
257
|
+
|
|
258
|
+
if (pos.value >= src.length) {
|
|
259
|
+
return err(unexpectedEndOfInput());
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const nextCh = src[pos.value];
|
|
263
|
+
|
|
264
|
+
if (nextCh === "}") {
|
|
265
|
+
// {n,} - at least n
|
|
266
|
+
pos.value++;
|
|
267
|
+
skipWs(src, pos);
|
|
268
|
+
return ok([createArrayWithRange(firstNum, undefined), pos.value]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (nextCh !== undefined && /\d/.test(nextCh)) {
|
|
272
|
+
// {n,m} - range
|
|
273
|
+
const secondStart = pos.value;
|
|
274
|
+
while (
|
|
275
|
+
pos.value < src.length &&
|
|
276
|
+
src[pos.value] !== undefined &&
|
|
277
|
+
/\d/.test(src[pos.value])
|
|
278
|
+
) {
|
|
279
|
+
pos.value++;
|
|
280
|
+
}
|
|
281
|
+
const secondNum = parseInt(src.slice(secondStart, pos.value), 10);
|
|
282
|
+
if (Number.isNaN(secondNum)) {
|
|
283
|
+
return err(invalidNumberFormat({ start: secondStart, end: pos.value }));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
skipWs(src, pos);
|
|
287
|
+
if (pos.value >= src.length || src[pos.value] !== "}") {
|
|
288
|
+
return err(unexpectedEndOfInput());
|
|
289
|
+
}
|
|
290
|
+
pos.value++;
|
|
291
|
+
skipWs(src, pos);
|
|
292
|
+
return ok([createArrayWithRange(firstNum, secondNum), pos.value]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return err(invalidRange({ start: pos.value, end: pos.value }));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return err(invalidRange({ start: pos.value, end: pos.value }));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// For any other pattern content, delegate to dcbor-pattern
|
|
302
|
+
const patternStr = `[${src.slice(pos.value)}]`;
|
|
303
|
+
const parseResult = parseDcborPattern(patternStr);
|
|
304
|
+
if (!parseResult.ok) {
|
|
305
|
+
return err(invalidPattern({ start: pos.value, end: src.length }));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Create an array pattern that wraps the dcbor-pattern
|
|
309
|
+
const consumed = src.length - pos.value;
|
|
310
|
+
return ok([createArrayFromDcborPattern(parseResult.value), consumed]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Parses a bare word (identifier-like token).
|
|
315
|
+
*
|
|
316
|
+
* @param src - The source string
|
|
317
|
+
* @returns The parsed word and consumed character count, or an error
|
|
318
|
+
*/
|
|
319
|
+
export function parseBareWord(src: string): Result<[string, number]> {
|
|
320
|
+
const pos = { value: 0 };
|
|
321
|
+
skipWs(src, pos);
|
|
322
|
+
|
|
323
|
+
const start = pos.value;
|
|
324
|
+
while (pos.value < src.length) {
|
|
325
|
+
const ch = src[pos.value];
|
|
326
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r" || ch === "\f" || ch === ")") {
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
pos.value++;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (start === pos.value) {
|
|
333
|
+
return err(unexpectedEndOfInput());
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const word = src.slice(start, pos.value);
|
|
337
|
+
skipWs(src, pos);
|
|
338
|
+
return ok([word, pos.value]);
|
|
339
|
+
}
|