@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.
Files changed (73) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +14 -0
  3. package/dist/index.cjs +6561 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +2732 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +2732 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +6562 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +6244 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +85 -0
  14. package/src/error.ts +333 -0
  15. package/src/format.ts +299 -0
  16. package/src/index.ts +20 -0
  17. package/src/interval.ts +230 -0
  18. package/src/parse/index.ts +95 -0
  19. package/src/parse/meta/and-parser.ts +47 -0
  20. package/src/parse/meta/capture-parser.ts +56 -0
  21. package/src/parse/meta/index.ts +13 -0
  22. package/src/parse/meta/not-parser.ts +28 -0
  23. package/src/parse/meta/or-parser.ts +47 -0
  24. package/src/parse/meta/primary-parser.ts +420 -0
  25. package/src/parse/meta/repeat-parser.ts +133 -0
  26. package/src/parse/meta/search-parser.ts +56 -0
  27. package/src/parse/parse-registry.ts +31 -0
  28. package/src/parse/structure/array-parser.ts +210 -0
  29. package/src/parse/structure/index.ts +9 -0
  30. package/src/parse/structure/map-parser.ts +128 -0
  31. package/src/parse/structure/tagged-parser.ts +269 -0
  32. package/src/parse/token.ts +997 -0
  33. package/src/parse/value/bool-parser.ts +33 -0
  34. package/src/parse/value/bytestring-parser.ts +42 -0
  35. package/src/parse/value/date-parser.ts +24 -0
  36. package/src/parse/value/digest-parser.ts +24 -0
  37. package/src/parse/value/index.ts +14 -0
  38. package/src/parse/value/known-value-parser.ts +24 -0
  39. package/src/parse/value/null-parser.ts +19 -0
  40. package/src/parse/value/number-parser.ts +19 -0
  41. package/src/parse/value/text-parser.ts +43 -0
  42. package/src/pattern/index.ts +740 -0
  43. package/src/pattern/match-registry.ts +137 -0
  44. package/src/pattern/matcher.ts +388 -0
  45. package/src/pattern/meta/and-pattern.ts +56 -0
  46. package/src/pattern/meta/any-pattern.ts +43 -0
  47. package/src/pattern/meta/capture-pattern.ts +57 -0
  48. package/src/pattern/meta/index.ts +168 -0
  49. package/src/pattern/meta/not-pattern.ts +70 -0
  50. package/src/pattern/meta/or-pattern.ts +56 -0
  51. package/src/pattern/meta/repeat-pattern.ts +117 -0
  52. package/src/pattern/meta/search-pattern.ts +298 -0
  53. package/src/pattern/meta/sequence-pattern.ts +72 -0
  54. package/src/pattern/structure/array-pattern/assigner.ts +95 -0
  55. package/src/pattern/structure/array-pattern/backtrack.ts +240 -0
  56. package/src/pattern/structure/array-pattern/helpers.ts +140 -0
  57. package/src/pattern/structure/array-pattern/index.ts +502 -0
  58. package/src/pattern/structure/index.ts +122 -0
  59. package/src/pattern/structure/map-pattern.ts +255 -0
  60. package/src/pattern/structure/tagged-pattern.ts +190 -0
  61. package/src/pattern/value/bool-pattern.ts +67 -0
  62. package/src/pattern/value/bytes-utils.ts +48 -0
  63. package/src/pattern/value/bytestring-pattern.ts +111 -0
  64. package/src/pattern/value/date-pattern.ts +162 -0
  65. package/src/pattern/value/digest-pattern.ts +136 -0
  66. package/src/pattern/value/index.ts +168 -0
  67. package/src/pattern/value/known-value-pattern.ts +123 -0
  68. package/src/pattern/value/null-pattern.ts +46 -0
  69. package/src/pattern/value/number-pattern.ts +181 -0
  70. package/src/pattern/value/text-pattern.ts +82 -0
  71. package/src/pattern/vm.ts +619 -0
  72. package/src/quantifier.ts +185 -0
  73. 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";
@@ -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
+ };