jsonh-ts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/build/index.d.ts +22 -0
  2. package/build/index.d.ts.map +1 -0
  3. package/build/index.js +22 -0
  4. package/build/index.js.map +1 -0
  5. package/build/json-token-type.d.ts +77 -0
  6. package/build/json-token-type.d.ts.map +1 -0
  7. package/build/json-token-type.js +79 -0
  8. package/build/json-token-type.js.map +1 -0
  9. package/build/jsonh-number-parser.d.ts +21 -0
  10. package/build/jsonh-number-parser.d.ts.map +1 -0
  11. package/build/jsonh-number-parser.js +174 -0
  12. package/build/jsonh-number-parser.js.map +1 -0
  13. package/build/jsonh-reader-options.d.ts +25 -0
  14. package/build/jsonh-reader-options.d.ts.map +1 -0
  15. package/build/jsonh-reader-options.js +26 -0
  16. package/build/jsonh-reader-options.js.map +1 -0
  17. package/build/jsonh-reader.d.ts +68 -0
  18. package/build/jsonh-reader.d.ts.map +1 -0
  19. package/build/jsonh-reader.js +1218 -0
  20. package/build/jsonh-reader.js.map +1 -0
  21. package/build/jsonh-token.d.ts +21 -0
  22. package/build/jsonh-token.d.ts.map +1 -0
  23. package/build/jsonh-token.js +36 -0
  24. package/build/jsonh-token.js.map +1 -0
  25. package/build/jsonh-version.d.ts +15 -0
  26. package/build/jsonh-version.d.ts.map +1 -0
  27. package/build/jsonh-version.js +17 -0
  28. package/build/jsonh-version.js.map +1 -0
  29. package/build/result-helpers.d.ts +6 -0
  30. package/build/result-helpers.d.ts.map +1 -0
  31. package/build/result-helpers.js +11 -0
  32. package/build/result-helpers.js.map +1 -0
  33. package/build/result.d.ts +48 -0
  34. package/build/result.d.ts.map +1 -0
  35. package/build/result.js +95 -0
  36. package/build/result.js.map +1 -0
  37. package/build/string-text-reader.d.ts +11 -0
  38. package/build/string-text-reader.d.ts.map +1 -0
  39. package/build/string-text-reader.js +33 -0
  40. package/build/string-text-reader.js.map +1 -0
  41. package/build/text-reader.d.ts +20 -0
  42. package/build/text-reader.d.ts.map +1 -0
  43. package/build/text-reader.js +19 -0
  44. package/build/text-reader.js.map +1 -0
  45. package/index.ts +21 -0
  46. package/json-token-type.ts +77 -0
  47. package/jsonh-number-parser.ts +191 -0
  48. package/jsonh-reader-options.ts +26 -0
  49. package/jsonh-reader.ts +1317 -0
  50. package/jsonh-token.ts +37 -0
  51. package/jsonh-version.ts +15 -0
  52. package/package.json +30 -0
  53. package/result.ts +97 -0
  54. package/string-text-reader.ts +35 -0
  55. package/text-reader.ts +30 -0
  56. package/tsconfig.json +38 -0
@@ -0,0 +1,1218 @@
1
+ "use strict";
2
+ var _a;
3
+ const JsonhReaderOptions = require("./jsonh-reader-options.js");
4
+ const TextReader = require("./text-reader.js");
5
+ const StringTextReader = require("./string-text-reader.js");
6
+ const JsonhToken = require("./jsonh-token.js");
7
+ const JsonTokenType = require("./json-token-type.js");
8
+ const JsonhNumberParser = require("./jsonh-number-parser.js");
9
+ const Result = require("./result.js");
10
+ /**
11
+ * A reader that reads JSONH tokens from a string.
12
+ */
13
+ class JsonhReader {
14
+ /**
15
+ * The text reader to read characters from.
16
+ */
17
+ #textReader;
18
+ /**
19
+ * The text reader to read characters from.
20
+ */
21
+ get textReader() {
22
+ return this.#textReader;
23
+ }
24
+ /**
25
+ * The options to use when reading JSONH.
26
+ */
27
+ #options;
28
+ /**
29
+ * The options to use when reading JSONH.
30
+ */
31
+ get options() {
32
+ return this.#options;
33
+ }
34
+ /**
35
+ * The number of characters read from {@link string}.
36
+ */
37
+ #charCounter;
38
+ /**
39
+ * The number of characters read from {@link string}.
40
+ */
41
+ get charCounter() {
42
+ return this.#charCounter;
43
+ }
44
+ /**
45
+ * Characters that cannot be used unescaped in quoteless strings.
46
+ */
47
+ static #reservedChars = ['\\', ',', ':', '[', ']', '{', '}', '/', '#', '"', '\''];
48
+ /**
49
+ * Characters that are considered newlines.
50
+ */
51
+ static #newlineChars = ['\n', '\r', '\u2028', '\u2029'];
52
+ /**
53
+ * Characters that are considered whitespace.
54
+ */
55
+ static #whitespaceChars = [
56
+ '\u0020', '\u00A0', '\u1680', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005',
57
+ '\u2006', '\u2007', '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000', '\u2028',
58
+ '\u2029', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u0085',
59
+ ];
60
+ /**
61
+ * Constructs a reader that reads JSONH from a text reader.
62
+ */
63
+ constructor(textReader, options = new JsonhReaderOptions()) {
64
+ if (typeof textReader === "string") {
65
+ throw new Error("Do not pass a string to new JsonhReader(). Use JsonhReader.fromString().");
66
+ }
67
+ this.#textReader = textReader;
68
+ this.#options = options;
69
+ this.#charCounter = 0;
70
+ }
71
+ /**
72
+ * Constructs a reader that reads JSONH from a text reader.
73
+ */
74
+ static fromTextReader(textReader, options = new JsonhReaderOptions()) {
75
+ return new _a(textReader, options);
76
+ }
77
+ /**
78
+ * Constructs a reader that reads JSONH from a string.
79
+ */
80
+ static fromString(string, options = new JsonhReaderOptions()) {
81
+ return new _a(new StringTextReader(string), options);
82
+ }
83
+ /**
84
+ * Parses a single element from a text reader.
85
+ */
86
+ static parseElementfromTextReader(textReader) {
87
+ return new _a(textReader).parseElement();
88
+ }
89
+ /**
90
+ * Parses a single element from a string.
91
+ */
92
+ static parseElementFromString(string) {
93
+ return this.fromString(string).parseElement();
94
+ }
95
+ /**
96
+ * Parses a single element from the reader.
97
+ */
98
+ parseElement() {
99
+ let currentNodes = [];
100
+ let currentPropertyName = null;
101
+ let submitNode = function (node) {
102
+ // Root value
103
+ if (currentNodes.length === 0) {
104
+ return true;
105
+ }
106
+ // Array item
107
+ if (currentPropertyName === null) {
108
+ currentNodes.at(-1).push(node);
109
+ return false;
110
+ }
111
+ // Object property
112
+ else {
113
+ currentNodes.at(-1)[currentPropertyName] = node;
114
+ currentPropertyName = null;
115
+ return false;
116
+ }
117
+ };
118
+ let startNode = function (node) {
119
+ submitNode(node);
120
+ currentNodes.push(node);
121
+ };
122
+ for (let tokenResult of this.readElement()) {
123
+ // Check error
124
+ if (tokenResult.isError) {
125
+ return Result.fromError(tokenResult.error);
126
+ }
127
+ switch (tokenResult.value.jsonType) {
128
+ // Null
129
+ case JsonTokenType.Null: {
130
+ let node = null;
131
+ if (submitNode(node)) {
132
+ return Result.fromValue(node);
133
+ }
134
+ break;
135
+ }
136
+ // True
137
+ case JsonTokenType.True: {
138
+ let node = true;
139
+ if (submitNode(node)) {
140
+ return Result.fromValue(node);
141
+ }
142
+ break;
143
+ }
144
+ // False
145
+ case JsonTokenType.False: {
146
+ let node = false;
147
+ if (submitNode(node)) {
148
+ return Result.fromValue(node);
149
+ }
150
+ break;
151
+ }
152
+ // String
153
+ case JsonTokenType.String: {
154
+ let node = tokenResult.value.value;
155
+ if (submitNode(node)) {
156
+ return Result.fromValue(node);
157
+ }
158
+ break;
159
+ }
160
+ // Number
161
+ case JsonTokenType.Number: {
162
+ // TODO
163
+ let result = JsonhNumberParser.parse(tokenResult.value.value);
164
+ if (result.isError) {
165
+ return Result.fromError(result.error);
166
+ }
167
+ let node = result.value;
168
+ if (submitNode(node)) {
169
+ return Result.fromValue(node);
170
+ }
171
+ break;
172
+ }
173
+ // Start Object
174
+ case JsonTokenType.StartObject: {
175
+ let node = {};
176
+ startNode(node);
177
+ break;
178
+ }
179
+ // Start Array
180
+ case JsonTokenType.StartArray: {
181
+ let node = [];
182
+ startNode(node);
183
+ break;
184
+ }
185
+ // End Object/Array
186
+ case JsonTokenType.EndObject:
187
+ case JsonTokenType.EndArray: {
188
+ // Nested node
189
+ if (currentNodes.length > 1) {
190
+ currentNodes.pop();
191
+ }
192
+ // Root node
193
+ else {
194
+ return Result.fromValue(currentNodes.at(-1));
195
+ }
196
+ break;
197
+ }
198
+ // Property Name
199
+ case JsonTokenType.PropertyName: {
200
+ currentPropertyName = tokenResult.value.value;
201
+ break;
202
+ }
203
+ // Comment
204
+ case JsonTokenType.Comment: {
205
+ break;
206
+ }
207
+ // Not Implemented
208
+ default: {
209
+ return Result.fromError(new Error("Token type not implemented"));
210
+ }
211
+ }
212
+ }
213
+ // End of input
214
+ return Result.fromError(new Error("Expected token, got end of input"));
215
+ }
216
+ /**
217
+ * Tries to find the given property name in the reader.
218
+ * For example, to find `c`:
219
+ * ```
220
+ * // Original position
221
+ * {
222
+ * "a": "1",
223
+ * "b": {
224
+ * "c": "2"
225
+ * },
226
+ * "c": // Final position
227
+ * "3"
228
+ * }
229
+ * ```
230
+ */
231
+ findPropertyValue(propertyName) {
232
+ let currentDepth = 0;
233
+ for (let tokenResult of this.readElement()) {
234
+ // Check error
235
+ if (tokenResult.isError) {
236
+ return false;
237
+ }
238
+ switch (tokenResult.value.jsonType) {
239
+ // Start structure
240
+ case JsonTokenType.StartObject:
241
+ case JsonTokenType.StartArray: {
242
+ currentDepth++;
243
+ break;
244
+ }
245
+ // End structure
246
+ case JsonTokenType.EndObject:
247
+ case JsonTokenType.EndArray: {
248
+ currentDepth--;
249
+ break;
250
+ }
251
+ // Property name
252
+ case JsonTokenType.PropertyName: {
253
+ if (currentDepth === 1 && tokenResult.value.value === propertyName) {
254
+ // Path found
255
+ return true;
256
+ }
257
+ break;
258
+ }
259
+ }
260
+ }
261
+ // Path not found
262
+ return false;
263
+ }
264
+ /**
265
+ * Reads a single element from the reader.
266
+ */
267
+ *readElement() {
268
+ // Comments & whitespace
269
+ for (let token of this.#readCommentsAndWhitespace()) {
270
+ if (token.isError) {
271
+ yield token;
272
+ return;
273
+ }
274
+ yield token;
275
+ }
276
+ // Peek result
277
+ let next = this.#peek();
278
+ if (next === null) {
279
+ yield Result.fromError(new Error("Expected token, got end of input"));
280
+ return;
281
+ }
282
+ // Object
283
+ if (next === '{') {
284
+ for (let token of this.#readObject()) {
285
+ if (token.isError) {
286
+ yield token;
287
+ return;
288
+ }
289
+ yield token;
290
+ }
291
+ }
292
+ // Array
293
+ else if (next === '[') {
294
+ for (let token of this.#readArray()) {
295
+ if (token.isError) {
296
+ yield token;
297
+ return;
298
+ }
299
+ yield token;
300
+ }
301
+ }
302
+ // Primitive value (null, true, false, string, number)
303
+ else {
304
+ let token = this.#readPrimitiveElement();
305
+ if (token.isError) {
306
+ yield token;
307
+ return;
308
+ }
309
+ // Detect braceless object from property name
310
+ if (token.value.jsonType === JsonTokenType.String) {
311
+ // Try read property name
312
+ let propertyNameTokens = [];
313
+ for (let propertyNameToken of this.#readPropertyName(token.value.value)) {
314
+ // Possible braceless object
315
+ if (!propertyNameToken.isError) {
316
+ propertyNameTokens.push(propertyNameToken.value);
317
+ }
318
+ // Primitive value (error reading property name)
319
+ else {
320
+ yield token;
321
+ for (let nonPropertyNameToken of propertyNameTokens) {
322
+ yield Result.fromValue(nonPropertyNameToken);
323
+ }
324
+ return;
325
+ }
326
+ }
327
+ // Braceless object
328
+ for (let objectToken of this.#readBracelessObject(propertyNameTokens)) {
329
+ if (objectToken.isError) {
330
+ yield objectToken;
331
+ return;
332
+ }
333
+ yield objectToken;
334
+ }
335
+ }
336
+ // Primitive value
337
+ else {
338
+ yield token;
339
+ }
340
+ }
341
+ }
342
+ *#readObject() {
343
+ // Opening brace
344
+ if (!this.#readOne('{')) {
345
+ // Braceless object
346
+ for (let token of this.#readBracelessObject()) {
347
+ if (token.isError) {
348
+ yield token;
349
+ return;
350
+ }
351
+ yield token;
352
+ }
353
+ return;
354
+ }
355
+ // Start object
356
+ yield Result.fromValue(new JsonhToken(JsonTokenType.StartObject));
357
+ while (true) {
358
+ // Comments & whitespace
359
+ for (let token of this.#readCommentsAndWhitespace()) {
360
+ if (token.isError) {
361
+ yield token;
362
+ return;
363
+ }
364
+ yield token;
365
+ }
366
+ let next = this.#peek();
367
+ if (next === null) {
368
+ // End of incomplete object
369
+ if (this.#options.incompleteInputs) {
370
+ yield Result.fromValue(new JsonhToken(JsonTokenType.EndObject));
371
+ return;
372
+ }
373
+ // Missing closing brace
374
+ yield Result.fromError(new Error("Expected `}` to end object, got end of input"));
375
+ return;
376
+ }
377
+ // Closing brace
378
+ if (next === '}') {
379
+ // End of object
380
+ this.#read();
381
+ yield Result.fromValue(new JsonhToken(JsonTokenType.EndObject));
382
+ return;
383
+ }
384
+ // Property
385
+ else {
386
+ for (let token of this.#readProperty()) {
387
+ if (token.isError) {
388
+ yield token;
389
+ return;
390
+ }
391
+ yield token;
392
+ }
393
+ }
394
+ }
395
+ }
396
+ *#readBracelessObject(propertyNameTokens = null) {
397
+ // Start of object
398
+ yield Result.fromValue(new JsonhToken(JsonTokenType.StartObject));
399
+ // Initial tokens
400
+ if (propertyNameTokens !== null) {
401
+ for (let initialToken of this.#readProperty(propertyNameTokens)) {
402
+ if (initialToken.isError) {
403
+ yield initialToken;
404
+ return;
405
+ }
406
+ yield initialToken;
407
+ }
408
+ }
409
+ while (true) {
410
+ // Comments & whitespace
411
+ for (let token of this.#readCommentsAndWhitespace()) {
412
+ if (token.isError) {
413
+ yield token;
414
+ return;
415
+ }
416
+ yield token;
417
+ }
418
+ if (this.#peek() === null) {
419
+ // End of braceless object
420
+ yield Result.fromValue(new JsonhToken(JsonTokenType.EndObject));
421
+ return;
422
+ }
423
+ // Property
424
+ for (let token of this.#readProperty()) {
425
+ if (token.isError) {
426
+ yield token;
427
+ return;
428
+ }
429
+ yield token;
430
+ }
431
+ }
432
+ }
433
+ *#readProperty(propertyNameTokens = null) {
434
+ // Property name
435
+ if (propertyNameTokens !== null) {
436
+ for (let token of propertyNameTokens) {
437
+ yield Result.fromValue(token);
438
+ }
439
+ }
440
+ else {
441
+ for (let token of this.#readPropertyName()) {
442
+ if (token.isError) {
443
+ yield token;
444
+ return;
445
+ }
446
+ yield token;
447
+ }
448
+ }
449
+ // Comments & whitespace
450
+ for (let token of this.#readCommentsAndWhitespace()) {
451
+ if (token.isError) {
452
+ yield token;
453
+ return;
454
+ }
455
+ yield token;
456
+ }
457
+ // Property value
458
+ for (let token of this.readElement()) {
459
+ if (token.isError) {
460
+ yield token;
461
+ return;
462
+ }
463
+ yield token;
464
+ }
465
+ // Comments & whitespace
466
+ for (let token of this.#readCommentsAndWhitespace()) {
467
+ if (token.isError) {
468
+ yield token;
469
+ return;
470
+ }
471
+ yield token;
472
+ }
473
+ // Optional comma
474
+ this.#readOne(',');
475
+ }
476
+ *#readPropertyName(string = null) {
477
+ // String
478
+ if (string === null) {
479
+ let stringToken = this.#readString();
480
+ if (stringToken.isError) {
481
+ yield stringToken;
482
+ return;
483
+ }
484
+ string = stringToken.value.value;
485
+ }
486
+ // Comments & whitespace
487
+ for (let token of this.#readCommentsAndWhitespace()) {
488
+ if (token.isError) {
489
+ yield token;
490
+ return;
491
+ }
492
+ yield token;
493
+ }
494
+ // Colon
495
+ if (!this.#readOne(':')) {
496
+ yield Result.fromError(new Error("Expected `:` after property name in object"));
497
+ return;
498
+ }
499
+ // End of property name
500
+ yield Result.fromValue(new JsonhToken(JsonTokenType.PropertyName, string));
501
+ }
502
+ *#readArray() {
503
+ // Opening bracket
504
+ if (!this.#readOne('[')) {
505
+ yield Result.fromError(new Error("Expected `[` to start array"));
506
+ return;
507
+ }
508
+ // Start of array
509
+ yield Result.fromValue(new JsonhToken(JsonTokenType.StartArray));
510
+ while (true) {
511
+ // Comments & whitespace
512
+ for (let token of this.#readCommentsAndWhitespace()) {
513
+ if (token.isError) {
514
+ yield token;
515
+ return;
516
+ }
517
+ yield token;
518
+ }
519
+ let next = this.#peek();
520
+ if (next === null) {
521
+ // End of incomplete array
522
+ if (this.#options.incompleteInputs) {
523
+ yield Result.fromValue(new JsonhToken(JsonTokenType.EndArray));
524
+ return;
525
+ }
526
+ // Missing closing bracket
527
+ yield Result.fromError(new Error("Expected `]` to end array, got end of input"));
528
+ return;
529
+ }
530
+ // Closing bracket
531
+ if (next === ']') {
532
+ // End of array
533
+ this.#read();
534
+ yield Result.fromValue(new JsonhToken(JsonTokenType.EndArray));
535
+ return;
536
+ }
537
+ // Item
538
+ else {
539
+ for (let token of this.#readItem()) {
540
+ if (token.isError) {
541
+ yield token;
542
+ return;
543
+ }
544
+ yield token;
545
+ }
546
+ }
547
+ }
548
+ }
549
+ *#readItem() {
550
+ // Element
551
+ for (let token of this.readElement()) {
552
+ if (token.isError) {
553
+ yield token;
554
+ return;
555
+ }
556
+ yield token;
557
+ }
558
+ // Comments & whitespace
559
+ for (let token of this.#readCommentsAndWhitespace()) {
560
+ if (token.isError) {
561
+ yield token;
562
+ return;
563
+ }
564
+ yield token;
565
+ }
566
+ // Optional comma
567
+ this.#readOne(',');
568
+ }
569
+ #readString() {
570
+ // Start quote
571
+ let startQuote = this.#readAny('"', '\'');
572
+ if (startQuote === null) {
573
+ return this.#readQuotelessString();
574
+ }
575
+ // Count multiple start quotes
576
+ let startQuoteCounter = 1;
577
+ while (this.#readOne(startQuote)) {
578
+ startQuoteCounter++;
579
+ }
580
+ // Empty string
581
+ if (startQuoteCounter === 2) {
582
+ return Result.fromValue(new JsonhToken(JsonTokenType.String, ""));
583
+ }
584
+ // Count multiple end quotes
585
+ let endQuoteCounter = 0;
586
+ // Read string
587
+ let stringBuilder = "";
588
+ while (true) {
589
+ let next = this.#read();
590
+ if (next === null) {
591
+ return Result.fromError(new Error("Expected end of string, got end of input"));
592
+ }
593
+ // Partial end quote was actually part of string
594
+ if (next !== startQuote) {
595
+ stringBuilder += startQuote.repeat(endQuoteCounter);
596
+ endQuoteCounter = 0;
597
+ }
598
+ // End quote
599
+ if (next === startQuote) {
600
+ endQuoteCounter++;
601
+ if (endQuoteCounter === startQuoteCounter) {
602
+ break;
603
+ }
604
+ }
605
+ // Escape sequence
606
+ else if (next === '\\') {
607
+ let escapeSequenceResult = this.#readEscapeSequence();
608
+ if (escapeSequenceResult.isError) {
609
+ return Result.fromError(escapeSequenceResult.error);
610
+ }
611
+ stringBuilder += escapeSequenceResult.value;
612
+ }
613
+ // Literal character
614
+ else {
615
+ stringBuilder += next;
616
+ }
617
+ }
618
+ // Condition: skip remaining steps unless started with multiple quotes
619
+ if (startQuoteCounter > 1) {
620
+ // Pass 1: count leading whitespace -> newline
621
+ let hasLeadingWhitespaceNewline = false;
622
+ let leadingWhitespaceNewlineCounter = 0;
623
+ for (let index = 0; index < stringBuilder.length; index++) {
624
+ let next = stringBuilder.at(index);
625
+ // Newline
626
+ if (_a.#newlineChars.includes(next)) {
627
+ // Join CR LF
628
+ if (next === '\r' && index + 1 < stringBuilder.length && stringBuilder[index + 1] === '\n') {
629
+ index++;
630
+ }
631
+ hasLeadingWhitespaceNewline = true;
632
+ leadingWhitespaceNewlineCounter = index + 1;
633
+ break;
634
+ }
635
+ // Non-whitespace
636
+ else if (!_a.#whitespaceChars.includes(next)) {
637
+ break;
638
+ }
639
+ }
640
+ // Condition: skip remaining steps if pass 1 failed
641
+ if (hasLeadingWhitespaceNewline) {
642
+ // Pass 2: count trailing newline -> whitespace
643
+ let hasTrailingNewlineWhitespace = false;
644
+ let lastNewlineIndex = 0;
645
+ let trailingWhitespaceCounter = 0;
646
+ for (let index = 0; index < stringBuilder.length; index++) {
647
+ let next = stringBuilder.at(index);
648
+ // Newline
649
+ if (_a.#newlineChars.includes(next)) {
650
+ hasTrailingNewlineWhitespace = true;
651
+ lastNewlineIndex = index;
652
+ trailingWhitespaceCounter = 0;
653
+ // Join CR LF
654
+ if (next === '\r' && index + 1 < stringBuilder.length && stringBuilder[index + 1] === '\n') {
655
+ index++;
656
+ }
657
+ }
658
+ // Whitespace
659
+ else if (_a.#whitespaceChars.includes(next)) {
660
+ trailingWhitespaceCounter++;
661
+ }
662
+ // Non-whitespace
663
+ else {
664
+ hasTrailingNewlineWhitespace = false;
665
+ trailingWhitespaceCounter = 0;
666
+ }
667
+ }
668
+ // Condition: skip remaining steps if pass 2 failed
669
+ if (hasTrailingNewlineWhitespace) {
670
+ // Pass 3: strip trailing newline -> whitespace
671
+ stringBuilder = _a.#removeRange(stringBuilder, lastNewlineIndex, stringBuilder.length - lastNewlineIndex);
672
+ // Pass 4: strip leading whitespace -> newline
673
+ stringBuilder = _a.#removeRange(stringBuilder, 0, leadingWhitespaceNewlineCounter);
674
+ // Condition: skip remaining steps if no trailing whitespace
675
+ if (trailingWhitespaceCounter > 0) {
676
+ // Pass 5: strip line-leading whitespace
677
+ let isLineLeadingWhitespace = true;
678
+ let lineLeadingWhitespaceCounter = 0;
679
+ for (let index = 0; index < stringBuilder.length; index++) {
680
+ let next = stringBuilder.at(index);
681
+ // Newline
682
+ if (_a.#newlineChars.includes(next)) {
683
+ isLineLeadingWhitespace = true;
684
+ lineLeadingWhitespaceCounter = 0;
685
+ }
686
+ // Whitespace
687
+ else if (_a.#whitespaceChars.includes(next)) {
688
+ if (isLineLeadingWhitespace) {
689
+ // Increment line-leading whitespace
690
+ lineLeadingWhitespaceCounter++;
691
+ // Maximum line-leading whitespace reached
692
+ if (lineLeadingWhitespaceCounter === trailingWhitespaceCounter) {
693
+ // Remove line-leading whitespace
694
+ stringBuilder = _a.#removeRange(stringBuilder, index + 1 - lineLeadingWhitespaceCounter, lineLeadingWhitespaceCounter);
695
+ index -= lineLeadingWhitespaceCounter;
696
+ // Exit line-leading whitespace
697
+ isLineLeadingWhitespace = false;
698
+ }
699
+ }
700
+ }
701
+ // Non-whitespace
702
+ else {
703
+ if (isLineLeadingWhitespace) {
704
+ // Remove partial line-leading whitespace
705
+ stringBuilder = _a.#removeRange(stringBuilder, index - lineLeadingWhitespaceCounter, lineLeadingWhitespaceCounter);
706
+ index -= lineLeadingWhitespaceCounter;
707
+ // Exit line-leading whitespace
708
+ isLineLeadingWhitespace = false;
709
+ }
710
+ }
711
+ }
712
+ }
713
+ }
714
+ }
715
+ }
716
+ // End of string
717
+ return Result.fromValue(new JsonhToken(JsonTokenType.String, stringBuilder));
718
+ }
719
+ #readQuotelessString(initialChars = "") {
720
+ let isNamedLiteralPossible = true;
721
+ // Read quoteless string
722
+ let stringBuilder = initialChars;
723
+ while (true) {
724
+ // Peek char
725
+ let next = this.#peek();
726
+ if (next === null) {
727
+ break;
728
+ }
729
+ // Escape sequence
730
+ if (next === '\\') {
731
+ this.#read();
732
+ let escapeSequenceResult = this.#readEscapeSequence();
733
+ if (escapeSequenceResult.isError) {
734
+ return Result.fromError(escapeSequenceResult.error);
735
+ }
736
+ stringBuilder += escapeSequenceResult.value;
737
+ isNamedLiteralPossible = false;
738
+ }
739
+ // End on reserved character
740
+ else if (_a.#reservedChars.includes(next)) {
741
+ break;
742
+ }
743
+ // End on newline
744
+ else if (_a.#newlineChars.includes(next)) {
745
+ break;
746
+ }
747
+ // Literal character
748
+ else {
749
+ this.#read();
750
+ stringBuilder += next;
751
+ }
752
+ }
753
+ // Ensure not empty
754
+ if (stringBuilder.length === 0) {
755
+ return Result.fromError(new Error("Empty quoteless string"));
756
+ }
757
+ // Trim whitespace
758
+ stringBuilder = _a.#trimAny(stringBuilder, _a.#whitespaceChars);
759
+ // Match named literal
760
+ if (isNamedLiteralPossible) {
761
+ if (stringBuilder === "null") {
762
+ return Result.fromValue(new JsonhToken(JsonTokenType.Null));
763
+ }
764
+ else if (stringBuilder === "true") {
765
+ return Result.fromValue(new JsonhToken(JsonTokenType.True));
766
+ }
767
+ else if (stringBuilder === "false") {
768
+ return Result.fromValue(new JsonhToken(JsonTokenType.False));
769
+ }
770
+ }
771
+ // End of quoteless string
772
+ return Result.fromValue(new JsonhToken(JsonTokenType.String, stringBuilder));
773
+ }
774
+ #detectQuotelessString() {
775
+ // Read whitespace
776
+ let whitespaceBuilder = "";
777
+ while (true) {
778
+ // Read char
779
+ let next = this.#peek();
780
+ if (next === null) {
781
+ break;
782
+ }
783
+ // Newline
784
+ if (_a.#newlineChars.includes(next)) {
785
+ // Quoteless strings cannot contain unescaped newlines
786
+ return {
787
+ foundQuotelessString: false,
788
+ whitespaceChars: whitespaceBuilder
789
+ };
790
+ }
791
+ // End of whitespace
792
+ if (!_a.#whitespaceChars.includes(next)) {
793
+ break;
794
+ }
795
+ // Whitespace
796
+ whitespaceBuilder += next;
797
+ this.#read();
798
+ }
799
+ // Found quoteless string if found backslash or non-reserved char
800
+ let nextChar = this.#peek();
801
+ return {
802
+ foundQuotelessString: nextChar !== null && (nextChar === '\\' || !_a.#reservedChars.includes(nextChar)),
803
+ whitespaceChars: whitespaceBuilder
804
+ };
805
+ }
806
+ #readNumber() {
807
+ // Read number
808
+ let numberBuilder = "";
809
+ // Read sign
810
+ let sign = this.#readAny('-', '+');
811
+ if (sign !== null) {
812
+ numberBuilder += sign;
813
+ }
814
+ // Read base
815
+ let baseDigits = "0123456789";
816
+ let hasBaseSpecifier = false;
817
+ if (this.#readOne('0')) {
818
+ numberBuilder += '0';
819
+ let hexBaseChar = this.#readAny('x', 'X');
820
+ if (hexBaseChar !== null) {
821
+ numberBuilder += hexBaseChar;
822
+ baseDigits = "0123456789abcdef";
823
+ hasBaseSpecifier = true;
824
+ }
825
+ else {
826
+ let binaryBaseChar = this.#readAny('b', 'B');
827
+ if (binaryBaseChar !== null) {
828
+ numberBuilder += binaryBaseChar;
829
+ baseDigits = "01";
830
+ hasBaseSpecifier = true;
831
+ }
832
+ else {
833
+ let octalBaseChar = this.#readAny('o', 'O');
834
+ if (octalBaseChar !== null) {
835
+ numberBuilder += octalBaseChar;
836
+ baseDigits = "01234567";
837
+ hasBaseSpecifier = true;
838
+ }
839
+ }
840
+ }
841
+ }
842
+ // Read main number
843
+ let mainResult = this.#readNumberNoExponent(baseDigits, hasBaseSpecifier);
844
+ numberBuilder += mainResult.numberNoExponent;
845
+ if (mainResult.result.isError) {
846
+ return { numberToken: Result.fromError(mainResult.result.error), partialCharsRead: numberBuilder };
847
+ }
848
+ // Hexadecimal exponent
849
+ if (numberBuilder.at(-1) === 'e' || numberBuilder.at(-1) === 'E') {
850
+ // Read sign
851
+ let exponentSign = this.#readAny('-', '+');
852
+ if (exponentSign !== null) {
853
+ numberBuilder += exponentSign;
854
+ // Read exponent number
855
+ let exponentResult = this.#readNumberNoExponent(baseDigits, hasBaseSpecifier);
856
+ numberBuilder += exponentResult.numberNoExponent;
857
+ if (exponentResult.result.isError) {
858
+ return { numberToken: Result.fromError(exponentResult.result.error), partialCharsRead: numberBuilder };
859
+ }
860
+ }
861
+ }
862
+ // Exponent
863
+ else {
864
+ let exponentChar = this.#readAny('e', 'E');
865
+ if (exponentChar !== null) {
866
+ numberBuilder += exponentChar;
867
+ // Read sign
868
+ let exponentSign = this.#readAny('-', '+');
869
+ if (exponentSign !== null) {
870
+ numberBuilder += exponentSign;
871
+ }
872
+ // Read exponent number
873
+ let exponentResult = this.#readNumberNoExponent(baseDigits, hasBaseSpecifier);
874
+ numberBuilder += exponentResult.numberNoExponent;
875
+ if (exponentResult.result.isError) {
876
+ return { numberToken: Result.fromError(exponentResult.result.error), partialCharsRead: numberBuilder };
877
+ }
878
+ }
879
+ }
880
+ // End of number
881
+ return { numberToken: Result.fromValue(new JsonhToken(JsonTokenType.Number, numberBuilder)), partialCharsRead: "" };
882
+ }
883
+ #readNumberNoExponent(baseDigits, hasBaseSpecifier) {
884
+ let numberBuilder = "";
885
+ // Leading underscore
886
+ if (!hasBaseSpecifier && this.#peek() === '_') {
887
+ return { result: Result.fromError(new Error("Leading `_` in number")), numberNoExponent: numberBuilder };
888
+ }
889
+ let isFraction = false;
890
+ let isEmpty = true;
891
+ while (true) {
892
+ // Peek char
893
+ let next = this.#peek();
894
+ if (next === null) {
895
+ break;
896
+ }
897
+ // Digit
898
+ if (baseDigits.includes(next.toLowerCase())) {
899
+ this.#read();
900
+ numberBuilder += next;
901
+ isEmpty = false;
902
+ }
903
+ // Dot
904
+ else if (next === '.') {
905
+ this.#read();
906
+ numberBuilder += next;
907
+ isEmpty = false;
908
+ // Duplicate dot
909
+ if (isFraction) {
910
+ return { result: Result.fromError(new Error("Duplicate `.` in number")), numberNoExponent: numberBuilder };
911
+ }
912
+ isFraction = true;
913
+ }
914
+ // Underscore
915
+ else if (next === '_') {
916
+ this.#read();
917
+ numberBuilder += next;
918
+ isEmpty = false;
919
+ }
920
+ // Other
921
+ else {
922
+ break;
923
+ }
924
+ }
925
+ // Ensure not empty
926
+ if (isEmpty) {
927
+ return { result: Result.fromError(new Error("Empty number")), numberNoExponent: numberBuilder };
928
+ }
929
+ // Ensure at least one digit
930
+ if (!_a.#containsAnyExcept(numberBuilder, ['.', '-', '+', '_'])) {
931
+ return { result: Result.fromError(new Error("Number must have at least one digit")), numberNoExponent: numberBuilder };
932
+ }
933
+ // Trailing underscore
934
+ if (numberBuilder.endsWith('_')) {
935
+ return { result: Result.fromError(new Error("Trailing `_` in number")), numberNoExponent: numberBuilder };
936
+ }
937
+ // End of number
938
+ return { result: Result.fromValue(), numberNoExponent: numberBuilder };
939
+ }
940
+ #readNumberOrQuotelessString() {
941
+ // Read number
942
+ let number = this.#readNumber();
943
+ if (!number.numberToken.isError) {
944
+ // Try read quoteless string starting with number
945
+ let detectQuotelessStringResult = this.#detectQuotelessString();
946
+ if (detectQuotelessStringResult.foundQuotelessString) {
947
+ return this.#readQuotelessString(number.numberToken.value.value + detectQuotelessStringResult.whitespaceChars);
948
+ }
949
+ // Otherwise, accept number
950
+ else {
951
+ return number.numberToken;
952
+ }
953
+ }
954
+ // Read quoteless string starting with malformed number
955
+ else {
956
+ return this.#readQuotelessString(number.partialCharsRead);
957
+ }
958
+ }
959
+ #readPrimitiveElement() {
960
+ // Peek char
961
+ let next = this.#peek();
962
+ if (next === null) {
963
+ return Result.fromError(new Error("Expected primitive element, got end of input"));
964
+ }
965
+ // Number
966
+ if (next.length === 1 && ((next >= '0' && next <= '9') || (next === '-' || next === '+') || next === '.')) {
967
+ return this.#readNumberOrQuotelessString();
968
+ }
969
+ // String
970
+ else if (next === '"' || next === '\'') {
971
+ return this.#readString();
972
+ }
973
+ // Quoteless string (or named literal)
974
+ else {
975
+ return this.#readQuotelessString();
976
+ }
977
+ }
978
+ *#readCommentsAndWhitespace() {
979
+ while (true) {
980
+ // Whitespace
981
+ this.#readWhitespace();
982
+ // Peek char
983
+ let next = this.#peek();
984
+ if (next === null) {
985
+ return;
986
+ }
987
+ // Comment
988
+ if (next === '#' || next === '/') {
989
+ yield this.#readComment();
990
+ }
991
+ // End of comments
992
+ else {
993
+ return;
994
+ }
995
+ }
996
+ }
997
+ #readComment() {
998
+ let blockComment = false;
999
+ // Hash-style comment
1000
+ if (this.#readOne('#')) {
1001
+ }
1002
+ else if (this.#readOne('/')) {
1003
+ // Line-style comment
1004
+ if (this.#readOne('/')) {
1005
+ }
1006
+ // Block-style comment
1007
+ else if (this.#readOne('*')) {
1008
+ blockComment = true;
1009
+ }
1010
+ else {
1011
+ return Result.fromError(new Error("Unexpected `/`"));
1012
+ }
1013
+ }
1014
+ else {
1015
+ return Result.fromError(new Error("Unexpected character"));
1016
+ }
1017
+ // Read comment
1018
+ let commentBuilder = "";
1019
+ while (true) {
1020
+ // Read char
1021
+ let next = this.#read();
1022
+ if (blockComment) {
1023
+ // Error
1024
+ if (next === null) {
1025
+ return Result.fromError(new Error("Expected end of block comment, got end of input"));
1026
+ }
1027
+ // End of block comment
1028
+ if (next === '*' && this.#readOne('/')) {
1029
+ return Result.fromValue(new JsonhToken(JsonTokenType.Comment, commentBuilder));
1030
+ }
1031
+ }
1032
+ else {
1033
+ // End of line comment
1034
+ if (next === null || _a.#newlineChars.includes(next)) {
1035
+ return Result.fromValue(new JsonhToken(JsonTokenType.Comment, commentBuilder));
1036
+ }
1037
+ }
1038
+ // Comment char
1039
+ commentBuilder += next;
1040
+ }
1041
+ }
1042
+ #readWhitespace() {
1043
+ while (true) {
1044
+ // Peek char
1045
+ let next = this.#peek();
1046
+ if (next === null) {
1047
+ return;
1048
+ }
1049
+ // Whitespace
1050
+ if (_a.#whitespaceChars.includes(next)) {
1051
+ this.#read();
1052
+ }
1053
+ // End of whitespace
1054
+ else {
1055
+ return;
1056
+ }
1057
+ }
1058
+ }
1059
+ #readHexSequence(length) {
1060
+ let hexChars = "";
1061
+ for (let index = 0; index < length; index++) {
1062
+ let next = this.#read();
1063
+ // Hex digit
1064
+ if (next !== null && ((next >= "0" && next <= "9") || (next >= "A" && next <= "F") || (next >= "a" && next <= "f"))) {
1065
+ hexChars += next;
1066
+ }
1067
+ // Unexpected char
1068
+ else {
1069
+ return Result.fromError(new Error("Incorrect number of hexadecimal digits in unicode escape sequence"));
1070
+ }
1071
+ }
1072
+ // Parse unicode character from hex digits
1073
+ return Result.fromValue(Number.parseInt(hexChars, 16));
1074
+ }
1075
+ #readEscapeSequence() {
1076
+ let escapeChar = this.#read();
1077
+ if (escapeChar === null) {
1078
+ return Result.fromError(new Error("Expected escape sequence, got end of input"));
1079
+ }
1080
+ // Reverse solidus
1081
+ if (escapeChar === '\\') {
1082
+ return Result.fromValue('\\');
1083
+ }
1084
+ // Backspace
1085
+ else if (escapeChar === 'b') {
1086
+ return Result.fromValue('\b');
1087
+ }
1088
+ // Form feed
1089
+ else if (escapeChar === 'f') {
1090
+ return Result.fromValue('\f');
1091
+ }
1092
+ // Newline
1093
+ else if (escapeChar === 'n') {
1094
+ return Result.fromValue('\n');
1095
+ }
1096
+ // Carriage return
1097
+ else if (escapeChar === 'r') {
1098
+ return Result.fromValue('\r');
1099
+ }
1100
+ // Tab
1101
+ else if (escapeChar === 't') {
1102
+ return Result.fromValue('\t');
1103
+ }
1104
+ // Vertical tab
1105
+ else if (escapeChar === 'v') {
1106
+ return Result.fromValue('\v');
1107
+ }
1108
+ // Null
1109
+ else if (escapeChar === '0') {
1110
+ return Result.fromValue('\0');
1111
+ }
1112
+ // Alert
1113
+ else if (escapeChar === 'a') {
1114
+ return Result.fromValue('\a');
1115
+ }
1116
+ // Escape
1117
+ else if (escapeChar === 'e') {
1118
+ return Result.fromValue('\u001b');
1119
+ }
1120
+ // Unicode hex sequence
1121
+ else if (escapeChar === 'u') {
1122
+ let hexSequence = this.#readHexSequence(4);
1123
+ if (hexSequence.isError) {
1124
+ return Result.fromError(hexSequence.error);
1125
+ }
1126
+ return Result.fromValue(String.fromCodePoint(hexSequence.value));
1127
+ }
1128
+ // Short unicode hex sequence
1129
+ else if (escapeChar === 'x') {
1130
+ let hexSequence = this.#readHexSequence(2);
1131
+ if (hexSequence.isError) {
1132
+ return Result.fromError(hexSequence.error);
1133
+ }
1134
+ return Result.fromValue(String.fromCodePoint(hexSequence.value));
1135
+ }
1136
+ // Long unicode hex sequence
1137
+ else if (escapeChar === 'U') {
1138
+ let hexSequence = this.#readHexSequence(8);
1139
+ if (hexSequence.isError) {
1140
+ return Result.fromError(hexSequence.error);
1141
+ }
1142
+ return Result.fromValue(String.fromCodePoint(hexSequence.value));
1143
+ }
1144
+ // Escaped newline
1145
+ else if (_a.#newlineChars.includes(escapeChar)) {
1146
+ // Join CR LF
1147
+ if (escapeChar === '\r') {
1148
+ this.#readOne('\n');
1149
+ }
1150
+ return Result.fromValue("");
1151
+ }
1152
+ // Other
1153
+ else {
1154
+ return Result.fromValue(escapeChar);
1155
+ }
1156
+ }
1157
+ #peek() {
1158
+ let next = this.#textReader.peek();
1159
+ if (next === null) {
1160
+ return null;
1161
+ }
1162
+ return next;
1163
+ }
1164
+ #read() {
1165
+ let next = this.#textReader.read();
1166
+ if (next === null) {
1167
+ return null;
1168
+ }
1169
+ this.#charCounter++;
1170
+ return next;
1171
+ }
1172
+ #readOne(option) {
1173
+ if (this.#peek() === option) {
1174
+ this.#read();
1175
+ return true;
1176
+ }
1177
+ return false;
1178
+ }
1179
+ #readAny(...options) {
1180
+ // Peek char
1181
+ let next = this.#peek();
1182
+ if (next === null) {
1183
+ return null;
1184
+ }
1185
+ // Match option
1186
+ if (!options.includes(next)) {
1187
+ return null;
1188
+ }
1189
+ // Option matched
1190
+ this.#read();
1191
+ return next;
1192
+ }
1193
+ static #removeRange(input, start, count) {
1194
+ return input.slice(0, start) + input.slice(start + count);
1195
+ }
1196
+ static #trimAny(input, trimChars) {
1197
+ let start = 0;
1198
+ let end = input.length;
1199
+ while (start < end && trimChars.includes(input.at(start))) {
1200
+ start++;
1201
+ }
1202
+ while (end > start && trimChars.includes(input.at(end - 1))) {
1203
+ end--;
1204
+ }
1205
+ return input.slice(start, end);
1206
+ }
1207
+ static #containsAnyExcept(input, allowed) {
1208
+ for (let char of input) {
1209
+ if (!allowed.includes(char)) {
1210
+ return true;
1211
+ }
1212
+ }
1213
+ return false;
1214
+ }
1215
+ }
1216
+ _a = JsonhReader;
1217
+ module.exports = JsonhReader;
1218
+ //# sourceMappingURL=jsonh-reader.js.map