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.
- package/LICENSE.md +7 -0
- package/README.md +66 -0
- package/dist/exports/Exports.d.ts +6 -0
- package/dist/exports/Exports.d.ts.map +1 -0
- package/dist/exports/Exports.js +6 -0
- package/dist/exports/Exports.js.map +1 -0
- package/dist/json/JsonEncoder.d.ts +3 -0
- package/dist/json/JsonEncoder.d.ts.map +1 -0
- package/dist/json/JsonEncoder.js +147 -0
- package/dist/json/JsonEncoder.js.map +1 -0
- package/dist/json/JsonParser.d.ts +3 -0
- package/dist/json/JsonParser.d.ts.map +1 -0
- package/dist/json/JsonParser.js +378 -0
- package/dist/json/JsonParser.js.map +1 -0
- package/dist/json5/Json5Encoder.d.ts +3 -0
- package/dist/json5/Json5Encoder.d.ts.map +1 -0
- package/dist/json5/Json5Encoder.js +155 -0
- package/dist/json5/Json5Encoder.js.map +1 -0
- package/dist/json5/Json5Parser.d.ts +3 -0
- package/dist/json5/Json5Parser.d.ts.map +1 -0
- package/dist/json5/Json5Parser.js +781 -0
- package/dist/json5/Json5Parser.js.map +1 -0
- package/dist/tests/Json5EncoderTest.d.ts +2 -0
- package/dist/tests/Json5EncoderTest.d.ts.map +1 -0
- package/dist/tests/Json5EncoderTest.js +44 -0
- package/dist/tests/Json5EncoderTest.js.map +1 -0
- package/dist/tests/Json5ParserTests.d.ts +3 -0
- package/dist/tests/Json5ParserTests.d.ts.map +1 -0
- package/dist/tests/Json5ParserTests.js +211 -0
- package/dist/tests/Json5ParserTests.js.map +1 -0
- package/dist/tests/JsonEncoderTests.d.ts +2 -0
- package/dist/tests/JsonEncoderTests.d.ts.map +1 -0
- package/dist/tests/JsonEncoderTests.js +44 -0
- package/dist/tests/JsonEncoderTests.js.map +1 -0
- package/dist/tests/JsonParserTests.d.ts +3 -0
- package/dist/tests/JsonParserTests.d.ts.map +1 -0
- package/dist/tests/JsonParserTests.js +140 -0
- package/dist/tests/JsonParserTests.js.map +1 -0
- package/dist/tests/Test.d.ts +2 -0
- package/dist/tests/Test.d.ts.map +1 -0
- package/dist/tests/Test.js +9 -0
- package/dist/tests/Test.js.map +1 -0
- package/dist/types/Types.d.ts +8 -0
- package/dist/types/Types.d.ts.map +1 -0
- package/dist/types/Types.js +2 -0
- package/dist/types/Types.js.map +1 -0
- package/dist/unused/Unused.d.ts +2 -0
- package/dist/unused/Unused.d.ts.map +1 -0
- package/dist/unused/Unused.js +47 -0
- package/dist/unused/Unused.js.map +1 -0
- package/dist/utilities/JsonParseError.d.ts +7 -0
- package/dist/utilities/JsonParseError.d.ts.map +1 -0
- package/dist/utilities/JsonParseError.js +10 -0
- package/dist/utilities/JsonParseError.js.map +1 -0
- package/dist/utilities/ObjectUtilities.d.ts +2 -0
- package/dist/utilities/ObjectUtilities.d.ts.map +1 -0
- package/dist/utilities/ObjectUtilities.js +142 -0
- package/dist/utilities/ObjectUtilities.js.map +1 -0
- package/dist/utilities/TypedArray.d.ts +4 -0
- package/dist/utilities/TypedArray.d.ts.map +1 -0
- package/dist/utilities/TypedArray.js +2 -0
- package/dist/utilities/TypedArray.js.map +1 -0
- package/dist/utilities/Utilities.d.ts +17 -0
- package/dist/utilities/Utilities.d.ts.map +1 -0
- package/dist/utilities/Utilities.js +97 -0
- package/dist/utilities/Utilities.js.map +1 -0
- package/package.json +38 -0
- package/src/exports/Exports.ts +7 -0
- package/src/json/JsonEncoder.ts +180 -0
- package/src/json/JsonParser.ts +491 -0
- package/src/json5/Json5Encoder.ts +189 -0
- package/src/json5/Json5Parser.ts +959 -0
- package/src/tests/Json5EncoderTest.ts +51 -0
- package/src/tests/Json5ParserTests.ts +237 -0
- package/src/tests/JsonEncoderTests.ts +51 -0
- package/src/tests/JsonParserTests.ts +159 -0
- package/src/tests/Test.ts +13 -0
- package/src/types/Types.ts +9 -0
- package/src/unused/Unused.ts +51 -0
- package/src/utilities/JsonParseError.ts +7 -0
- package/src/utilities/ObjectUtilities.ts +184 -0
- package/src/utilities/TypedArray.ts +39 -0
- package/src/utilities/Utilities.ts +130 -0
- 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
|
+
}
|