@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
package/src/format.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format Module for dcbor-pattern
|
|
3
|
+
*
|
|
4
|
+
* This module provides formatting utilities for displaying paths returned by
|
|
5
|
+
* pattern matching. Unlike `bc-envelope-pattern` which supports digest URs and
|
|
6
|
+
* envelope URs, this module focuses on CBOR diagnostic notation formatting.
|
|
7
|
+
*
|
|
8
|
+
* ## Features
|
|
9
|
+
*
|
|
10
|
+
* - **Diagnostic formatting**: Format CBOR elements using either standard or
|
|
11
|
+
* flat diagnostic notation
|
|
12
|
+
* - **Path indentation**: Automatically indent nested path elements
|
|
13
|
+
* - **Truncation support**: Optionally truncate long representations with
|
|
14
|
+
* ellipsis
|
|
15
|
+
* - **Flexible options**: Choose whether to show all elements or just the
|
|
16
|
+
* final destination
|
|
17
|
+
*
|
|
18
|
+
* @module format
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { type Cbor, summary, diagnosticOpt } from "@bcts/dcbor";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A Path is a sequence of CBOR values representing the traversal from root
|
|
25
|
+
* to a matched element.
|
|
26
|
+
*/
|
|
27
|
+
export type Path = Cbor[];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A builder that provides formatting options for each path element.
|
|
31
|
+
*/
|
|
32
|
+
export enum PathElementFormat {
|
|
33
|
+
/**
|
|
34
|
+
* Diagnostic summary format, with optional maximum length for truncation.
|
|
35
|
+
*/
|
|
36
|
+
DiagnosticSummary = "diagnostic_summary",
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Flat diagnostic format (single line), with optional maximum length for
|
|
40
|
+
* truncation.
|
|
41
|
+
*/
|
|
42
|
+
DiagnosticFlat = "diagnostic_flat",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options for formatting paths.
|
|
47
|
+
*/
|
|
48
|
+
export interface FormatPathsOpts {
|
|
49
|
+
/**
|
|
50
|
+
* Whether to indent each path element.
|
|
51
|
+
* If true, each element will be indented by 4 spaces per level.
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
readonly indent: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format for each path element.
|
|
58
|
+
* @default PathElementFormat.DiagnosticSummary
|
|
59
|
+
*/
|
|
60
|
+
readonly elementFormat: PathElementFormat;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Maximum length for element representation before truncation.
|
|
64
|
+
* If undefined, no truncation is applied.
|
|
65
|
+
*/
|
|
66
|
+
readonly maxLength: number | undefined;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* If true, only the last element of each path will be formatted.
|
|
70
|
+
* This is useful for displaying only the final destination of a path.
|
|
71
|
+
* If false, all elements will be formatted.
|
|
72
|
+
* @default false
|
|
73
|
+
*/
|
|
74
|
+
readonly lastElementOnly: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Default formatting options.
|
|
79
|
+
*/
|
|
80
|
+
export const DEFAULT_FORMAT_OPTS: FormatPathsOpts = {
|
|
81
|
+
indent: true,
|
|
82
|
+
elementFormat: PathElementFormat.DiagnosticSummary,
|
|
83
|
+
maxLength: undefined,
|
|
84
|
+
lastElementOnly: false,
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates formatting options with builder pattern.
|
|
89
|
+
*/
|
|
90
|
+
export class FormatPathsOptsBuilder {
|
|
91
|
+
#opts: FormatPathsOpts;
|
|
92
|
+
|
|
93
|
+
constructor() {
|
|
94
|
+
this.#opts = { ...DEFAULT_FORMAT_OPTS };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates a new builder with default options.
|
|
99
|
+
*/
|
|
100
|
+
static new(): FormatPathsOptsBuilder {
|
|
101
|
+
return new FormatPathsOptsBuilder();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets whether to indent each path element.
|
|
106
|
+
*/
|
|
107
|
+
indent(indent: boolean): FormatPathsOptsBuilder {
|
|
108
|
+
this.#opts = { ...this.#opts, indent };
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Sets the format for each path element.
|
|
114
|
+
*/
|
|
115
|
+
elementFormat(format: PathElementFormat): FormatPathsOptsBuilder {
|
|
116
|
+
this.#opts = { ...this.#opts, elementFormat: format };
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Sets the maximum length for element representation.
|
|
122
|
+
*/
|
|
123
|
+
maxLength(length: number | undefined): FormatPathsOptsBuilder {
|
|
124
|
+
this.#opts = { ...this.#opts, maxLength: length };
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sets whether to format only the last element of each path.
|
|
130
|
+
*/
|
|
131
|
+
lastElementOnly(lastOnly: boolean): FormatPathsOptsBuilder {
|
|
132
|
+
this.#opts = { ...this.#opts, lastElementOnly: lastOnly };
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Builds the options object.
|
|
138
|
+
*/
|
|
139
|
+
build(): FormatPathsOpts {
|
|
140
|
+
return this.#opts;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Truncates a string to the specified maximum length, appending an ellipsis if
|
|
146
|
+
* truncated.
|
|
147
|
+
*
|
|
148
|
+
* @internal
|
|
149
|
+
* @param s - The string to truncate
|
|
150
|
+
* @param maxLength - Maximum length, or undefined for no truncation
|
|
151
|
+
* @returns The possibly truncated string
|
|
152
|
+
*/
|
|
153
|
+
const truncateWithEllipsis = (s: string, maxLength?: number): string => {
|
|
154
|
+
if (maxLength === undefined || s.length <= maxLength) {
|
|
155
|
+
return s;
|
|
156
|
+
}
|
|
157
|
+
if (maxLength > 1) {
|
|
158
|
+
return `${s.slice(0, maxLength - 1)}…`;
|
|
159
|
+
}
|
|
160
|
+
return "…";
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format a single CBOR element according to the specified format.
|
|
165
|
+
*
|
|
166
|
+
* @internal
|
|
167
|
+
* @param cbor - The CBOR value to format
|
|
168
|
+
* @param format - The format to use
|
|
169
|
+
* @param maxLength - Maximum length before truncation
|
|
170
|
+
* @returns The formatted string
|
|
171
|
+
*/
|
|
172
|
+
const formatCborElement = (cbor: Cbor, format: PathElementFormat, maxLength?: number): string => {
|
|
173
|
+
let diagnostic: string;
|
|
174
|
+
|
|
175
|
+
// Use the diagnostic functions from @bcts/dcbor
|
|
176
|
+
if (format === PathElementFormat.DiagnosticSummary) {
|
|
177
|
+
// summary() provides a compact representation with summarizers
|
|
178
|
+
diagnostic = summary(cbor);
|
|
179
|
+
} else {
|
|
180
|
+
// diagnosticOpt with flat: true provides a single-line representation
|
|
181
|
+
diagnostic = diagnosticOpt(cbor, { flat: true });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return truncateWithEllipsis(diagnostic, maxLength);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Format each path element on its own line, each line successively indented by
|
|
189
|
+
* 4 spaces. Options can be provided to customize the formatting.
|
|
190
|
+
*
|
|
191
|
+
* @param path - The path to format
|
|
192
|
+
* @param opts - Formatting options
|
|
193
|
+
* @returns The formatted path string
|
|
194
|
+
*/
|
|
195
|
+
export const formatPathOpt = (path: Path, opts: FormatPathsOpts = DEFAULT_FORMAT_OPTS): string => {
|
|
196
|
+
if (opts.lastElementOnly) {
|
|
197
|
+
// Only format the last element, no indentation.
|
|
198
|
+
const lastElement = path[path.length - 1];
|
|
199
|
+
if (lastElement !== undefined) {
|
|
200
|
+
return formatCborElement(lastElement, opts.elementFormat, opts.maxLength);
|
|
201
|
+
}
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Multi-line output with indentation for diagnostic formats.
|
|
206
|
+
const lines: string[] = [];
|
|
207
|
+
for (let index = 0; index < path.length; index++) {
|
|
208
|
+
const element = path[index];
|
|
209
|
+
const indent = opts.indent ? " ".repeat(index * 4) : "";
|
|
210
|
+
const content = formatCborElement(element, opts.elementFormat, opts.maxLength);
|
|
211
|
+
lines.push(`${indent}${content}`);
|
|
212
|
+
}
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Format each path element on its own line, each line successively indented by
|
|
218
|
+
* 4 spaces.
|
|
219
|
+
*
|
|
220
|
+
* @param path - The path to format
|
|
221
|
+
* @returns The formatted path string
|
|
222
|
+
*/
|
|
223
|
+
export const formatPath = (path: Path): string => {
|
|
224
|
+
return formatPathOpt(path, DEFAULT_FORMAT_OPTS);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Format multiple paths with captures in a structured way.
|
|
229
|
+
* Captures come first, sorted lexicographically by name, with their name
|
|
230
|
+
* prefixed by '@'. Regular paths follow after all captures.
|
|
231
|
+
*
|
|
232
|
+
* @param paths - The paths to format
|
|
233
|
+
* @param captures - Named capture groups and their paths
|
|
234
|
+
* @param opts - Formatting options
|
|
235
|
+
* @returns The formatted string
|
|
236
|
+
*/
|
|
237
|
+
export const formatPathsWithCaptures = (
|
|
238
|
+
paths: Path[],
|
|
239
|
+
captures: Map<string, Path[]>,
|
|
240
|
+
opts: FormatPathsOpts = DEFAULT_FORMAT_OPTS,
|
|
241
|
+
): string => {
|
|
242
|
+
const result: string[] = [];
|
|
243
|
+
|
|
244
|
+
// First, format all captures, sorted lexicographically by name
|
|
245
|
+
const captureNames = Array.from(captures.keys()).sort();
|
|
246
|
+
|
|
247
|
+
for (const captureName of captureNames) {
|
|
248
|
+
const capturePaths = captures.get(captureName);
|
|
249
|
+
if (capturePaths !== undefined) {
|
|
250
|
+
result.push(`@${captureName}`);
|
|
251
|
+
for (const path of capturePaths) {
|
|
252
|
+
const formattedPath = formatPathOpt(path, opts);
|
|
253
|
+
// Add indentation to each line of the formatted path
|
|
254
|
+
for (const line of formattedPath.split("\n")) {
|
|
255
|
+
if (line.length > 0) {
|
|
256
|
+
result.push(` ${line}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Then, format all regular paths
|
|
264
|
+
for (const path of paths) {
|
|
265
|
+
const formattedPath = formatPathOpt(path, opts);
|
|
266
|
+
for (const line of formattedPath.split("\n")) {
|
|
267
|
+
if (line.length > 0) {
|
|
268
|
+
result.push(line);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return result.join("\n");
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Format multiple paths with custom formatting options.
|
|
278
|
+
*
|
|
279
|
+
* @param paths - The paths to format
|
|
280
|
+
* @param opts - Formatting options
|
|
281
|
+
* @returns The formatted string
|
|
282
|
+
*/
|
|
283
|
+
export const formatPathsOpt = (
|
|
284
|
+
paths: Path[],
|
|
285
|
+
opts: FormatPathsOpts = DEFAULT_FORMAT_OPTS,
|
|
286
|
+
): string => {
|
|
287
|
+
// Call formatPathsWithCaptures with empty captures
|
|
288
|
+
return formatPathsWithCaptures(paths, new Map(), opts);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Format multiple paths with default options.
|
|
293
|
+
*
|
|
294
|
+
* @param paths - The paths to format
|
|
295
|
+
* @returns The formatted string
|
|
296
|
+
*/
|
|
297
|
+
export const formatPaths = (paths: Path[]): string => {
|
|
298
|
+
return formatPathsOpt(paths, DEFAULT_FORMAT_OPTS);
|
|
299
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/dcbor-pattern - Pattern matching for dCBOR (Deterministic CBOR)
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-dcbor-pattern-rust.
|
|
5
|
+
*
|
|
6
|
+
* @module dcbor-pattern
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Core types
|
|
10
|
+
export * from "./error";
|
|
11
|
+
export * from "./reluctance";
|
|
12
|
+
export * from "./interval";
|
|
13
|
+
export * from "./quantifier";
|
|
14
|
+
export * from "./format";
|
|
15
|
+
|
|
16
|
+
// Pattern types
|
|
17
|
+
export * from "./pattern";
|
|
18
|
+
|
|
19
|
+
// Parsing
|
|
20
|
+
export * from "./parse";
|
package/src/interval.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides an `Interval` type representing a range of values with a
|
|
3
|
+
* minimum and optional maximum.
|
|
4
|
+
*
|
|
5
|
+
* This module is used in the context of pattern matching for dCBOR items
|
|
6
|
+
* to represent cardinality specifications like `{n}`, `{n,m}`, or `{n,}`
|
|
7
|
+
* in pattern expressions.
|
|
8
|
+
*
|
|
9
|
+
* @module interval
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents an inclusive interval with a minimum value and an optional
|
|
14
|
+
* maximum value.
|
|
15
|
+
*
|
|
16
|
+
* When the maximum is `undefined`, the interval is considered unbounded above.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Single value interval
|
|
21
|
+
* const exact = new Interval(3, 3); // Matches exactly 3
|
|
22
|
+
*
|
|
23
|
+
* // Bounded range
|
|
24
|
+
* const range = new Interval(1, 5); // Matches 1 to 5 inclusive
|
|
25
|
+
*
|
|
26
|
+
* // Unbounded range
|
|
27
|
+
* const unbounded = new Interval(2); // Matches 2 or more
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class Interval {
|
|
31
|
+
readonly #min: number;
|
|
32
|
+
readonly #max: number | undefined;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new Interval.
|
|
36
|
+
*
|
|
37
|
+
* @param min - The minimum value (inclusive)
|
|
38
|
+
* @param max - The maximum value (inclusive), or undefined for unbounded
|
|
39
|
+
*/
|
|
40
|
+
constructor(min: number, max?: number) {
|
|
41
|
+
this.#min = min;
|
|
42
|
+
this.#max = max;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates an interval from a range specification.
|
|
47
|
+
*
|
|
48
|
+
* @param start - The start of the range (inclusive)
|
|
49
|
+
* @param end - The end of the range (inclusive), or undefined for unbounded
|
|
50
|
+
* @returns A new Interval
|
|
51
|
+
*/
|
|
52
|
+
static from(start: number, end?: number): Interval {
|
|
53
|
+
return new Interval(start, end);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates an interval for exactly n occurrences.
|
|
58
|
+
*
|
|
59
|
+
* @param n - The exact count
|
|
60
|
+
* @returns A new Interval with min = max = n
|
|
61
|
+
*/
|
|
62
|
+
static exactly(n: number): Interval {
|
|
63
|
+
return new Interval(n, n);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates an interval for at least n occurrences.
|
|
68
|
+
*
|
|
69
|
+
* @param n - The minimum count
|
|
70
|
+
* @returns A new Interval with min = n and no maximum
|
|
71
|
+
*/
|
|
72
|
+
static atLeast(n: number): Interval {
|
|
73
|
+
return new Interval(n, undefined);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates an interval for at most n occurrences.
|
|
78
|
+
*
|
|
79
|
+
* @param n - The maximum count
|
|
80
|
+
* @returns A new Interval with min = 0 and max = n
|
|
81
|
+
*/
|
|
82
|
+
static atMost(n: number): Interval {
|
|
83
|
+
return new Interval(0, n);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Creates an interval for zero or more occurrences (0..).
|
|
88
|
+
*
|
|
89
|
+
* @returns A new Interval representing *
|
|
90
|
+
*/
|
|
91
|
+
static zeroOrMore(): Interval {
|
|
92
|
+
return new Interval(0, undefined);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates an interval for one or more occurrences (1..).
|
|
97
|
+
*
|
|
98
|
+
* @returns A new Interval representing +
|
|
99
|
+
*/
|
|
100
|
+
static oneOrMore(): Interval {
|
|
101
|
+
return new Interval(1, undefined);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates an interval for zero or one occurrence (0..=1).
|
|
106
|
+
*
|
|
107
|
+
* @returns A new Interval representing ?
|
|
108
|
+
*/
|
|
109
|
+
static zeroOrOne(): Interval {
|
|
110
|
+
return new Interval(0, 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns the minimum value of the interval.
|
|
115
|
+
*/
|
|
116
|
+
min(): number {
|
|
117
|
+
return this.#min;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Returns the maximum value of the interval, or `undefined` if unbounded.
|
|
122
|
+
*/
|
|
123
|
+
max(): number | undefined {
|
|
124
|
+
return this.#max;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Checks if the given count falls within this interval.
|
|
129
|
+
*
|
|
130
|
+
* @param count - The count to check
|
|
131
|
+
* @returns true if count is within the interval
|
|
132
|
+
*/
|
|
133
|
+
contains(count: number): boolean {
|
|
134
|
+
return count >= this.#min && (this.#max === undefined || count <= this.#max);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Checks if the interval represents a single value (i.e., min equals max).
|
|
139
|
+
*/
|
|
140
|
+
isSingle(): boolean {
|
|
141
|
+
return this.#max !== undefined && this.#min === this.#max;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Checks if the interval is unbounded (i.e., has no maximum value).
|
|
146
|
+
*/
|
|
147
|
+
isUnbounded(): boolean {
|
|
148
|
+
return this.#max === undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns a string representation of the interval using standard range notation.
|
|
153
|
+
*
|
|
154
|
+
* @returns The range notation string
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* new Interval(3, 3).rangeNotation() // "{3}"
|
|
159
|
+
* new Interval(1, 5).rangeNotation() // "{1,5}"
|
|
160
|
+
* new Interval(2).rangeNotation() // "{2,}"
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
rangeNotation(): string {
|
|
164
|
+
if (this.#max !== undefined && this.#min === this.#max) {
|
|
165
|
+
return `{${this.#min}}`;
|
|
166
|
+
}
|
|
167
|
+
if (this.#max !== undefined) {
|
|
168
|
+
return `{${this.#min},${this.#max}}`;
|
|
169
|
+
}
|
|
170
|
+
return `{${this.#min},}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns a string representation of the interval using shorthand notation
|
|
175
|
+
* where applicable.
|
|
176
|
+
*
|
|
177
|
+
* @returns The shorthand notation string
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* new Interval(0, 1).shorthandNotation() // "?"
|
|
182
|
+
* new Interval(0).shorthandNotation() // "*"
|
|
183
|
+
* new Interval(1).shorthandNotation() // "+"
|
|
184
|
+
* new Interval(1, 5).shorthandNotation() // "{1,5}"
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
shorthandNotation(): string {
|
|
188
|
+
// Check for optional (?)
|
|
189
|
+
if (this.#min === 0 && this.#max === 1) {
|
|
190
|
+
return "?";
|
|
191
|
+
}
|
|
192
|
+
// Check for single value
|
|
193
|
+
if (this.#max !== undefined && this.#min === this.#max) {
|
|
194
|
+
return `{${this.#min}}`;
|
|
195
|
+
}
|
|
196
|
+
// Check for bounded range
|
|
197
|
+
if (this.#max !== undefined) {
|
|
198
|
+
return `{${this.#min},${this.#max}}`;
|
|
199
|
+
}
|
|
200
|
+
// Check for zero or more (*)
|
|
201
|
+
if (this.#min === 0) {
|
|
202
|
+
return "*";
|
|
203
|
+
}
|
|
204
|
+
// Check for one or more (+)
|
|
205
|
+
if (this.#min === 1) {
|
|
206
|
+
return "+";
|
|
207
|
+
}
|
|
208
|
+
// General unbounded case
|
|
209
|
+
return `{${this.#min},}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Returns a string representation using range notation.
|
|
214
|
+
*/
|
|
215
|
+
toString(): string {
|
|
216
|
+
return this.rangeNotation();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Checks equality with another Interval.
|
|
221
|
+
*/
|
|
222
|
+
equals(other: Interval): boolean {
|
|
223
|
+
return this.#min === other.#min && this.#max === other.#max;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Default interval is exactly 1 occurrence.
|
|
229
|
+
*/
|
|
230
|
+
export const DEFAULT_INTERVAL = Interval.exactly(1);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsing module for dCBOR patterns.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the parser for dCBOR pattern expressions,
|
|
5
|
+
* converting string representations into Pattern AST nodes.
|
|
6
|
+
*
|
|
7
|
+
* @module parse
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from "./token";
|
|
11
|
+
export * from "./value";
|
|
12
|
+
export * from "./structure";
|
|
13
|
+
export * from "./meta";
|
|
14
|
+
export * from "./parse-registry";
|
|
15
|
+
|
|
16
|
+
import type { Pattern } from "../pattern";
|
|
17
|
+
import type { Result } from "../error";
|
|
18
|
+
import { Ok, Err } from "../error";
|
|
19
|
+
import { Lexer } from "./token";
|
|
20
|
+
import { parseOr } from "./meta/or-parser";
|
|
21
|
+
import { setParseOrFn } from "./parse-registry";
|
|
22
|
+
|
|
23
|
+
// Register the parseOr function with the parse registry
|
|
24
|
+
// This breaks the circular dependency between meta and structure parsers
|
|
25
|
+
setParseOrFn(parseOr);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parses a complete dCBOR pattern expression.
|
|
29
|
+
*
|
|
30
|
+
* @param input - The pattern string to parse
|
|
31
|
+
* @returns A Result containing the parsed Pattern or an error
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const result = parse("number");
|
|
36
|
+
* if (result.ok) {
|
|
37
|
+
* console.log(result.value);
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const parse = (input: string): Result<Pattern> => {
|
|
42
|
+
const result = parsePartial(input);
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [pattern, consumed] = result.value;
|
|
48
|
+
if (consumed < input.length) {
|
|
49
|
+
// There's extra data after the pattern
|
|
50
|
+
return Err({
|
|
51
|
+
type: "ExtraData",
|
|
52
|
+
span: { start: consumed, end: input.length },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Ok(pattern);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parses a partial dCBOR pattern expression, returning the parsed pattern
|
|
61
|
+
* and the number of characters consumed.
|
|
62
|
+
*
|
|
63
|
+
* Unlike `parse()`, this function succeeds even if additional characters
|
|
64
|
+
* follow the first pattern. The returned index points to the first unparsed
|
|
65
|
+
* character after the pattern.
|
|
66
|
+
*
|
|
67
|
+
* @param input - The pattern string to parse
|
|
68
|
+
* @returns A Result containing a tuple of [Pattern, consumedLength] or an error
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const result = parsePartial("true rest");
|
|
73
|
+
* if (result.ok) {
|
|
74
|
+
* const [pattern, consumed] = result.value;
|
|
75
|
+
* console.log(consumed); // 4 or 5 (includes whitespace)
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export const parsePartial = (input: string): Result<[Pattern, number]> => {
|
|
80
|
+
if (input.trim().length === 0) {
|
|
81
|
+
return Err({ type: "EmptyInput" });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const lexer = new Lexer(input);
|
|
85
|
+
const patternResult = parseOr(lexer);
|
|
86
|
+
|
|
87
|
+
if (!patternResult.ok) {
|
|
88
|
+
return patternResult;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Calculate consumed bytes
|
|
92
|
+
const consumed = lexer.position();
|
|
93
|
+
|
|
94
|
+
return Ok([patternResult.value, consumed]);
|
|
95
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AND pattern parser.
|
|
3
|
+
*
|
|
4
|
+
* @module parse/meta/and-parser
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Lexer } from "../token";
|
|
8
|
+
import type { Pattern } from "../../pattern";
|
|
9
|
+
import type { Result } from "../../error";
|
|
10
|
+
import { Ok } from "../../error";
|
|
11
|
+
import { and } from "../../pattern";
|
|
12
|
+
import { parseNot } from "./not-parser";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse an AND pattern.
|
|
16
|
+
*/
|
|
17
|
+
export const parseAnd = (lexer: Lexer): Result<Pattern> => {
|
|
18
|
+
const patterns: Pattern[] = [];
|
|
19
|
+
const first = parseNot(lexer);
|
|
20
|
+
if (!first.ok) {
|
|
21
|
+
return first;
|
|
22
|
+
}
|
|
23
|
+
patterns.push(first.value);
|
|
24
|
+
|
|
25
|
+
while (true) {
|
|
26
|
+
const peeked = lexer.peekToken();
|
|
27
|
+
if (peeked?.ok !== true) {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
if (peeked.value.type !== "And") {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
lexer.next(); // consume the AND token
|
|
34
|
+
|
|
35
|
+
const next = parseNot(lexer);
|
|
36
|
+
if (!next.ok) {
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
patterns.push(next.value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (patterns.length === 1) {
|
|
43
|
+
return Ok(patterns[0]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return Ok(and(...patterns));
|
|
47
|
+
};
|