quick-json5 0.1.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 (84) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +66 -0
  3. package/dist/exports/Exports.d.ts +6 -0
  4. package/dist/exports/Exports.d.ts.map +1 -0
  5. package/dist/exports/Exports.js +6 -0
  6. package/dist/exports/Exports.js.map +1 -0
  7. package/dist/json/JsonEncoder.d.ts +3 -0
  8. package/dist/json/JsonEncoder.d.ts.map +1 -0
  9. package/dist/json/JsonEncoder.js +147 -0
  10. package/dist/json/JsonEncoder.js.map +1 -0
  11. package/dist/json/JsonParser.d.ts +3 -0
  12. package/dist/json/JsonParser.d.ts.map +1 -0
  13. package/dist/json/JsonParser.js +378 -0
  14. package/dist/json/JsonParser.js.map +1 -0
  15. package/dist/json5/Json5Encoder.d.ts +3 -0
  16. package/dist/json5/Json5Encoder.d.ts.map +1 -0
  17. package/dist/json5/Json5Encoder.js +155 -0
  18. package/dist/json5/Json5Encoder.js.map +1 -0
  19. package/dist/json5/Json5Parser.d.ts +3 -0
  20. package/dist/json5/Json5Parser.d.ts.map +1 -0
  21. package/dist/json5/Json5Parser.js +781 -0
  22. package/dist/json5/Json5Parser.js.map +1 -0
  23. package/dist/tests/Json5EncoderTest.d.ts +2 -0
  24. package/dist/tests/Json5EncoderTest.d.ts.map +1 -0
  25. package/dist/tests/Json5EncoderTest.js +44 -0
  26. package/dist/tests/Json5EncoderTest.js.map +1 -0
  27. package/dist/tests/Json5ParserTests.d.ts +3 -0
  28. package/dist/tests/Json5ParserTests.d.ts.map +1 -0
  29. package/dist/tests/Json5ParserTests.js +211 -0
  30. package/dist/tests/Json5ParserTests.js.map +1 -0
  31. package/dist/tests/JsonEncoderTests.d.ts +2 -0
  32. package/dist/tests/JsonEncoderTests.d.ts.map +1 -0
  33. package/dist/tests/JsonEncoderTests.js +44 -0
  34. package/dist/tests/JsonEncoderTests.js.map +1 -0
  35. package/dist/tests/JsonParserTests.d.ts +3 -0
  36. package/dist/tests/JsonParserTests.d.ts.map +1 -0
  37. package/dist/tests/JsonParserTests.js +140 -0
  38. package/dist/tests/JsonParserTests.js.map +1 -0
  39. package/dist/tests/Test.d.ts +2 -0
  40. package/dist/tests/Test.d.ts.map +1 -0
  41. package/dist/tests/Test.js +9 -0
  42. package/dist/tests/Test.js.map +1 -0
  43. package/dist/types/Types.d.ts +8 -0
  44. package/dist/types/Types.d.ts.map +1 -0
  45. package/dist/types/Types.js +2 -0
  46. package/dist/types/Types.js.map +1 -0
  47. package/dist/unused/Unused.d.ts +2 -0
  48. package/dist/unused/Unused.d.ts.map +1 -0
  49. package/dist/unused/Unused.js +47 -0
  50. package/dist/unused/Unused.js.map +1 -0
  51. package/dist/utilities/JsonParseError.d.ts +7 -0
  52. package/dist/utilities/JsonParseError.d.ts.map +1 -0
  53. package/dist/utilities/JsonParseError.js +10 -0
  54. package/dist/utilities/JsonParseError.js.map +1 -0
  55. package/dist/utilities/ObjectUtilities.d.ts +2 -0
  56. package/dist/utilities/ObjectUtilities.d.ts.map +1 -0
  57. package/dist/utilities/ObjectUtilities.js +142 -0
  58. package/dist/utilities/ObjectUtilities.js.map +1 -0
  59. package/dist/utilities/TypedArray.d.ts +4 -0
  60. package/dist/utilities/TypedArray.d.ts.map +1 -0
  61. package/dist/utilities/TypedArray.js +2 -0
  62. package/dist/utilities/TypedArray.js.map +1 -0
  63. package/dist/utilities/Utilities.d.ts +17 -0
  64. package/dist/utilities/Utilities.d.ts.map +1 -0
  65. package/dist/utilities/Utilities.js +97 -0
  66. package/dist/utilities/Utilities.js.map +1 -0
  67. package/package.json +38 -0
  68. package/src/exports/Exports.ts +7 -0
  69. package/src/json/JsonEncoder.ts +180 -0
  70. package/src/json/JsonParser.ts +491 -0
  71. package/src/json5/Json5Encoder.ts +189 -0
  72. package/src/json5/Json5Parser.ts +959 -0
  73. package/src/tests/Json5EncoderTest.ts +51 -0
  74. package/src/tests/Json5ParserTests.ts +237 -0
  75. package/src/tests/JsonEncoderTests.ts +51 -0
  76. package/src/tests/JsonParserTests.ts +159 -0
  77. package/src/tests/Test.ts +13 -0
  78. package/src/types/Types.ts +9 -0
  79. package/src/unused/Unused.ts +51 -0
  80. package/src/utilities/JsonParseError.ts +7 -0
  81. package/src/utilities/ObjectUtilities.ts +184 -0
  82. package/src/utilities/TypedArray.ts +39 -0
  83. package/src/utilities/Utilities.ts +130 -0
  84. package/tsconfig.json +55 -0
@@ -0,0 +1,781 @@
1
+ import { JsonParserError } from '../utilities/JsonParseError.js';
2
+ import { getPositionInfo, hexCharCodeToNumber, positivePowersOf10 } from '../utilities/Utilities.js';
3
+ export function parse(jsonString, reviver, options) {
4
+ if (typeof jsonString !== 'string') {
5
+ throw new TypeError(`Given JSON5 string argument is not a string.`);
6
+ }
7
+ if (reviver !== undefined && typeof reviver !== 'function') {
8
+ throw new TypeError(`Reviver can only be a function.`);
9
+ }
10
+ const extensionsEnabled = options?.enableExtensions === true;
11
+ let readPosition = 0;
12
+ function parse(initialCharCode) {
13
+ ////////////////////////////////////////////////////////////////////////////////////////////
14
+ // Parse string
15
+ ////////////////////////////////////////////////////////////////////////////////////////////
16
+ if (initialCharCode === 34 || initialCharCode === 39) { // '"' or '\'' for string start
17
+ readPosition += 1;
18
+ // Try to quickly parse the string for the case where there are no escape patterns
19
+ {
20
+ const remainingString = jsonString.substring(readPosition);
21
+ let match;
22
+ if (initialCharCode === 34) {
23
+ match = remainingString.match(/^[^\\\r\n]*?"/);
24
+ }
25
+ else {
26
+ match = remainingString.match(/^[^\\\r\n]*?'/);
27
+ }
28
+ if (match !== null) {
29
+ const matchString = match[0];
30
+ const str = matchString.substring(0, matchString.length - 1);
31
+ readPosition += matchString.length;
32
+ return str;
33
+ }
34
+ }
35
+ // Fallback to slower method if an escape pattern is found, line breaks found, or string is unterminated.
36
+ let decodedString = '';
37
+ while (true) {
38
+ const charCode = readCharCodeAndAdvance();
39
+ if (charCode === initialCharCode) { // '"' or '\''
40
+ break;
41
+ }
42
+ else if (charCode === 92) { // '\\'
43
+ const escapeSequenceCharcode = readCharCodeAndAdvance();
44
+ if (escapeSequenceCharcode <= 92) {
45
+ if (escapeSequenceCharcode === 10) {
46
+ // Skip line continuation with \n character
47
+ }
48
+ else if (escapeSequenceCharcode === 13) {
49
+ // Skip line continuation with \r character or \r\n sequence
50
+ if (jsonString.charCodeAt(readPosition) === 10) {
51
+ readPosition += 1;
52
+ }
53
+ }
54
+ else if (escapeSequenceCharcode === 48) { // '0'
55
+ decodedString += '\0';
56
+ }
57
+ else if (escapeSequenceCharcode >= 49 && escapeSequenceCharcode <= 58) {
58
+ const positionInfo = getInfoForCurrentReadPosition();
59
+ throw new JsonParserError(`Found invalid escaped digit character at ${positionInfo.positionString}.`, positionInfo);
60
+ }
61
+ else {
62
+ decodedString += String.fromCharCode(escapeSequenceCharcode);
63
+ }
64
+ }
65
+ else if (escapeSequenceCharcode === 110) { // 'n'
66
+ decodedString += '\n';
67
+ }
68
+ else if (escapeSequenceCharcode === 114) { // 'r'
69
+ decodedString += '\r';
70
+ }
71
+ else if (escapeSequenceCharcode === 116) { // 't'
72
+ decodedString += '\t';
73
+ }
74
+ else if (escapeSequenceCharcode === 117) { // 'u' escape sequence
75
+ if (readPosition + 4 > jsonString.length) {
76
+ const positionInfo = getInfoForPosition(jsonString.length - 1);
77
+ throw new JsonParserError(`Expected 4 hexadecimal characters at ${positionInfo.positionString}. Reached end of input.`, positionInfo);
78
+ }
79
+ try {
80
+ const codePoint = hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 0)) << 12 |
81
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 1)) << 8 |
82
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 2)) << 4 |
83
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 3));
84
+ decodedString += String.fromCharCode(codePoint);
85
+ }
86
+ catch {
87
+ const positionInfo = getInfoForPosition(jsonString.length - 1);
88
+ throw new JsonParserError(`Invalid character in hexadecimal sequence at ${positionInfo.positionString}.`, positionInfo);
89
+ }
90
+ readPosition += 4;
91
+ }
92
+ else if (escapeSequenceCharcode === 120) { // 'x' escape sequence
93
+ if (readPosition + 2 > jsonString.length) {
94
+ const positionInfo = getInfoForCurrentReadPosition();
95
+ throw new JsonParserError(`Expected 2 hexadecimal characters at ${positionInfo.positionString}. Reached end of input.`, positionInfo);
96
+ }
97
+ try {
98
+ const codePoint = hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 0)) << 4 |
99
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 1));
100
+ decodedString += String.fromCharCode(codePoint);
101
+ }
102
+ catch {
103
+ const positionInfo = getInfoForCurrentReadPosition();
104
+ throw new JsonParserError(`Invalid character in hexadecimal sequence at ${positionInfo.positionString}.`, positionInfo);
105
+ }
106
+ readPosition += 2;
107
+ }
108
+ else if (escapeSequenceCharcode === 102) { // 'f'
109
+ decodedString += '\f';
110
+ }
111
+ else if (escapeSequenceCharcode === 118) { // 'v'
112
+ decodedString += '\v';
113
+ }
114
+ else if (escapeSequenceCharcode === 98) { // 'b'
115
+ decodedString += '\b';
116
+ }
117
+ else if (escapeSequenceCharcode === 0x2028 || escapeSequenceCharcode === 0x2029) {
118
+ // Skip line continuation with unicode line and paragraph separator characters
119
+ }
120
+ else if (escapeSequenceCharcode === undefined) {
121
+ throw new JsonParserError(`Unterminated string literal.`, getInfoForPosition(jsonString.length - 1));
122
+ }
123
+ else { // Anything else
124
+ decodedString += String.fromCharCode(escapeSequenceCharcode);
125
+ }
126
+ }
127
+ else if (charCode === undefined) {
128
+ throw new JsonParserError(`Unterminated string literal.`, getInfoForPosition(jsonString.length - 1));
129
+ }
130
+ else {
131
+ if (charCode === 10 || charCode === 13) {
132
+ const positionInfo = getInfoForPosition(readPosition - 1);
133
+ throw new JsonParserError(`Found invalid unescaped line break character in string literal at ${positionInfo.positionString}.`, positionInfo);
134
+ }
135
+ decodedString += String.fromCharCode(charCode);
136
+ }
137
+ }
138
+ return decodedString;
139
+ }
140
+ ////////////////////////////////////////////////////////////////////////////////////////////
141
+ // Parse numeric pattern
142
+ ////////////////////////////////////////////////////////////////////////////////////////////
143
+ if (initialCharCode < 91) { // '0'-'9', '-', '+', '.', 'I', 'N' for numeric pattern start
144
+ let numberStringStartPosition = readPosition;
145
+ let charCode = initialCharCode;
146
+ ////////////////////////////////////////////////////////////////////////////////////////////
147
+ // Parse sign
148
+ ////////////////////////////////////////////////////////////////////////////////////////////
149
+ let isNegative = false;
150
+ if (charCode === 45) { // '-'
151
+ isNegative = true;
152
+ charCode = advanceAndReadCharCode();
153
+ }
154
+ else if (charCode === 43) { // '+'
155
+ charCode = advanceAndReadCharCode();
156
+ }
157
+ ////////////////////////////////////////////////////////////////////////////////////////////
158
+ // Parse Infinity literal
159
+ ////////////////////////////////////////////////////////////////////////////////////////////
160
+ if (charCode === 73) { // 'I' for Infinity
161
+ if (jsonString.charCodeAt(readPosition + 1) !== 110 || // 'n'
162
+ jsonString.charCodeAt(readPosition + 2) !== 102 || // 'f'
163
+ jsonString.charCodeAt(readPosition + 3) !== 105 || // 'i'
164
+ jsonString.charCodeAt(readPosition + 4) !== 110 || // 'n'
165
+ jsonString.charCodeAt(readPosition + 5) !== 105 || // 'i'
166
+ jsonString.charCodeAt(readPosition + 6) !== 116 || // 't'
167
+ jsonString.charCodeAt(readPosition + 7) !== 121) { // 'y'
168
+ const positionInfo = getInfoForCurrentReadPosition();
169
+ throw new JsonParserError(`Expected 'Infinity' at ${positionInfo.positionString}.`, positionInfo);
170
+ }
171
+ readPosition += 8;
172
+ if (isNegative) {
173
+ return -Infinity;
174
+ }
175
+ else {
176
+ return Infinity;
177
+ }
178
+ }
179
+ ////////////////////////////////////////////////////////////////////////////////////////////
180
+ // Parse NaN literal
181
+ ////////////////////////////////////////////////////////////////////////////////////////////
182
+ if (charCode === 78) { // 'N' for NaN
183
+ if (jsonString.charCodeAt(readPosition + 1) !== 97 || // 'a'
184
+ jsonString.charCodeAt(readPosition + 2) !== 78) { // 'N'
185
+ const positionInfo = getInfoForCurrentReadPosition();
186
+ throw new JsonParserError(`Expected 'NaN' at ${positionInfo.positionString}.`, positionInfo);
187
+ }
188
+ readPosition += 3;
189
+ if (isNegative) {
190
+ return -NaN;
191
+ }
192
+ else {
193
+ return NaN;
194
+ }
195
+ }
196
+ ////////////////////////////////////////////////////////////////////////////////////////////
197
+ // Parse a potential string literal starting with '0'.
198
+ // Like '0x' (hexadecimal), '0o' (octal) or '0b' (binary)
199
+ ////////////////////////////////////////////////////////////////////////////////////////////
200
+ if (charCode === 48) { // '0'
201
+ const nextCharCode = jsonString.charCodeAt(readPosition + 1);
202
+ if (nextCharCode === 120 || nextCharCode === 88) { // 'x' or 'X' for hexadecimal number
203
+ readPosition += 1;
204
+ charCode = advanceAndReadCharCode();
205
+ const hexDigitsStartPosition = readPosition;
206
+ let parsedHexValue = 0;
207
+ let lastUnderscorePosition = -1;
208
+ while (true) {
209
+ let digitValue;
210
+ if (charCode >= 48 && charCode <= 57) { // '0'..'9'
211
+ digitValue = charCode - 48;
212
+ }
213
+ else if (charCode >= 65 && charCode <= 70) { // 'A'..'F'
214
+ digitValue = charCode - 65 + 10;
215
+ }
216
+ else if (charCode >= 97 && charCode <= 102) { // 'a'..'f'
217
+ digitValue = charCode - 97 + 10;
218
+ }
219
+ else if (charCode === 95) { // '_'
220
+ lastUnderscorePosition = readPosition;
221
+ // Skip underscore
222
+ charCode = advanceAndReadCharCode();
223
+ continue;
224
+ }
225
+ else {
226
+ break;
227
+ }
228
+ parsedHexValue = (parsedHexValue * 16) + digitValue;
229
+ charCode = advanceAndReadCharCode();
230
+ }
231
+ if (readPosition === hexDigitsStartPosition) {
232
+ const positionInfo = getInfoForCurrentReadPosition();
233
+ throw new JsonParserError(`Expected at least one hexadecimal digit at ${positionInfo.positionString}.`, positionInfo);
234
+ }
235
+ if (lastUnderscorePosition >= 0) {
236
+ if (extensionsEnabled === false) {
237
+ const positionInfo = getInfoForPosition(hexDigitsStartPosition);
238
+ throw new JsonParserError(`Hexadecimal literal at ${positionInfo.positionString} contains underscore separators, which are only supported when JSON5 extensions are enabled in options.`, positionInfo);
239
+ }
240
+ if (jsonString[hexDigitsStartPosition] === '_' || lastUnderscorePosition === readPosition - 1) {
241
+ const positionInfo = getInfoForPosition(hexDigitsStartPosition);
242
+ throw new JsonParserError(`Hexadecimal literal at ${positionInfo.positionString} contains an invalid preceding or trailing underscore.`, positionInfo);
243
+ }
244
+ }
245
+ if (isNegative) {
246
+ return -parsedHexValue;
247
+ }
248
+ else {
249
+ return parsedHexValue;
250
+ }
251
+ }
252
+ else if (nextCharCode === 111) { // 'o' for octal
253
+ readPosition += 1;
254
+ charCode = advanceAndReadCharCode();
255
+ const octalDigitsStartPosition = readPosition;
256
+ if (extensionsEnabled === false) {
257
+ const positionInfo = getInfoForPosition(octalDigitsStartPosition);
258
+ throw new JsonParserError(`Octal literal at ${positionInfo.positionString} can only be parsed when JSON5 extensions are enabled in options.`, positionInfo);
259
+ }
260
+ let parsedOctalValue = 0;
261
+ while (true) {
262
+ let digitValue;
263
+ if (charCode >= 48 && charCode <= 55) { // '0'..'7'
264
+ digitValue = charCode - 48;
265
+ }
266
+ else if (charCode === 95) { // '_'
267
+ // Skip underscore
268
+ charCode = advanceAndReadCharCode();
269
+ continue;
270
+ }
271
+ else {
272
+ break;
273
+ }
274
+ parsedOctalValue = (parsedOctalValue * 8) + digitValue;
275
+ charCode = advanceAndReadCharCode();
276
+ }
277
+ if (readPosition === octalDigitsStartPosition) {
278
+ const positionInfo = getInfoForCurrentReadPosition();
279
+ throw new JsonParserError(`Expected at least one octal digit at ${positionInfo.positionString}.`, positionInfo);
280
+ }
281
+ if (jsonString[octalDigitsStartPosition] === '_' || jsonString[readPosition - 1] === '_') {
282
+ const positionInfo = getInfoForPosition(octalDigitsStartPosition);
283
+ throw new JsonParserError(`Octal literal at ${positionInfo.positionString} contains an invalid preceding or trailing underscore.`, positionInfo);
284
+ }
285
+ if (isNegative) {
286
+ return -parsedOctalValue;
287
+ }
288
+ else {
289
+ return parsedOctalValue;
290
+ }
291
+ }
292
+ else if (nextCharCode === 98) { // 'b' for binary
293
+ readPosition += 1;
294
+ charCode = advanceAndReadCharCode();
295
+ const binaryDigitsStartPosition = readPosition;
296
+ if (extensionsEnabled === false) {
297
+ const positionInfo = getInfoForPosition(binaryDigitsStartPosition);
298
+ throw new JsonParserError(`Binary literal at ${positionInfo.positionString} can only be parsed when JSON5 extensions are enabled in options.`, positionInfo);
299
+ }
300
+ let parsedBinaryValue = 0;
301
+ while (true) {
302
+ let digitValue;
303
+ if (charCode === 48) { // '0'..'9'
304
+ digitValue = 0;
305
+ }
306
+ else if (charCode === 49) {
307
+ digitValue = 1;
308
+ }
309
+ else if (charCode === 95) { // '_'
310
+ // Skip underscore
311
+ charCode = advanceAndReadCharCode();
312
+ continue;
313
+ }
314
+ else {
315
+ break;
316
+ }
317
+ parsedBinaryValue = (parsedBinaryValue * 2) + digitValue;
318
+ charCode = advanceAndReadCharCode();
319
+ }
320
+ if (readPosition === binaryDigitsStartPosition) {
321
+ const positionInfo = getInfoForCurrentReadPosition();
322
+ throw new JsonParserError(`Expected at least one binary digit at ${positionInfo.positionString}.`, positionInfo);
323
+ }
324
+ if (jsonString[binaryDigitsStartPosition] === '_' || jsonString[readPosition - 1] === '_') {
325
+ const positionInfo = getInfoForPosition(binaryDigitsStartPosition);
326
+ throw new JsonParserError(`Binary literal at ${positionInfo.positionString} contains an invalid preceding or trailing underscore.`, positionInfo);
327
+ }
328
+ if (isNegative) {
329
+ return -parsedBinaryValue;
330
+ }
331
+ else {
332
+ return parsedBinaryValue;
333
+ }
334
+ }
335
+ }
336
+ ////////////////////////////////////////////////////////////////////////////////////////////
337
+ // Parse decimal number
338
+ ////////////////////////////////////////////////////////////////////////////////////////////
339
+ {
340
+ let concatenatedInteger = 0;
341
+ let integerPartDigitCount = 0;
342
+ let lastUnderscorePosition = -1;
343
+ // Parse integer part
344
+ {
345
+ const digitsStartPosition = readPosition;
346
+ while (true) { // '0'-'9'
347
+ if (charCode >= 48 && charCode <= 57) {
348
+ const digitValue = charCode - 48;
349
+ concatenatedInteger = (concatenatedInteger * 10) + digitValue;
350
+ charCode = advanceAndReadCharCode();
351
+ }
352
+ else if (charCode === 95) { // '_'
353
+ lastUnderscorePosition = readPosition;
354
+ // Skip underscore
355
+ charCode = advanceAndReadCharCode();
356
+ continue;
357
+ }
358
+ else {
359
+ break;
360
+ }
361
+ }
362
+ // Ensure valid integer part
363
+ integerPartDigitCount = readPosition - digitsStartPosition;
364
+ const firstDigit = jsonString[digitsStartPosition];
365
+ if (lastUnderscorePosition >= 0) {
366
+ if (extensionsEnabled === false) {
367
+ const positionInfo = getInfoForPosition(digitsStartPosition);
368
+ throw new JsonParserError(`Numeric literal at ${positionInfo.positionString} contains underscore separators, which are only supported when JSON5 extensions are enabled in options.`, positionInfo);
369
+ }
370
+ if (firstDigit === '_' || lastUnderscorePosition === readPosition - 1) {
371
+ const positionInfo = getInfoForPosition(digitsStartPosition);
372
+ throw new JsonParserError(`Numeric literal at ${positionInfo.positionString} contains an invalid preceding or trailing underscore.`, positionInfo);
373
+ }
374
+ }
375
+ if (integerPartDigitCount > 1 && firstDigit === '0') {
376
+ const positionInfo = getInfoForPosition(digitsStartPosition);
377
+ throw new JsonParserError(`Invalid leading zero found in numeric literal at ${positionInfo.positionString}.`, positionInfo);
378
+ }
379
+ }
380
+ let fractionalPartDigitCount = 0;
381
+ // Parse fractional part
382
+ if (charCode === 46) { // '.'
383
+ charCode = advanceAndReadCharCode();
384
+ const digitsStartPosition = readPosition;
385
+ while (true) { // '0'-'9'
386
+ if (charCode >= 48 && charCode <= 57) {
387
+ const digitValue = charCode - 48;
388
+ concatenatedInteger = (concatenatedInteger * 10) + digitValue;
389
+ charCode = advanceAndReadCharCode();
390
+ }
391
+ else if (charCode === 95) { // '_'
392
+ lastUnderscorePosition = readPosition;
393
+ // Skip underscore
394
+ charCode = advanceAndReadCharCode();
395
+ continue;
396
+ }
397
+ else {
398
+ break;
399
+ }
400
+ }
401
+ // Notice that trailing 0s, like 0.1000000000000000000000000, would cause
402
+ // the concatenated integer to grow, and later fall back to `Number()`.
403
+ // Is it worthy to try to trim them somehow, or consider this rare?
404
+ // Ensure valid fractional part
405
+ fractionalPartDigitCount = readPosition - digitsStartPosition;
406
+ if (integerPartDigitCount === 0 && fractionalPartDigitCount === 0) {
407
+ const positionInfo = getInfoForCurrentReadPosition();
408
+ throw new JsonParserError(`Expected at least one decimal digit at ${positionInfo.positionString}.`, positionInfo);
409
+ }
410
+ if (lastUnderscorePosition >= 0) {
411
+ if (extensionsEnabled === false) {
412
+ const positionInfo = getInfoForPosition(digitsStartPosition);
413
+ throw new JsonParserError(`Decimal digits at ${positionInfo.positionString} contain underscore separators, which are only supported when JSON5 extensions are enabled in options.`, positionInfo);
414
+ }
415
+ if (jsonString[digitsStartPosition] === '_' || lastUnderscorePosition === readPosition - 1) {
416
+ const positionInfo = getInfoForPosition(digitsStartPosition);
417
+ throw new JsonParserError(`Decimal digits in numeric literal at ${positionInfo.positionString} contains an invalid preceding or trailing underscore.`, positionInfo);
418
+ }
419
+ }
420
+ }
421
+ else if (charCode === 110) { // 'n' for BigInt
422
+ if (extensionsEnabled === false) {
423
+ const positionInfo = getInfoForPosition(numberStringStartPosition);
424
+ throw new JsonParserError(`BigInt literal at ${positionInfo.positionString} can only be parsed when JSON5 extensions are enabled in options.`, positionInfo);
425
+ }
426
+ // Get substring for BigInt literal
427
+ let bigIntSubstring = jsonString.substring(numberStringStartPosition, readPosition);
428
+ // Remove all underscore separators from substring
429
+ if (lastUnderscorePosition >= 0) {
430
+ bigIntSubstring = bigIntSubstring.replaceAll('_', '');
431
+ }
432
+ // Parse substring as BigInt
433
+ const bigintValue = BigInt(bigIntSubstring);
434
+ // Accept 'n' suffix character
435
+ readPosition += 1;
436
+ return bigintValue;
437
+ }
438
+ else if (integerPartDigitCount === 0) {
439
+ const positionInfo = getInfoForCurrentReadPosition();
440
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}.`, positionInfo);
441
+ }
442
+ let exponent = 0;
443
+ // Parse exponent part
444
+ if (charCode === 101 || charCode === 69) { // 'e' or 'E'
445
+ charCode = advanceAndReadCharCode();
446
+ let isNegativeExponent = false;
447
+ if (charCode === 43) { // '+'
448
+ charCode = advanceAndReadCharCode();
449
+ }
450
+ else if (charCode === 45) { // '-'
451
+ isNegativeExponent = true;
452
+ charCode = advanceAndReadCharCode();
453
+ }
454
+ let exponentDigitsStartPosition = readPosition;
455
+ while (true) {
456
+ if (charCode >= 48 && charCode <= 57) { // '0'-'9'
457
+ const digitValue = charCode - 48;
458
+ exponent = (exponent * 10) + digitValue;
459
+ charCode = advanceAndReadCharCode();
460
+ }
461
+ else if (charCode === 95) { // '_'
462
+ lastUnderscorePosition = readPosition;
463
+ // Skip underscore
464
+ charCode = advanceAndReadCharCode();
465
+ continue;
466
+ }
467
+ else {
468
+ break;
469
+ }
470
+ }
471
+ // Ensure valid exponent part
472
+ if (readPosition === exponentDigitsStartPosition) {
473
+ const positionInfo = getInfoForCurrentReadPosition();
474
+ throw new JsonParserError(`Exepcted at least one exponent digit at ${positionInfo.positionString}.`, positionInfo);
475
+ }
476
+ if (lastUnderscorePosition >= 0) {
477
+ if (extensionsEnabled === false) {
478
+ const positionInfo = getInfoForPosition(exponentDigitsStartPosition);
479
+ throw new JsonParserError(`Exponent digits at ${positionInfo.positionString} contain underscore separators, which are only supported when JSON5 extensions are enabled in options.`, positionInfo);
480
+ }
481
+ if (jsonString[exponentDigitsStartPosition] === '_' || lastUnderscorePosition === readPosition - 1) {
482
+ const positionInfo = getInfoForPosition(exponentDigitsStartPosition);
483
+ throw new JsonParserError(`Exponent digits in numeric literal at ${positionInfo.positionString} contains an invalid preceding or trailing underscore.`, positionInfo);
484
+ }
485
+ }
486
+ if (isNegativeExponent) {
487
+ exponent = -exponent;
488
+ }
489
+ }
490
+ const adjustedExponent = exponent - fractionalPartDigitCount;
491
+ // Use fast method for cases where the number can be parsed efficiently,
492
+ // or fall back to slower method if not possible.
493
+ //
494
+ // Reasoning:
495
+ // * Integers in the range 0 to 2^53 - 1 can be represented exactly in FP64
496
+ // * The powers of 10, up to 10^22 can be represented exactly in FP64, internally as
497
+ // `5^k * 2^k`, where 5^22 is still within FP64 mantissa (about 2^51.0824),
498
+ // but 5^23 is not (about 2^53.4043). `* 2^k` is a lossless bit shift and always exact
499
+ // * Therefore, `n * 10^k` or `n / 10^k` where n and k is in these ranges, would compute
500
+ // an approximation, directly via FPU operations, that would be as good
501
+ // as the more complex algorithms used in `Number(str)`.
502
+ // * This property was verified empirically by testing with a billion random inputs
503
+ if (concatenatedInteger < 2 ** 53 && adjustedExponent >= -22 && adjustedExponent <= 22) {
504
+ let parsedNumber = concatenatedInteger;
505
+ if (adjustedExponent > 0) {
506
+ parsedNumber *= positivePowersOf10[adjustedExponent];
507
+ }
508
+ else if (adjustedExponent < 0) {
509
+ parsedNumber /= positivePowersOf10[-adjustedExponent];
510
+ }
511
+ if (isNegative) {
512
+ return -parsedNumber;
513
+ }
514
+ else {
515
+ return parsedNumber;
516
+ }
517
+ }
518
+ else {
519
+ // Get substring for number literal
520
+ let numberString = jsonString.substring(numberStringStartPosition, readPosition);
521
+ // Remove all underscore separators from substring, if seen
522
+ if (lastUnderscorePosition >= 0) {
523
+ numberString = numberString.replaceAll('_', '');
524
+ }
525
+ // Convert to number
526
+ return Number(numberString);
527
+ }
528
+ }
529
+ }
530
+ ////////////////////////////////////////////////////////////////////////////////////////////
531
+ // Parse 'true' literal
532
+ ////////////////////////////////////////////////////////////////////////////////////////////
533
+ if (initialCharCode === 116) { // 't' for true
534
+ if (jsonString.charCodeAt(readPosition + 1) !== 114 || // 'r'
535
+ jsonString.charCodeAt(readPosition + 2) !== 117 || // 'u'
536
+ jsonString.charCodeAt(readPosition + 3) !== 101) { // 'e'
537
+ const positionInfo = getInfoForCurrentReadPosition();
538
+ throw new JsonParserError(`Expected 'true' at ${positionInfo.positionString}.`, positionInfo);
539
+ }
540
+ readPosition += 4;
541
+ return true;
542
+ }
543
+ ////////////////////////////////////////////////////////////////////////////////////////////
544
+ // Parse 'false' literal
545
+ ////////////////////////////////////////////////////////////////////////////////////////////
546
+ if (initialCharCode === 102) { // 'f' for false
547
+ if (jsonString.charCodeAt(readPosition + 1) !== 97 || // 'a'
548
+ jsonString.charCodeAt(readPosition + 2) !== 108 || // 'l'
549
+ jsonString.charCodeAt(readPosition + 3) !== 115 || // 's'
550
+ jsonString.charCodeAt(readPosition + 4) !== 101) { // 'e'
551
+ const positionInfo = getInfoForCurrentReadPosition();
552
+ throw new JsonParserError(`Expected 'false' at ${positionInfo.positionString}.`, positionInfo);
553
+ }
554
+ readPosition += 5;
555
+ return false;
556
+ }
557
+ ////////////////////////////////////////////////////////////////////////////////////////////
558
+ // Parse 'null' literal
559
+ ////////////////////////////////////////////////////////////////////////////////////////////
560
+ if (initialCharCode === 110) { // 'n' for null
561
+ if (jsonString.charCodeAt(readPosition + 1) !== 117 || // 'u'
562
+ jsonString.charCodeAt(readPosition + 2) !== 108 || // 'l'
563
+ jsonString.charCodeAt(readPosition + 3) !== 108) { // 'l'
564
+ const positionInfo = getInfoForCurrentReadPosition();
565
+ throw new JsonParserError(`Expected 'null' at ${positionInfo.positionString}.`, positionInfo);
566
+ }
567
+ readPosition += 4;
568
+ return null;
569
+ }
570
+ ////////////////////////////////////////////////////////////////////////////////////////////
571
+ // Parse array
572
+ ////////////////////////////////////////////////////////////////////////////////////////////
573
+ if (initialCharCode === 91) { // '[' for array start
574
+ readPosition += 1;
575
+ let charCode = skipToNextReadableCharCode();
576
+ if (charCode === 93) { // ']' for array end
577
+ readPosition += 1;
578
+ return [];
579
+ }
580
+ const arr = [];
581
+ while (true) {
582
+ const elementStartPosition = readPosition;
583
+ const element = parse(charCode);
584
+ if (reviver !== undefined) {
585
+ applyReviver('', element, elementStartPosition);
586
+ }
587
+ arr.push(element);
588
+ charCode = skipToNextReadableCharCode();
589
+ if (charCode === 44) { // ',' for comma
590
+ readPosition += 1;
591
+ charCode = skipToNextReadableCharCode();
592
+ // Handle possibility of trailing comma
593
+ if (charCode === 93) { // ']' for array end
594
+ readPosition += 1;
595
+ break;
596
+ }
597
+ continue;
598
+ }
599
+ if (charCode === 93) { // ']' for array end
600
+ readPosition += 1;
601
+ break;
602
+ }
603
+ {
604
+ const positionInfo = getInfoForCurrentReadPosition();
605
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' in array expression at ${positionInfo.positionString}. Expected ',' or ']'.`, positionInfo);
606
+ }
607
+ }
608
+ return arr;
609
+ }
610
+ ////////////////////////////////////////////////////////////////////////////////////////////
611
+ // Parse object
612
+ ////////////////////////////////////////////////////////////////////////////////////////////
613
+ if (initialCharCode === 123) { // '{' for object start
614
+ readPosition += 1;
615
+ let charCode = skipToNextReadableCharCode();
616
+ if (charCode === 125) { // '}' for object end
617
+ readPosition += 1;
618
+ return {};
619
+ }
620
+ const obj = {};
621
+ while (true) {
622
+ let key;
623
+ if (charCode === 34 || charCode === 39) { // '"' or '\'' for quoted key
624
+ key = parse(charCode);
625
+ }
626
+ else { // Unquoted key
627
+ const unquotedKeyRegExp = /^(?:[\p{ID_Start}\$_\u200C\u200D]|\\u[0-9a-fA-F]{4})(?:[\p{ID_Continue}\$_\u200C\u200D]|\\u[0-9a-fA-F]{4})*/u;
628
+ const remainingString = jsonString.substring(readPosition);
629
+ const unquotedKeyMatch = remainingString.match(unquotedKeyRegExp);
630
+ if (unquotedKeyMatch === null) {
631
+ const positionInfo = getInfoForCurrentReadPosition();
632
+ throw new JsonParserError(`Expected '"', '\'', or valid unquoted key identifier at ${positionInfo.positionString}.`, positionInfo);
633
+ }
634
+ let escapedKey = unquotedKeyMatch[0];
635
+ if (escapedKey.includes('\\')) {
636
+ key = escapedKey.replaceAll(/\\u[0-9a-fA-F]{4}/g, (substr) => {
637
+ const codePoint = hexCharCodeToNumber(substr.charCodeAt(2)) << 12 |
638
+ hexCharCodeToNumber(substr.charCodeAt(3)) << 8 |
639
+ hexCharCodeToNumber(substr.charCodeAt(4)) << 4 |
640
+ hexCharCodeToNumber(substr.charCodeAt(5));
641
+ return String.fromCharCode(codePoint);
642
+ });
643
+ }
644
+ else {
645
+ key = escapedKey;
646
+ }
647
+ readPosition += escapedKey.length;
648
+ }
649
+ charCode = skipToNextReadableCharCode();
650
+ if (charCode !== 58) { // ':'
651
+ const positionInfo = getInfoForCurrentReadPosition();
652
+ throw new JsonParserError(`Invalid character '${jsonString[readPosition]}' at ${positionInfo.positionString}. Expected ':'.`, positionInfo);
653
+ }
654
+ readPosition += 1;
655
+ charCode = skipToNextReadableCharCode();
656
+ const valueStartPosition = readPosition;
657
+ let value = parse(charCode);
658
+ if (reviver !== undefined) {
659
+ value = applyReviver(key, value, valueStartPosition, obj);
660
+ }
661
+ obj[key] = value;
662
+ charCode = skipToNextReadableCharCode();
663
+ if (charCode === 44) { // ','
664
+ readPosition += 1;
665
+ charCode = skipToNextReadableCharCode();
666
+ // Handle possibility of trailing comma
667
+ if (charCode === 125) { // '}' for object end
668
+ readPosition += 1;
669
+ break;
670
+ }
671
+ continue;
672
+ }
673
+ if (charCode === 125) { // '}' for object end
674
+ readPosition += 1;
675
+ break;
676
+ }
677
+ {
678
+ const positionInfo = getInfoForCurrentReadPosition();
679
+ throw new JsonParserError(`Invalid character '${jsonString[readPosition]}' in object expression at ${positionInfo.positionString}. Expected ',' or '}'.`, positionInfo);
680
+ }
681
+ }
682
+ return obj;
683
+ }
684
+ {
685
+ const positionInfo = getInfoForCurrentReadPosition();
686
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(initialCharCode)}' at ${positionInfo.positionString}.`, positionInfo);
687
+ }
688
+ }
689
+ function advanceAndReadCharCode() {
690
+ return jsonString.charCodeAt(++readPosition);
691
+ }
692
+ function readCharCodeAndAdvance() {
693
+ return jsonString.charCodeAt(readPosition++);
694
+ }
695
+ function skipWhitespaceAndComments() {
696
+ function skipCommentAndFollowingWhitespace() {
697
+ const commentAndFollowingWhitespace = jsonString.substring(readPosition).match(/^\/\/[^\r\n\u2028\u2029]*\s*|^\/\*[\s\S]*?\*\/\s*/);
698
+ if (commentAndFollowingWhitespace === null) {
699
+ const positionInfo = getInfoForCurrentReadPosition();
700
+ throw new JsonParserError(`Failed to match a valid comment at ${positionInfo.positionString}.`, positionInfo);
701
+ }
702
+ readPosition += commentAndFollowingWhitespace[0].length;
703
+ }
704
+ function isOtherWhitespace(charCode) {
705
+ if (charCode >= 0x2000 && charCode <= 0x205F) {
706
+ if (charCode <= 0x200A || charCode === 0x2028 || charCode === 0x2029 || charCode === 0x202F || charCode === 0x205F) {
707
+ return true;
708
+ }
709
+ }
710
+ else if (charCode === 0x1680 || charCode === 0x3000 || charCode === 0xFEFF) {
711
+ return true;
712
+ }
713
+ return false;
714
+ }
715
+ while (readPosition < jsonString.length) {
716
+ const charCode = jsonString.charCodeAt(readPosition);
717
+ if (charCode > 0x000D && charCode < 0x1680) {
718
+ if (charCode === 0x0020 || charCode === 0x00A0) {
719
+ readPosition += 1;
720
+ continue;
721
+ }
722
+ if (charCode === 47) { // '/' for comment start
723
+ skipCommentAndFollowingWhitespace();
724
+ continue;
725
+ }
726
+ }
727
+ else if ((charCode >= 0x0009 && charCode <= 0x000D) || isOtherWhitespace(charCode)) {
728
+ readPosition += 1;
729
+ continue;
730
+ }
731
+ return charCode;
732
+ }
733
+ return undefined;
734
+ }
735
+ function skipToNextReadableCharCode() {
736
+ const nextCharCode = skipWhitespaceAndComments();
737
+ if (nextCharCode === undefined) {
738
+ throw new JsonParserError(`Unexpected termination of JSON5 input.`, getInfoForPosition(jsonString.length - 1));
739
+ }
740
+ return nextCharCode;
741
+ }
742
+ function getInfoForCurrentReadPosition() {
743
+ return getInfoForPosition(readPosition);
744
+ }
745
+ function getInfoForPosition(position) {
746
+ return getPositionInfo(jsonString, position);
747
+ }
748
+ function applyReviver(key, value, valueStartPosition, thisArg) {
749
+ if (reviver === undefined) {
750
+ return;
751
+ }
752
+ let context;
753
+ if (typeof value !== 'object' || value === null) {
754
+ context = jsonString.substring(valueStartPosition, readPosition);
755
+ }
756
+ let result;
757
+ if (thisArg) {
758
+ result = reviver.call(thisArg, key, value, context);
759
+ }
760
+ else {
761
+ result = reviver(key, value, context);
762
+ }
763
+ return result;
764
+ }
765
+ // Parse the given string
766
+ {
767
+ const initialCharCode = skipToNextReadableCharCode();
768
+ const documentStartPosition = readPosition;
769
+ let result = parse(initialCharCode);
770
+ if (reviver !== undefined) {
771
+ result = applyReviver('', result, documentStartPosition);
772
+ }
773
+ const finalSkipResult = skipWhitespaceAndComments();
774
+ if (finalSkipResult !== undefined) {
775
+ const positionInfo = getInfoForCurrentReadPosition();
776
+ throw new JsonParserError(`Unexpected trailing character(s) starting at ${positionInfo.positionString}.`, positionInfo);
777
+ }
778
+ return result;
779
+ }
780
+ }
781
+ //# sourceMappingURL=Json5Parser.js.map