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