@bcts/dcbor-parse 1.0.0-alpha.13
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 +1141 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +433 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +433 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +1142 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +1118 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +84 -0
- package/src/compose.ts +156 -0
- package/src/error.ts +373 -0
- package/src/index.ts +82 -0
- package/src/parse.ts +411 -0
- package/src/token.ts +641 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1118 @@
|
|
|
1
|
+
import { CborDate, CborMap, cbor, getGlobalTagsStore } from "@bcts/dcbor";
|
|
2
|
+
import { KNOWN_VALUES, KnownValue } from "@bcts/known-values";
|
|
3
|
+
import { UR } from "@bcts/uniform-resources";
|
|
4
|
+
|
|
5
|
+
//#region src/error.ts
|
|
6
|
+
/**
|
|
7
|
+
* Creates a span with the given start and end positions.
|
|
8
|
+
*/
|
|
9
|
+
function span(start, end) {
|
|
10
|
+
return {
|
|
11
|
+
start,
|
|
12
|
+
end
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Creates a default (empty) span.
|
|
17
|
+
*/
|
|
18
|
+
function defaultSpan() {
|
|
19
|
+
return {
|
|
20
|
+
start: 0,
|
|
21
|
+
end: 0
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const parseError = {
|
|
25
|
+
emptyInput() {
|
|
26
|
+
return { type: "EmptyInput" };
|
|
27
|
+
},
|
|
28
|
+
unexpectedEndOfInput() {
|
|
29
|
+
return { type: "UnexpectedEndOfInput" };
|
|
30
|
+
},
|
|
31
|
+
extraData(span$1) {
|
|
32
|
+
return {
|
|
33
|
+
type: "ExtraData",
|
|
34
|
+
span: span$1
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
unexpectedToken(token$1, span$1) {
|
|
38
|
+
return {
|
|
39
|
+
type: "UnexpectedToken",
|
|
40
|
+
token: token$1,
|
|
41
|
+
span: span$1
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
unrecognizedToken(span$1) {
|
|
45
|
+
return {
|
|
46
|
+
type: "UnrecognizedToken",
|
|
47
|
+
span: span$1
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
expectedComma(span$1) {
|
|
51
|
+
return {
|
|
52
|
+
type: "ExpectedComma",
|
|
53
|
+
span: span$1
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
expectedColon(span$1) {
|
|
57
|
+
return {
|
|
58
|
+
type: "ExpectedColon",
|
|
59
|
+
span: span$1
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
unmatchedParentheses(span$1) {
|
|
63
|
+
return {
|
|
64
|
+
type: "UnmatchedParentheses",
|
|
65
|
+
span: span$1
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
unmatchedBraces(span$1) {
|
|
69
|
+
return {
|
|
70
|
+
type: "UnmatchedBraces",
|
|
71
|
+
span: span$1
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
expectedMapKey(span$1) {
|
|
75
|
+
return {
|
|
76
|
+
type: "ExpectedMapKey",
|
|
77
|
+
span: span$1
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
invalidTagValue(value, span$1) {
|
|
81
|
+
return {
|
|
82
|
+
type: "InvalidTagValue",
|
|
83
|
+
value,
|
|
84
|
+
span: span$1
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
unknownTagName(name, span$1) {
|
|
88
|
+
return {
|
|
89
|
+
type: "UnknownTagName",
|
|
90
|
+
name,
|
|
91
|
+
span: span$1
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
invalidHexString(span$1) {
|
|
95
|
+
return {
|
|
96
|
+
type: "InvalidHexString",
|
|
97
|
+
span: span$1
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
invalidBase64String(span$1) {
|
|
101
|
+
return {
|
|
102
|
+
type: "InvalidBase64String",
|
|
103
|
+
span: span$1
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
unknownUrType(urType, span$1) {
|
|
107
|
+
return {
|
|
108
|
+
type: "UnknownUrType",
|
|
109
|
+
urType,
|
|
110
|
+
span: span$1
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
invalidUr(message, span$1) {
|
|
114
|
+
return {
|
|
115
|
+
type: "InvalidUr",
|
|
116
|
+
message,
|
|
117
|
+
span: span$1
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
invalidKnownValue(value, span$1) {
|
|
121
|
+
return {
|
|
122
|
+
type: "InvalidKnownValue",
|
|
123
|
+
value,
|
|
124
|
+
span: span$1
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
unknownKnownValueName(name, span$1) {
|
|
128
|
+
return {
|
|
129
|
+
type: "UnknownKnownValueName",
|
|
130
|
+
name,
|
|
131
|
+
span: span$1
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
invalidDateString(dateString, span$1) {
|
|
135
|
+
return {
|
|
136
|
+
type: "InvalidDateString",
|
|
137
|
+
dateString,
|
|
138
|
+
span: span$1
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
duplicateMapKey(span$1) {
|
|
142
|
+
return {
|
|
143
|
+
type: "DuplicateMapKey",
|
|
144
|
+
span: span$1
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Checks if an error is the default unrecognized token error.
|
|
150
|
+
*
|
|
151
|
+
* Corresponds to Rust `Error::is_default()`
|
|
152
|
+
*/
|
|
153
|
+
function isDefaultError(error) {
|
|
154
|
+
return error.type === "UnrecognizedToken";
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Gets the error message for a parse error.
|
|
158
|
+
*
|
|
159
|
+
* Corresponds to Rust's `Display` implementation for `Error`
|
|
160
|
+
*/
|
|
161
|
+
function errorMessage(error) {
|
|
162
|
+
switch (error.type) {
|
|
163
|
+
case "EmptyInput": return "Empty input";
|
|
164
|
+
case "UnexpectedEndOfInput": return "Unexpected end of input";
|
|
165
|
+
case "ExtraData": return "Extra data at end of input";
|
|
166
|
+
case "UnexpectedToken": return `Unexpected token ${tokenDebugString(error.token)}`;
|
|
167
|
+
case "UnrecognizedToken": return "Unrecognized token";
|
|
168
|
+
case "ExpectedComma": return "Expected comma";
|
|
169
|
+
case "ExpectedColon": return "Expected colon";
|
|
170
|
+
case "UnmatchedParentheses": return "Unmatched parentheses";
|
|
171
|
+
case "UnmatchedBraces": return "Unmatched braces";
|
|
172
|
+
case "ExpectedMapKey": return "Expected map key";
|
|
173
|
+
case "InvalidTagValue": return `Invalid tag value '${error.value}'`;
|
|
174
|
+
case "UnknownTagName": return `Unknown tag name '${error.name}'`;
|
|
175
|
+
case "InvalidHexString": return "Invalid hex string";
|
|
176
|
+
case "InvalidBase64String": return "Invalid base64 string";
|
|
177
|
+
case "UnknownUrType": return `Unknown UR type '${error.urType}'`;
|
|
178
|
+
case "InvalidUr": return `Invalid UR '${error.message}'`;
|
|
179
|
+
case "InvalidKnownValue": return `Invalid known value '${error.value}'`;
|
|
180
|
+
case "UnknownKnownValueName": return `Unknown known value name '${error.name}'`;
|
|
181
|
+
case "InvalidDateString": return `Invalid date string '${error.dateString}'`;
|
|
182
|
+
case "DuplicateMapKey": return "Duplicate map key";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Gets the span for a parse error, if applicable.
|
|
187
|
+
*/
|
|
188
|
+
function errorSpan(error) {
|
|
189
|
+
switch (error.type) {
|
|
190
|
+
case "EmptyInput":
|
|
191
|
+
case "UnexpectedEndOfInput": return;
|
|
192
|
+
case "ExtraData":
|
|
193
|
+
case "UnexpectedToken":
|
|
194
|
+
case "UnrecognizedToken":
|
|
195
|
+
case "ExpectedComma":
|
|
196
|
+
case "ExpectedColon":
|
|
197
|
+
case "UnmatchedParentheses":
|
|
198
|
+
case "UnmatchedBraces":
|
|
199
|
+
case "ExpectedMapKey":
|
|
200
|
+
case "InvalidTagValue":
|
|
201
|
+
case "UnknownTagName":
|
|
202
|
+
case "InvalidHexString":
|
|
203
|
+
case "InvalidBase64String":
|
|
204
|
+
case "UnknownUrType":
|
|
205
|
+
case "InvalidUr":
|
|
206
|
+
case "InvalidKnownValue":
|
|
207
|
+
case "UnknownKnownValueName":
|
|
208
|
+
case "InvalidDateString":
|
|
209
|
+
case "DuplicateMapKey": return error.span;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Formats an error message with source context, line number, and caret.
|
|
214
|
+
*
|
|
215
|
+
* Corresponds to Rust `Error::format_message()`
|
|
216
|
+
*/
|
|
217
|
+
function formatMessage(message, source, range) {
|
|
218
|
+
const start = range.start;
|
|
219
|
+
const end = range.end;
|
|
220
|
+
let lineNumber = 1;
|
|
221
|
+
let lineStart = 0;
|
|
222
|
+
for (let idx = 0; idx < source.length && idx < start; idx++) if (source[idx] === "\n") {
|
|
223
|
+
lineNumber++;
|
|
224
|
+
lineStart = idx + 1;
|
|
225
|
+
}
|
|
226
|
+
const line = source.split("\n")[lineNumber - 1] ?? "";
|
|
227
|
+
const column = Math.max(0, start - lineStart);
|
|
228
|
+
const underlineLen = Math.max(1, end - start);
|
|
229
|
+
const caret = " ".repeat(column) + "^".repeat(underlineLen);
|
|
230
|
+
return `line ${lineNumber}: ${message}\n${line}\n${caret}`;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Gets the full error message with source context.
|
|
234
|
+
*
|
|
235
|
+
* Corresponds to Rust `Error::full_message()`
|
|
236
|
+
*/
|
|
237
|
+
function fullErrorMessage(error, source) {
|
|
238
|
+
const message = errorMessage(error);
|
|
239
|
+
switch (error.type) {
|
|
240
|
+
case "EmptyInput": return formatMessage(message, source, defaultSpan());
|
|
241
|
+
case "UnexpectedEndOfInput": return formatMessage(message, source, span(source.length, source.length));
|
|
242
|
+
case "ExtraData":
|
|
243
|
+
case "UnexpectedToken":
|
|
244
|
+
case "UnrecognizedToken":
|
|
245
|
+
case "ExpectedComma":
|
|
246
|
+
case "ExpectedColon":
|
|
247
|
+
case "UnmatchedParentheses":
|
|
248
|
+
case "UnmatchedBraces":
|
|
249
|
+
case "ExpectedMapKey":
|
|
250
|
+
case "InvalidTagValue":
|
|
251
|
+
case "UnknownTagName":
|
|
252
|
+
case "InvalidHexString":
|
|
253
|
+
case "InvalidBase64String":
|
|
254
|
+
case "UnknownUrType":
|
|
255
|
+
case "InvalidUr":
|
|
256
|
+
case "InvalidKnownValue":
|
|
257
|
+
case "UnknownKnownValueName":
|
|
258
|
+
case "InvalidDateString":
|
|
259
|
+
case "DuplicateMapKey": return formatMessage(message, source, error.span);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Creates a default parse error (UnrecognizedToken with empty span).
|
|
264
|
+
*
|
|
265
|
+
* Corresponds to Rust `Error::default()`
|
|
266
|
+
*/
|
|
267
|
+
function defaultParseError() {
|
|
268
|
+
return parseError.unrecognizedToken(defaultSpan());
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Creates a successful result.
|
|
272
|
+
*/
|
|
273
|
+
function ok(value) {
|
|
274
|
+
return {
|
|
275
|
+
ok: true,
|
|
276
|
+
value
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Creates an error result.
|
|
281
|
+
*/
|
|
282
|
+
function err(error) {
|
|
283
|
+
return {
|
|
284
|
+
ok: false,
|
|
285
|
+
error
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Checks if a result is successful.
|
|
290
|
+
*/
|
|
291
|
+
function isOk(result) {
|
|
292
|
+
return result.ok;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Checks if a result is an error.
|
|
296
|
+
*/
|
|
297
|
+
function isErr(result) {
|
|
298
|
+
return !result.ok;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Unwraps a result, throwing if it's an error.
|
|
302
|
+
*/
|
|
303
|
+
function unwrap(result) {
|
|
304
|
+
if (result.ok) return result.value;
|
|
305
|
+
throw new Error(errorMessage(result.error));
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Unwraps a result error, throwing if it's successful.
|
|
309
|
+
*/
|
|
310
|
+
function unwrapErr(result) {
|
|
311
|
+
if (!result.ok) return result.error;
|
|
312
|
+
throw new Error("Called unwrapErr on an Ok result");
|
|
313
|
+
}
|
|
314
|
+
function tokenDebugString(token$1) {
|
|
315
|
+
return JSON.stringify(token$1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/token.ts
|
|
320
|
+
/**
|
|
321
|
+
* @bcts/dcbor-parse - Token types and Lexer
|
|
322
|
+
*
|
|
323
|
+
* This is a 1:1 TypeScript port of bc-dcbor-parse-rust token.rs
|
|
324
|
+
*
|
|
325
|
+
* @module dcbor-parse/token
|
|
326
|
+
*/
|
|
327
|
+
const token = {
|
|
328
|
+
bool(value) {
|
|
329
|
+
return {
|
|
330
|
+
type: "Bool",
|
|
331
|
+
value
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
braceOpen() {
|
|
335
|
+
return { type: "BraceOpen" };
|
|
336
|
+
},
|
|
337
|
+
braceClose() {
|
|
338
|
+
return { type: "BraceClose" };
|
|
339
|
+
},
|
|
340
|
+
bracketOpen() {
|
|
341
|
+
return { type: "BracketOpen" };
|
|
342
|
+
},
|
|
343
|
+
bracketClose() {
|
|
344
|
+
return { type: "BracketClose" };
|
|
345
|
+
},
|
|
346
|
+
parenthesisOpen() {
|
|
347
|
+
return { type: "ParenthesisOpen" };
|
|
348
|
+
},
|
|
349
|
+
parenthesisClose() {
|
|
350
|
+
return { type: "ParenthesisClose" };
|
|
351
|
+
},
|
|
352
|
+
colon() {
|
|
353
|
+
return { type: "Colon" };
|
|
354
|
+
},
|
|
355
|
+
comma() {
|
|
356
|
+
return { type: "Comma" };
|
|
357
|
+
},
|
|
358
|
+
null() {
|
|
359
|
+
return { type: "Null" };
|
|
360
|
+
},
|
|
361
|
+
nan() {
|
|
362
|
+
return { type: "NaN" };
|
|
363
|
+
},
|
|
364
|
+
infinity() {
|
|
365
|
+
return { type: "Infinity" };
|
|
366
|
+
},
|
|
367
|
+
negInfinity() {
|
|
368
|
+
return { type: "NegInfinity" };
|
|
369
|
+
},
|
|
370
|
+
byteStringHex(value) {
|
|
371
|
+
return {
|
|
372
|
+
type: "ByteStringHex",
|
|
373
|
+
value
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
byteStringBase64(value) {
|
|
377
|
+
return {
|
|
378
|
+
type: "ByteStringBase64",
|
|
379
|
+
value
|
|
380
|
+
};
|
|
381
|
+
},
|
|
382
|
+
dateLiteral(value) {
|
|
383
|
+
return {
|
|
384
|
+
type: "DateLiteral",
|
|
385
|
+
value
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
number(value) {
|
|
389
|
+
return {
|
|
390
|
+
type: "Number",
|
|
391
|
+
value
|
|
392
|
+
};
|
|
393
|
+
},
|
|
394
|
+
string(value) {
|
|
395
|
+
return {
|
|
396
|
+
type: "String",
|
|
397
|
+
value
|
|
398
|
+
};
|
|
399
|
+
},
|
|
400
|
+
tagValue(value) {
|
|
401
|
+
return {
|
|
402
|
+
type: "TagValue",
|
|
403
|
+
value
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
tagName(value) {
|
|
407
|
+
return {
|
|
408
|
+
type: "TagName",
|
|
409
|
+
value
|
|
410
|
+
};
|
|
411
|
+
},
|
|
412
|
+
knownValueNumber(value) {
|
|
413
|
+
return {
|
|
414
|
+
type: "KnownValueNumber",
|
|
415
|
+
value
|
|
416
|
+
};
|
|
417
|
+
},
|
|
418
|
+
knownValueName(value) {
|
|
419
|
+
return {
|
|
420
|
+
type: "KnownValueName",
|
|
421
|
+
value
|
|
422
|
+
};
|
|
423
|
+
},
|
|
424
|
+
unit() {
|
|
425
|
+
return { type: "Unit" };
|
|
426
|
+
},
|
|
427
|
+
ur(value) {
|
|
428
|
+
return {
|
|
429
|
+
type: "UR",
|
|
430
|
+
value
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
435
|
+
* Lexer for dCBOR diagnostic notation.
|
|
436
|
+
*
|
|
437
|
+
* Corresponds to the Rust `logos::Lexer` used in parse.rs
|
|
438
|
+
*/
|
|
439
|
+
var Lexer = class {
|
|
440
|
+
#source;
|
|
441
|
+
#position;
|
|
442
|
+
#tokenStart;
|
|
443
|
+
#tokenEnd;
|
|
444
|
+
constructor(source) {
|
|
445
|
+
this.#source = source;
|
|
446
|
+
this.#position = 0;
|
|
447
|
+
this.#tokenStart = 0;
|
|
448
|
+
this.#tokenEnd = 0;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Gets the current span (position range of the last token).
|
|
452
|
+
*/
|
|
453
|
+
span() {
|
|
454
|
+
return span(this.#tokenStart, this.#tokenEnd);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Gets the slice of source corresponding to the last token.
|
|
458
|
+
*/
|
|
459
|
+
slice() {
|
|
460
|
+
return this.#source.slice(this.#tokenStart, this.#tokenEnd);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Gets the next token, or undefined if at end of input.
|
|
464
|
+
* Returns a Result to handle lexing errors.
|
|
465
|
+
*/
|
|
466
|
+
next() {
|
|
467
|
+
this.#skipWhitespaceAndComments();
|
|
468
|
+
if (this.#position >= this.#source.length) return;
|
|
469
|
+
this.#tokenStart = this.#position;
|
|
470
|
+
const result = this.#tryMatchKeyword() ?? this.#tryMatchDateLiteral() ?? this.#tryMatchTagValueOrNumber() ?? this.#tryMatchTagName() ?? this.#tryMatchString() ?? this.#tryMatchByteStringHex() ?? this.#tryMatchByteStringBase64() ?? this.#tryMatchKnownValue() ?? this.#tryMatchUR() ?? this.#tryMatchPunctuation();
|
|
471
|
+
if (result === void 0) {
|
|
472
|
+
this.#position++;
|
|
473
|
+
this.#tokenEnd = this.#position;
|
|
474
|
+
return err(parseError.unrecognizedToken(this.span()));
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
#skipWhitespaceAndComments() {
|
|
479
|
+
while (this.#position < this.#source.length) {
|
|
480
|
+
const ch = this.#source[this.#position];
|
|
481
|
+
if (ch === " " || ch === " " || ch === "\r" || ch === "\n" || ch === "\f") {
|
|
482
|
+
this.#position++;
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (ch === "/" && this.#position + 1 < this.#source.length && this.#source[this.#position + 1] !== "/") {
|
|
486
|
+
this.#position++;
|
|
487
|
+
while (this.#position < this.#source.length && this.#source[this.#position] !== "/") this.#position++;
|
|
488
|
+
if (this.#position < this.#source.length) this.#position++;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (ch === "#") {
|
|
492
|
+
while (this.#position < this.#source.length && this.#source[this.#position] !== "\n") this.#position++;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
#tryMatchKeyword() {
|
|
499
|
+
const keywords = [
|
|
500
|
+
["true", token.bool(true)],
|
|
501
|
+
["false", token.bool(false)],
|
|
502
|
+
["null", token.null()],
|
|
503
|
+
["NaN", token.nan()],
|
|
504
|
+
["Infinity", token.infinity()],
|
|
505
|
+
["-Infinity", token.negInfinity()],
|
|
506
|
+
["Unit", token.unit()]
|
|
507
|
+
];
|
|
508
|
+
for (const [keyword, tok] of keywords) if (this.#matchLiteral(keyword)) {
|
|
509
|
+
const nextChar = this.#source[this.#position];
|
|
510
|
+
if (nextChar === void 0 || !this.#isIdentifierChar(nextChar)) {
|
|
511
|
+
this.#tokenEnd = this.#position;
|
|
512
|
+
return ok(tok);
|
|
513
|
+
}
|
|
514
|
+
this.#position = this.#tokenStart;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
#tryMatchDateLiteral() {
|
|
518
|
+
const dateRegex = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?/;
|
|
519
|
+
const remaining = this.#source.slice(this.#position);
|
|
520
|
+
const match = dateRegex.exec(remaining);
|
|
521
|
+
if (match !== null) {
|
|
522
|
+
const dateStr = match[0];
|
|
523
|
+
this.#position += dateStr.length;
|
|
524
|
+
this.#tokenEnd = this.#position;
|
|
525
|
+
if (!isValidDateString(dateStr)) return err(parseError.invalidDateString(dateStr, this.span()));
|
|
526
|
+
try {
|
|
527
|
+
const date = CborDate.fromString(dateStr);
|
|
528
|
+
return ok(token.dateLiteral(date));
|
|
529
|
+
} catch {
|
|
530
|
+
return err(parseError.invalidDateString(dateStr, this.span()));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
#tryMatchTagValueOrNumber() {
|
|
535
|
+
const numberRegex = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/;
|
|
536
|
+
const remaining = this.#source.slice(this.#position);
|
|
537
|
+
const match = numberRegex.exec(remaining);
|
|
538
|
+
if (match !== null) {
|
|
539
|
+
const numStr = match[0];
|
|
540
|
+
if (this.#source[this.#position + numStr.length] === "(" && !numStr.includes(".") && !numStr.includes("e") && !numStr.includes("E") && !numStr.startsWith("-")) {
|
|
541
|
+
this.#position += numStr.length + 1;
|
|
542
|
+
this.#tokenEnd = this.#position;
|
|
543
|
+
const tagValue = parseInt(numStr, 10);
|
|
544
|
+
if (!Number.isSafeInteger(tagValue) || tagValue < 0) return err(parseError.invalidTagValue(numStr, span(this.#tokenStart, this.#tokenStart + numStr.length)));
|
|
545
|
+
return ok(token.tagValue(tagValue));
|
|
546
|
+
}
|
|
547
|
+
this.#position += numStr.length;
|
|
548
|
+
this.#tokenEnd = this.#position;
|
|
549
|
+
const num = parseFloat(numStr);
|
|
550
|
+
return ok(token.number(num));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
#tryMatchTagName() {
|
|
554
|
+
const tagNameRegex = /^[a-zA-Z_][a-zA-Z0-9_-]*\(/;
|
|
555
|
+
const remaining = this.#source.slice(this.#position);
|
|
556
|
+
const match = tagNameRegex.exec(remaining);
|
|
557
|
+
if (match !== null) {
|
|
558
|
+
const fullMatch = match[0];
|
|
559
|
+
const name = fullMatch.slice(0, -1);
|
|
560
|
+
this.#position += fullMatch.length;
|
|
561
|
+
this.#tokenEnd = this.#position;
|
|
562
|
+
return ok(token.tagName(name));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
#tryMatchString() {
|
|
566
|
+
if (this.#source[this.#position] !== "\"") return;
|
|
567
|
+
const stringRegex = /^"([^"\\\x00-\x1F]|\\(["\\bnfrt/]|u[a-fA-F0-9]{4}))*"/;
|
|
568
|
+
const remaining = this.#source.slice(this.#position);
|
|
569
|
+
const match = stringRegex.exec(remaining);
|
|
570
|
+
if (match !== null) {
|
|
571
|
+
const fullMatch = match[0];
|
|
572
|
+
this.#position += fullMatch.length;
|
|
573
|
+
this.#tokenEnd = this.#position;
|
|
574
|
+
return ok(token.string(fullMatch));
|
|
575
|
+
}
|
|
576
|
+
this.#position++;
|
|
577
|
+
while (this.#position < this.#source.length) {
|
|
578
|
+
const ch = this.#source[this.#position];
|
|
579
|
+
if (ch === "\"" || ch === "\n") {
|
|
580
|
+
if (ch === "\"") this.#position++;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
if (ch === "\\") this.#position += 2;
|
|
584
|
+
else this.#position++;
|
|
585
|
+
}
|
|
586
|
+
this.#tokenEnd = this.#position;
|
|
587
|
+
return err(parseError.unrecognizedToken(this.span()));
|
|
588
|
+
}
|
|
589
|
+
#tryMatchByteStringHex() {
|
|
590
|
+
if (!this.#matchLiteral("h'")) return;
|
|
591
|
+
const hexRegex = /^[0-9a-fA-F]*/;
|
|
592
|
+
const remaining = this.#source.slice(this.#position);
|
|
593
|
+
const match = hexRegex.exec(remaining);
|
|
594
|
+
const hexPart = match !== null ? match[0] : "";
|
|
595
|
+
this.#position += hexPart.length;
|
|
596
|
+
if (this.#source[this.#position] !== "'") {
|
|
597
|
+
this.#tokenEnd = this.#position;
|
|
598
|
+
return err(parseError.invalidHexString(this.span()));
|
|
599
|
+
}
|
|
600
|
+
this.#position++;
|
|
601
|
+
this.#tokenEnd = this.#position;
|
|
602
|
+
if (hexPart.length % 2 !== 0) return err(parseError.invalidHexString(this.span()));
|
|
603
|
+
const bytes = hexToBytes(hexPart);
|
|
604
|
+
return ok(token.byteStringHex(bytes));
|
|
605
|
+
}
|
|
606
|
+
#tryMatchByteStringBase64() {
|
|
607
|
+
if (!this.#matchLiteral("b64'")) return;
|
|
608
|
+
const base64Regex = /^[A-Za-z0-9+/=]*/;
|
|
609
|
+
const remaining = this.#source.slice(this.#position);
|
|
610
|
+
const match = base64Regex.exec(remaining);
|
|
611
|
+
const base64Part = match !== null ? match[0] : "";
|
|
612
|
+
this.#position += base64Part.length;
|
|
613
|
+
if (this.#source[this.#position] !== "'") {
|
|
614
|
+
this.#tokenEnd = this.#position;
|
|
615
|
+
return err(parseError.invalidBase64String(this.span()));
|
|
616
|
+
}
|
|
617
|
+
this.#position++;
|
|
618
|
+
this.#tokenEnd = this.#position;
|
|
619
|
+
if (base64Part.length < 2) return err(parseError.invalidBase64String(this.span()));
|
|
620
|
+
try {
|
|
621
|
+
const bytes = base64ToBytes(base64Part);
|
|
622
|
+
return ok(token.byteStringBase64(bytes));
|
|
623
|
+
} catch {
|
|
624
|
+
return err(parseError.invalidBase64String(this.span()));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
#tryMatchKnownValue() {
|
|
628
|
+
if (this.#source[this.#position] !== "'") return;
|
|
629
|
+
if (this.#source[this.#position + 1] === "'") {
|
|
630
|
+
this.#position += 2;
|
|
631
|
+
this.#tokenEnd = this.#position;
|
|
632
|
+
return ok(token.knownValueName(""));
|
|
633
|
+
}
|
|
634
|
+
const numericRegex = /^'(0|[1-9][0-9]*)'/;
|
|
635
|
+
const remaining = this.#source.slice(this.#position);
|
|
636
|
+
let match = numericRegex.exec(remaining);
|
|
637
|
+
if (match !== null) {
|
|
638
|
+
const fullMatch = match[0];
|
|
639
|
+
const numStr = match[1];
|
|
640
|
+
this.#position += fullMatch.length;
|
|
641
|
+
this.#tokenEnd = this.#position;
|
|
642
|
+
const value = parseInt(numStr, 10);
|
|
643
|
+
if (!Number.isSafeInteger(value) || value < 0) return err(parseError.invalidKnownValue(numStr, span(this.#tokenStart + 1, this.#tokenEnd - 1)));
|
|
644
|
+
return ok(token.knownValueNumber(value));
|
|
645
|
+
}
|
|
646
|
+
match = /^'([a-zA-Z_][a-zA-Z0-9_-]*)'/.exec(remaining);
|
|
647
|
+
if (match !== null) {
|
|
648
|
+
const fullMatch = match[0];
|
|
649
|
+
const name = match[1];
|
|
650
|
+
this.#position += fullMatch.length;
|
|
651
|
+
this.#tokenEnd = this.#position;
|
|
652
|
+
return ok(token.knownValueName(name));
|
|
653
|
+
}
|
|
654
|
+
this.#position++;
|
|
655
|
+
while (this.#position < this.#source.length && this.#source[this.#position] !== "'") this.#position++;
|
|
656
|
+
if (this.#position < this.#source.length) this.#position++;
|
|
657
|
+
this.#tokenEnd = this.#position;
|
|
658
|
+
return err(parseError.unrecognizedToken(this.span()));
|
|
659
|
+
}
|
|
660
|
+
#tryMatchUR() {
|
|
661
|
+
const urRegex = /^ur:([a-zA-Z0-9][a-zA-Z0-9-]*)\/([a-zA-Z]{8,})/;
|
|
662
|
+
const remaining = this.#source.slice(this.#position);
|
|
663
|
+
const match = urRegex.exec(remaining);
|
|
664
|
+
if (match !== null) {
|
|
665
|
+
const fullMatch = match[0];
|
|
666
|
+
this.#position += fullMatch.length;
|
|
667
|
+
this.#tokenEnd = this.#position;
|
|
668
|
+
try {
|
|
669
|
+
const ur = UR.fromURString(fullMatch);
|
|
670
|
+
return ok(token.ur(ur));
|
|
671
|
+
} catch (e) {
|
|
672
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
673
|
+
return err(parseError.invalidUr(errorMsg, this.span()));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
#tryMatchPunctuation() {
|
|
678
|
+
const ch = this.#source[this.#position];
|
|
679
|
+
const matched = {
|
|
680
|
+
"{": token.braceOpen(),
|
|
681
|
+
"}": token.braceClose(),
|
|
682
|
+
"[": token.bracketOpen(),
|
|
683
|
+
"]": token.bracketClose(),
|
|
684
|
+
"(": token.parenthesisOpen(),
|
|
685
|
+
")": token.parenthesisClose(),
|
|
686
|
+
":": token.colon(),
|
|
687
|
+
",": token.comma()
|
|
688
|
+
}[ch];
|
|
689
|
+
if (matched !== void 0) {
|
|
690
|
+
this.#position++;
|
|
691
|
+
this.#tokenEnd = this.#position;
|
|
692
|
+
return ok(matched);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
#matchLiteral(literal) {
|
|
696
|
+
if (this.#source.slice(this.#position, this.#position + literal.length) === literal) {
|
|
697
|
+
this.#position += literal.length;
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
#isIdentifierChar(ch) {
|
|
703
|
+
return /[a-zA-Z0-9_-]/.test(ch);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
/**
|
|
707
|
+
* Converts a hex string to bytes.
|
|
708
|
+
*/
|
|
709
|
+
function hexToBytes(hex) {
|
|
710
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
711
|
+
for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
712
|
+
return bytes;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Converts a base64 string to bytes with strict validation.
|
|
716
|
+
* Rejects base64 strings with invalid padding (matches Rust's base64 crate behavior).
|
|
717
|
+
*/
|
|
718
|
+
function base64ToBytes(base64) {
|
|
719
|
+
const expectedPadding = (4 - base64.replace(/=/g, "").length % 4) % 4;
|
|
720
|
+
const paddingMatch = /=+$/.exec(base64);
|
|
721
|
+
if (expectedPadding !== (paddingMatch !== null ? paddingMatch[0].length : 0)) throw new Error("Invalid base64 padding");
|
|
722
|
+
const binaryString = atob(base64);
|
|
723
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
724
|
+
for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
725
|
+
return bytes;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Validates a date string has valid month/day values.
|
|
729
|
+
* JavaScript Date is lenient and accepts invalid dates like 2023-02-30,
|
|
730
|
+
* but Rust's Date::from_string rejects them.
|
|
731
|
+
*/
|
|
732
|
+
function isValidDateString(dateStr) {
|
|
733
|
+
const dateMatch = /^(\d{4})-(\d{2})-(\d{2})/.exec(dateStr);
|
|
734
|
+
if (dateMatch === null) return false;
|
|
735
|
+
const year = parseInt(dateMatch[1], 10);
|
|
736
|
+
const month = parseInt(dateMatch[2], 10);
|
|
737
|
+
const day = parseInt(dateMatch[3], 10);
|
|
738
|
+
if (month < 1 || month > 12) return false;
|
|
739
|
+
if (day < 1) return false;
|
|
740
|
+
const daysInMonth = [
|
|
741
|
+
31,
|
|
742
|
+
28,
|
|
743
|
+
31,
|
|
744
|
+
30,
|
|
745
|
+
31,
|
|
746
|
+
30,
|
|
747
|
+
31,
|
|
748
|
+
31,
|
|
749
|
+
30,
|
|
750
|
+
31,
|
|
751
|
+
30,
|
|
752
|
+
31
|
|
753
|
+
];
|
|
754
|
+
if ((year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) && month === 2) {
|
|
755
|
+
if (day > 29) return false;
|
|
756
|
+
} else if (day > daysInMonth[month - 1]) return false;
|
|
757
|
+
const timeMatch = /T(\d{2}):(\d{2}):(\d{2})/.exec(dateStr);
|
|
758
|
+
if (timeMatch !== null) {
|
|
759
|
+
const hour = parseInt(timeMatch[1], 10);
|
|
760
|
+
const minute = parseInt(timeMatch[2], 10);
|
|
761
|
+
const second = parseInt(timeMatch[3], 10);
|
|
762
|
+
if (hour < 0 || hour > 23) return false;
|
|
763
|
+
if (minute < 0 || minute > 59) return false;
|
|
764
|
+
if (second < 0 || second > 59) return false;
|
|
765
|
+
}
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
//#endregion
|
|
770
|
+
//#region src/parse.ts
|
|
771
|
+
/**
|
|
772
|
+
* @bcts/dcbor-parse - Parse module
|
|
773
|
+
*
|
|
774
|
+
* This is a 1:1 TypeScript port of bc-dcbor-parse-rust parse.rs
|
|
775
|
+
*
|
|
776
|
+
* @module dcbor-parse/parse
|
|
777
|
+
*/
|
|
778
|
+
/**
|
|
779
|
+
* Parses a dCBOR item from a string input.
|
|
780
|
+
*
|
|
781
|
+
* This function takes a string slice containing a dCBOR diagnostic notation
|
|
782
|
+
* encoded value and attempts to parse it into a `Cbor` object. If the input
|
|
783
|
+
* contains extra tokens after a valid item, an error is returned.
|
|
784
|
+
*
|
|
785
|
+
* @param src - A string containing the dCBOR-encoded data.
|
|
786
|
+
* @returns `Ok(Cbor)` if parsing is successful and the input contains exactly one
|
|
787
|
+
* valid dCBOR item, which itself might be an atomic value like a number or
|
|
788
|
+
* string, or a complex value like an array or map.
|
|
789
|
+
* `Err(ParseError)` if parsing fails or if extra tokens are found after the item.
|
|
790
|
+
*
|
|
791
|
+
* @example
|
|
792
|
+
* ```typescript
|
|
793
|
+
* const result = parseDcborItem("[1, 2, 3]");
|
|
794
|
+
* if (result.ok) {
|
|
795
|
+
* console.log(result.value.toDiagnostic()); // "[1, 2, 3]"
|
|
796
|
+
* }
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
799
|
+
function parseDcborItem(src) {
|
|
800
|
+
const lexer = new Lexer(src);
|
|
801
|
+
const firstTokenResult = expectToken(lexer);
|
|
802
|
+
if (!firstTokenResult.ok) {
|
|
803
|
+
if (firstTokenResult.error.type === "UnexpectedEndOfInput") return err(parseError.emptyInput());
|
|
804
|
+
return firstTokenResult;
|
|
805
|
+
}
|
|
806
|
+
const parseResult = parseItemToken(firstTokenResult.value, lexer);
|
|
807
|
+
if (!parseResult.ok) return parseResult;
|
|
808
|
+
if (lexer.next() !== void 0) return err(parseError.extraData(lexer.span()));
|
|
809
|
+
return parseResult;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Parses a dCBOR item from the beginning of a string and returns the parsed
|
|
813
|
+
* `Cbor` along with the number of bytes consumed.
|
|
814
|
+
*
|
|
815
|
+
* Unlike `parseDcborItem`, this function succeeds even if additional
|
|
816
|
+
* characters follow the first item. The returned index points to the first
|
|
817
|
+
* unparsed character after skipping any trailing whitespace or comments.
|
|
818
|
+
*
|
|
819
|
+
* @param src - A string containing the dCBOR-encoded data.
|
|
820
|
+
* @returns `Ok([Cbor, number])` with the parsed item and bytes consumed.
|
|
821
|
+
*
|
|
822
|
+
* @example
|
|
823
|
+
* ```typescript
|
|
824
|
+
* const result = parseDcborItemPartial("true )");
|
|
825
|
+
* if (result.ok) {
|
|
826
|
+
* const [cbor, used] = result.value;
|
|
827
|
+
* console.log(cbor.toDiagnostic()); // "true"
|
|
828
|
+
* console.log(used); // 5
|
|
829
|
+
* }
|
|
830
|
+
* ```
|
|
831
|
+
*/
|
|
832
|
+
function parseDcborItemPartial(src) {
|
|
833
|
+
const lexer = new Lexer(src);
|
|
834
|
+
const firstTokenResult = expectToken(lexer);
|
|
835
|
+
if (!firstTokenResult.ok) {
|
|
836
|
+
if (firstTokenResult.error.type === "UnexpectedEndOfInput") return err(parseError.emptyInput());
|
|
837
|
+
return firstTokenResult;
|
|
838
|
+
}
|
|
839
|
+
const parseResult = parseItemToken(firstTokenResult.value, lexer);
|
|
840
|
+
if (!parseResult.ok) return parseResult;
|
|
841
|
+
const consumed = lexer.next() !== void 0 ? lexer.span().start : src.length;
|
|
842
|
+
return ok([parseResult.value, consumed]);
|
|
843
|
+
}
|
|
844
|
+
function parseItem(lexer) {
|
|
845
|
+
const tokenResult = expectToken(lexer);
|
|
846
|
+
if (!tokenResult.ok) return tokenResult;
|
|
847
|
+
return parseItemToken(tokenResult.value, lexer);
|
|
848
|
+
}
|
|
849
|
+
function expectToken(lexer) {
|
|
850
|
+
const spanBefore = lexer.span();
|
|
851
|
+
const result = lexer.next();
|
|
852
|
+
if (result === void 0) return err(parseError.unexpectedEndOfInput());
|
|
853
|
+
if (!result.ok) {
|
|
854
|
+
if (isDefaultError(result.error)) return err(parseError.unrecognizedToken(spanBefore));
|
|
855
|
+
return result;
|
|
856
|
+
}
|
|
857
|
+
return result;
|
|
858
|
+
}
|
|
859
|
+
function parseItemToken(token$1, lexer) {
|
|
860
|
+
switch (token$1.type) {
|
|
861
|
+
case "Bool": return ok(cbor(token$1.value));
|
|
862
|
+
case "Null": return ok(cbor(null));
|
|
863
|
+
case "ByteStringHex": return ok(cbor(token$1.value));
|
|
864
|
+
case "ByteStringBase64": return ok(cbor(token$1.value));
|
|
865
|
+
case "DateLiteral": return ok(cbor(token$1.value));
|
|
866
|
+
case "Number": return ok(cbor(token$1.value));
|
|
867
|
+
case "NaN": return ok(cbor(NaN));
|
|
868
|
+
case "Infinity": return ok(cbor(Number.POSITIVE_INFINITY));
|
|
869
|
+
case "NegInfinity": return ok(cbor(Number.NEGATIVE_INFINITY));
|
|
870
|
+
case "String": return parseString(token$1.value, lexer.span());
|
|
871
|
+
case "UR": return parseUr(token$1.value, lexer.span());
|
|
872
|
+
case "TagValue": return parseNumberTag(token$1.value, lexer);
|
|
873
|
+
case "TagName": return parseNameTag(token$1.value, lexer);
|
|
874
|
+
case "KnownValueNumber": return ok(new KnownValue(token$1.value).taggedCbor());
|
|
875
|
+
case "KnownValueName": {
|
|
876
|
+
if (token$1.value === "") return ok(new KnownValue(0).taggedCbor());
|
|
877
|
+
const knownValue = knownValueForName(token$1.value);
|
|
878
|
+
if (knownValue !== void 0) return ok(knownValue.taggedCbor());
|
|
879
|
+
const tokenSpan = lexer.span();
|
|
880
|
+
return err(parseError.unknownKnownValueName(token$1.value, span(tokenSpan.start + 1, tokenSpan.end - 1)));
|
|
881
|
+
}
|
|
882
|
+
case "Unit": return ok(new KnownValue(0).taggedCbor());
|
|
883
|
+
case "BracketOpen": return parseArray(lexer);
|
|
884
|
+
case "BraceOpen": return parseMap(lexer);
|
|
885
|
+
case "BraceClose":
|
|
886
|
+
case "BracketClose":
|
|
887
|
+
case "ParenthesisOpen":
|
|
888
|
+
case "ParenthesisClose":
|
|
889
|
+
case "Colon":
|
|
890
|
+
case "Comma": return err(parseError.unexpectedToken(token$1, lexer.span()));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function parseString(s, tokenSpan) {
|
|
894
|
+
if (s.startsWith("\"") && s.endsWith("\"")) return ok(cbor(s.slice(1, -1)));
|
|
895
|
+
return err(parseError.unrecognizedToken(tokenSpan));
|
|
896
|
+
}
|
|
897
|
+
function tagForName(name) {
|
|
898
|
+
return getGlobalTagsStore().tagForName(name)?.value;
|
|
899
|
+
}
|
|
900
|
+
function knownValueForName(name) {
|
|
901
|
+
return KNOWN_VALUES.get().knownValueNamed(name);
|
|
902
|
+
}
|
|
903
|
+
function parseUr(ur, tokenSpan) {
|
|
904
|
+
const urType = ur.urTypeStr();
|
|
905
|
+
const tag = tagForName(urType);
|
|
906
|
+
if (tag !== void 0) return ok(cbor({
|
|
907
|
+
tag,
|
|
908
|
+
value: ur.cbor()
|
|
909
|
+
}));
|
|
910
|
+
return err(parseError.unknownUrType(urType, span(tokenSpan.start + 3, tokenSpan.start + 3 + urType.length)));
|
|
911
|
+
}
|
|
912
|
+
function parseNumberTag(tagValue, lexer) {
|
|
913
|
+
const itemResult = parseItem(lexer);
|
|
914
|
+
if (!itemResult.ok) return itemResult;
|
|
915
|
+
const closeResult = expectToken(lexer);
|
|
916
|
+
if (!closeResult.ok) {
|
|
917
|
+
if (closeResult.error.type === "UnexpectedEndOfInput") return err(parseError.unmatchedParentheses(lexer.span()));
|
|
918
|
+
return closeResult;
|
|
919
|
+
}
|
|
920
|
+
if (closeResult.value.type === "ParenthesisClose") return ok(cbor({
|
|
921
|
+
tag: tagValue,
|
|
922
|
+
value: itemResult.value
|
|
923
|
+
}));
|
|
924
|
+
return err(parseError.unmatchedParentheses(lexer.span()));
|
|
925
|
+
}
|
|
926
|
+
function parseNameTag(name, lexer) {
|
|
927
|
+
const tagSpan = span(lexer.span().start, lexer.span().end - 1);
|
|
928
|
+
const itemResult = parseItem(lexer);
|
|
929
|
+
if (!itemResult.ok) return itemResult;
|
|
930
|
+
const closeResult = expectToken(lexer);
|
|
931
|
+
if (!closeResult.ok) return closeResult;
|
|
932
|
+
if (closeResult.value.type === "ParenthesisClose") {
|
|
933
|
+
const tag = tagForName(name);
|
|
934
|
+
if (tag !== void 0) return ok(cbor({
|
|
935
|
+
tag,
|
|
936
|
+
value: itemResult.value
|
|
937
|
+
}));
|
|
938
|
+
return err(parseError.unknownTagName(name, tagSpan));
|
|
939
|
+
}
|
|
940
|
+
return err(parseError.unmatchedParentheses(lexer.span()));
|
|
941
|
+
}
|
|
942
|
+
function parseArray(lexer) {
|
|
943
|
+
const items = [];
|
|
944
|
+
let awaitsComma = false;
|
|
945
|
+
let awaitsItem = false;
|
|
946
|
+
while (true) {
|
|
947
|
+
const tokenResult = expectToken(lexer);
|
|
948
|
+
if (!tokenResult.ok) return tokenResult;
|
|
949
|
+
const token$1 = tokenResult.value;
|
|
950
|
+
if (token$1.type === "BracketClose" && !awaitsItem) return ok(cbor(items));
|
|
951
|
+
if (token$1.type === "Comma" && awaitsComma) {
|
|
952
|
+
awaitsItem = true;
|
|
953
|
+
awaitsComma = false;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (awaitsComma) return err(parseError.expectedComma(lexer.span()));
|
|
957
|
+
const itemResult = parseItemToken(token$1, lexer);
|
|
958
|
+
if (!itemResult.ok) return itemResult;
|
|
959
|
+
items.push(itemResult.value);
|
|
960
|
+
awaitsItem = false;
|
|
961
|
+
awaitsComma = true;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function parseMap(lexer) {
|
|
965
|
+
const map = new CborMap();
|
|
966
|
+
let awaitsComma = false;
|
|
967
|
+
let awaitsKey = false;
|
|
968
|
+
while (true) {
|
|
969
|
+
const tokenResult = expectToken(lexer);
|
|
970
|
+
if (!tokenResult.ok) {
|
|
971
|
+
if (tokenResult.error.type === "UnexpectedEndOfInput") return err(parseError.unmatchedBraces(lexer.span()));
|
|
972
|
+
return tokenResult;
|
|
973
|
+
}
|
|
974
|
+
const token$1 = tokenResult.value;
|
|
975
|
+
if (token$1.type === "BraceClose" && !awaitsKey) return ok(cbor(map));
|
|
976
|
+
if (token$1.type === "Comma" && awaitsComma) {
|
|
977
|
+
awaitsKey = true;
|
|
978
|
+
awaitsComma = false;
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
if (awaitsComma) return err(parseError.expectedComma(lexer.span()));
|
|
982
|
+
const keyResult = parseItemToken(token$1, lexer);
|
|
983
|
+
if (!keyResult.ok) return keyResult;
|
|
984
|
+
const key = keyResult.value;
|
|
985
|
+
const keySpan = lexer.span();
|
|
986
|
+
if (map.has(key)) return err(parseError.duplicateMapKey(keySpan));
|
|
987
|
+
const colonResult = expectToken(lexer);
|
|
988
|
+
if (!colonResult.ok) return colonResult;
|
|
989
|
+
if (colonResult.value.type !== "Colon") return err(parseError.expectedColon(lexer.span()));
|
|
990
|
+
const valueResult = parseItem(lexer);
|
|
991
|
+
if (!valueResult.ok) {
|
|
992
|
+
if (valueResult.error.type === "UnexpectedToken") {
|
|
993
|
+
if (valueResult.error.token.type === "BraceClose") return err(parseError.expectedMapKey(lexer.span()));
|
|
994
|
+
}
|
|
995
|
+
return valueResult;
|
|
996
|
+
}
|
|
997
|
+
map.set(key, valueResult.value);
|
|
998
|
+
awaitsKey = false;
|
|
999
|
+
awaitsComma = true;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
//#endregion
|
|
1004
|
+
//#region src/compose.ts
|
|
1005
|
+
/**
|
|
1006
|
+
* @bcts/dcbor-parse - Compose module
|
|
1007
|
+
*
|
|
1008
|
+
* This is a 1:1 TypeScript port of bc-dcbor-parse-rust compose.rs
|
|
1009
|
+
*
|
|
1010
|
+
* @module dcbor-parse/compose
|
|
1011
|
+
*/
|
|
1012
|
+
const composeError = {
|
|
1013
|
+
oddMapLength() {
|
|
1014
|
+
return { type: "OddMapLength" };
|
|
1015
|
+
},
|
|
1016
|
+
duplicateMapKey() {
|
|
1017
|
+
return { type: "DuplicateMapKey" };
|
|
1018
|
+
},
|
|
1019
|
+
parseError(error) {
|
|
1020
|
+
return {
|
|
1021
|
+
type: "ParseError",
|
|
1022
|
+
error
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
/**
|
|
1027
|
+
* Gets the error message for a compose error.
|
|
1028
|
+
*/
|
|
1029
|
+
function composeErrorMessage(error) {
|
|
1030
|
+
switch (error.type) {
|
|
1031
|
+
case "OddMapLength": return "Invalid odd map length";
|
|
1032
|
+
case "DuplicateMapKey": return "Duplicate map key";
|
|
1033
|
+
case "ParseError": return `Invalid CBOR item: ${error.error.type}`;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Creates a successful compose result.
|
|
1038
|
+
*/
|
|
1039
|
+
function composeOk(value) {
|
|
1040
|
+
return {
|
|
1041
|
+
ok: true,
|
|
1042
|
+
value
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Creates an error compose result.
|
|
1047
|
+
*/
|
|
1048
|
+
function composeErr(error) {
|
|
1049
|
+
return {
|
|
1050
|
+
ok: false,
|
|
1051
|
+
error
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Composes a dCBOR array from a slice of string slices, and returns a CBOR
|
|
1056
|
+
* object representing the array.
|
|
1057
|
+
*
|
|
1058
|
+
* Each string slice is parsed as a dCBOR item.
|
|
1059
|
+
*
|
|
1060
|
+
* @param array - Array of strings, each representing a dCBOR item
|
|
1061
|
+
* @returns A CBOR array containing all parsed items
|
|
1062
|
+
*
|
|
1063
|
+
* @example
|
|
1064
|
+
* ```typescript
|
|
1065
|
+
* const result = composeDcborArray(["1", "2", "3"]);
|
|
1066
|
+
* if (result.ok) {
|
|
1067
|
+
* console.log(result.value.toDiagnostic()); // "[1, 2, 3]"
|
|
1068
|
+
* }
|
|
1069
|
+
* ```
|
|
1070
|
+
*/
|
|
1071
|
+
function composeDcborArray(array) {
|
|
1072
|
+
const result = [];
|
|
1073
|
+
for (const item of array) {
|
|
1074
|
+
const parseResult = parseDcborItem(item);
|
|
1075
|
+
if (!parseResult.ok) return composeErr(composeError.parseError(parseResult.error));
|
|
1076
|
+
result.push(parseResult.value);
|
|
1077
|
+
}
|
|
1078
|
+
return composeOk(cbor(result));
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Composes a dCBOR map from a slice of string slices, and returns a CBOR
|
|
1082
|
+
* object representing the map.
|
|
1083
|
+
*
|
|
1084
|
+
* The length of the slice must be even, as each key must have a corresponding
|
|
1085
|
+
* value.
|
|
1086
|
+
*
|
|
1087
|
+
* Each string slice is parsed as a dCBOR item.
|
|
1088
|
+
*
|
|
1089
|
+
* @param array - Array of strings representing key-value pairs in alternating order
|
|
1090
|
+
* @returns A CBOR map containing all parsed key-value pairs
|
|
1091
|
+
*
|
|
1092
|
+
* @example
|
|
1093
|
+
* ```typescript
|
|
1094
|
+
* const result = composeDcborMap(["1", "2", "3", "4"]);
|
|
1095
|
+
* if (result.ok) {
|
|
1096
|
+
* console.log(result.value.toDiagnostic()); // "{1: 2, 3: 4}"
|
|
1097
|
+
* }
|
|
1098
|
+
* ```
|
|
1099
|
+
*/
|
|
1100
|
+
function composeDcborMap(array) {
|
|
1101
|
+
if (array.length % 2 !== 0) return composeErr(composeError.oddMapLength());
|
|
1102
|
+
const map = new CborMap();
|
|
1103
|
+
for (let i = 0; i < array.length; i += 2) {
|
|
1104
|
+
const keyStr = array[i];
|
|
1105
|
+
const valueStr = array[i + 1];
|
|
1106
|
+
const keyResult = parseDcborItem(keyStr);
|
|
1107
|
+
if (!keyResult.ok) return composeErr(composeError.parseError(keyResult.error));
|
|
1108
|
+
const valueResult = parseDcborItem(valueStr);
|
|
1109
|
+
if (!valueResult.ok) return composeErr(composeError.parseError(valueResult.error));
|
|
1110
|
+
if (map.has(keyResult.value)) return composeErr(composeError.duplicateMapKey());
|
|
1111
|
+
map.set(keyResult.value, valueResult.value);
|
|
1112
|
+
}
|
|
1113
|
+
return composeOk(cbor(map));
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
//#endregion
|
|
1117
|
+
export { Lexer, composeDcborArray, composeDcborMap, composeErr, composeError, composeErrorMessage, composeOk, defaultParseError, defaultSpan, err, errorMessage, errorSpan, fullErrorMessage, isDefaultError, isErr, isOk, ok, parseDcborItem, parseDcborItemPartial, parseError, span, token, unwrap, unwrapErr };
|
|
1118
|
+
//# sourceMappingURL=index.mjs.map
|