2023-10-03 11:14:36 +08:00
|
|
|
const util = require('./util')
|
|
|
|
|
|
|
|
let source
|
|
|
|
let parseState
|
|
|
|
let stack
|
|
|
|
let pos
|
|
|
|
let line
|
|
|
|
let column
|
|
|
|
let token
|
|
|
|
let key
|
|
|
|
let root
|
|
|
|
|
|
|
|
module.exports = function parse (text, reviver) {
|
|
|
|
source = String(text)
|
|
|
|
parseState = 'start'
|
|
|
|
stack = []
|
|
|
|
pos = 0
|
|
|
|
line = 1
|
|
|
|
column = 0
|
|
|
|
token = undefined
|
|
|
|
key = undefined
|
|
|
|
root = undefined
|
|
|
|
|
|
|
|
do {
|
|
|
|
token = lex()
|
|
|
|
|
|
|
|
// This code is unreachable.
|
|
|
|
// if (!parseStates[parseState]) {
|
|
|
|
// throw invalidParseState()
|
|
|
|
// }
|
|
|
|
|
|
|
|
parseStates[parseState]()
|
|
|
|
} while (token.type !== 'eof')
|
|
|
|
|
|
|
|
if (typeof reviver === 'function') {
|
|
|
|
return internalize({'': root}, '', reviver)
|
|
|
|
}
|
|
|
|
|
|
|
|
return root
|
|
|
|
}
|
|
|
|
|
|
|
|
function internalize (holder, name, reviver) {
|
|
|
|
const value = holder[name]
|
|
|
|
if (value != null && typeof value === 'object') {
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
for (let i = 0; i < value.length; i++) {
|
|
|
|
const key = String(i)
|
|
|
|
const replacement = internalize(value, key, reviver)
|
|
|
|
if (replacement === undefined) {
|
|
|
|
delete value[key]
|
|
|
|
} else {
|
|
|
|
Object.defineProperty(value, key, {
|
|
|
|
value: replacement,
|
|
|
|
writable: true,
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (const key in value) {
|
|
|
|
const replacement = internalize(value, key, reviver)
|
|
|
|
if (replacement === undefined) {
|
|
|
|
delete value[key]
|
|
|
|
} else {
|
|
|
|
Object.defineProperty(value, key, {
|
|
|
|
value: replacement,
|
|
|
|
writable: true,
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reviver.call(holder, name, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
let lexState
|
|
|
|
let buffer
|
|
|
|
let doubleQuote
|
|
|
|
let sign
|
|
|
|
let c
|
|
|
|
|
|
|
|
function lex () {
|
|
|
|
lexState = 'default'
|
|
|
|
buffer = ''
|
|
|
|
doubleQuote = false
|
|
|
|
sign = 1
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
c = peek()
|
|
|
|
|
|
|
|
// This code is unreachable.
|
|
|
|
// if (!lexStates[lexState]) {
|
|
|
|
// throw invalidLexState(lexState)
|
|
|
|
// }
|
|
|
|
|
|
|
|
const token = lexStates[lexState]()
|
|
|
|
if (token) {
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function peek () {
|
|
|
|
if (source[pos]) {
|
|
|
|
return String.fromCodePoint(source.codePointAt(pos))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function read () {
|
|
|
|
const c = peek()
|
|
|
|
|
|
|
|
if (c === '\n') {
|
|
|
|
line++
|
|
|
|
column = 0
|
|
|
|
} else if (c) {
|
|
|
|
column += c.length
|
|
|
|
} else {
|
|
|
|
column++
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c) {
|
|
|
|
pos += c.length
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
const lexStates = {
|
|
|
|
default () {
|
|
|
|
switch (c) {
|
|
|
|
case '\t':
|
|
|
|
case '\v':
|
|
|
|
case '\f':
|
|
|
|
case ' ':
|
|
|
|
case '\u00A0':
|
|
|
|
case '\uFEFF':
|
|
|
|
case '\n':
|
|
|
|
case '\r':
|
|
|
|
case '\u2028':
|
|
|
|
case '\u2029':
|
|
|
|
read()
|
|
|
|
return
|
|
|
|
|
|
|
|
case '/':
|
|
|
|
read()
|
|
|
|
lexState = 'comment'
|
|
|
|
return
|
|
|
|
|
|
|
|
case undefined:
|
|
|
|
read()
|
|
|
|
return newToken('eof')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (util.isSpaceSeparator(c)) {
|
|
|
|
read()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// This code is unreachable.
|
|
|
|
// if (!lexStates[parseState]) {
|
|
|
|
// throw invalidLexState(parseState)
|
|
|
|
// }
|
|
|
|
|
|
|
|
return lexStates[parseState]()
|
|
|
|
},
|
|
|
|
|
|
|
|
comment () {
|
|
|
|
switch (c) {
|
|
|
|
case '*':
|
|
|
|
read()
|
|
|
|
lexState = 'multiLineComment'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '/':
|
|
|
|
read()
|
|
|
|
lexState = 'singleLineComment'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
multiLineComment () {
|
|
|
|
switch (c) {
|
|
|
|
case '*':
|
|
|
|
read()
|
|
|
|
lexState = 'multiLineCommentAsterisk'
|
|
|
|
return
|
|
|
|
|
|
|
|
case undefined:
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
read()
|
|
|
|
},
|
|
|
|
|
|
|
|
multiLineCommentAsterisk () {
|
|
|
|
switch (c) {
|
|
|
|
case '*':
|
|
|
|
read()
|
|
|
|
return
|
|
|
|
|
|
|
|
case '/':
|
|
|
|
read()
|
|
|
|
lexState = 'default'
|
|
|
|
return
|
|
|
|
|
|
|
|
case undefined:
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
read()
|
|
|
|
lexState = 'multiLineComment'
|
|
|
|
},
|
|
|
|
|
|
|
|
singleLineComment () {
|
|
|
|
switch (c) {
|
|
|
|
case '\n':
|
|
|
|
case '\r':
|
|
|
|
case '\u2028':
|
|
|
|
case '\u2029':
|
|
|
|
read()
|
|
|
|
lexState = 'default'
|
|
|
|
return
|
|
|
|
|
|
|
|
case undefined:
|
|
|
|
read()
|
|
|
|
return newToken('eof')
|
|
|
|
}
|
|
|
|
|
|
|
|
read()
|
|
|
|
},
|
|
|
|
|
|
|
|
value () {
|
|
|
|
switch (c) {
|
|
|
|
case '{':
|
|
|
|
case '[':
|
|
|
|
return newToken('punctuator', read())
|
|
|
|
|
|
|
|
case 'n':
|
|
|
|
read()
|
|
|
|
literal('ull')
|
|
|
|
return newToken('null', null)
|
|
|
|
|
|
|
|
case 't':
|
|
|
|
read()
|
|
|
|
literal('rue')
|
|
|
|
return newToken('boolean', true)
|
|
|
|
|
|
|
|
case 'f':
|
|
|
|
read()
|
|
|
|
literal('alse')
|
|
|
|
return newToken('boolean', false)
|
|
|
|
|
|
|
|
case '-':
|
|
|
|
case '+':
|
|
|
|
if (read() === '-') {
|
|
|
|
sign = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
lexState = 'sign'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '.':
|
|
|
|
buffer = read()
|
|
|
|
lexState = 'decimalPointLeading'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '0':
|
|
|
|
buffer = read()
|
|
|
|
lexState = 'zero'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
buffer = read()
|
|
|
|
lexState = 'decimalInteger'
|
|
|
|
return
|
|
|
|
|
|
|
|
case 'I':
|
|
|
|
read()
|
|
|
|
literal('nfinity')
|
|
|
|
return newToken('numeric', Infinity)
|
|
|
|
|
|
|
|
case 'N':
|
|
|
|
read()
|
|
|
|
literal('aN')
|
|
|
|
return newToken('numeric', NaN)
|
|
|
|
|
|
|
|
case '"':
|
|
|
|
case "'":
|
|
|
|
doubleQuote = (read() === '"')
|
|
|
|
buffer = ''
|
|
|
|
lexState = 'string'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
identifierNameStartEscape () {
|
|
|
|
if (c !== 'u') {
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
read()
|
|
|
|
const u = unicodeEscape()
|
|
|
|
switch (u) {
|
|
|
|
case '$':
|
|
|
|
case '_':
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
if (!util.isIdStartChar(u)) {
|
|
|
|
throw invalidIdentifier()
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += u
|
|
|
|
lexState = 'identifierName'
|
|
|
|
},
|
|
|
|
|
|
|
|
identifierName () {
|
|
|
|
switch (c) {
|
|
|
|
case '$':
|
|
|
|
case '_':
|
|
|
|
case '\u200C':
|
|
|
|
case '\u200D':
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
|
|
|
|
case '\\':
|
|
|
|
read()
|
|
|
|
lexState = 'identifierNameEscape'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (util.isIdContinueChar(c)) {
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return newToken('identifier', buffer)
|
|
|
|
},
|
|
|
|
|
|
|
|
identifierNameEscape () {
|
|
|
|
if (c !== 'u') {
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
read()
|
|
|
|
const u = unicodeEscape()
|
|
|
|
switch (u) {
|
|
|
|
case '$':
|
|
|
|
case '_':
|
|
|
|
case '\u200C':
|
|
|
|
case '\u200D':
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
if (!util.isIdContinueChar(u)) {
|
|
|
|
throw invalidIdentifier()
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += u
|
|
|
|
lexState = 'identifierName'
|
|
|
|
},
|
|
|
|
|
|
|
|
sign () {
|
|
|
|
switch (c) {
|
|
|
|
case '.':
|
|
|
|
buffer = read()
|
|
|
|
lexState = 'decimalPointLeading'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '0':
|
|
|
|
buffer = read()
|
|
|
|
lexState = 'zero'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
buffer = read()
|
|
|
|
lexState = 'decimalInteger'
|
|
|
|
return
|
|
|
|
|
|
|
|
case 'I':
|
|
|
|
read()
|
|
|
|
literal('nfinity')
|
|
|
|
return newToken('numeric', sign * Infinity)
|
|
|
|
|
|
|
|
case 'N':
|
|
|
|
read()
|
|
|
|
literal('aN')
|
|
|
|
return newToken('numeric', NaN)
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
zero () {
|
|
|
|
switch (c) {
|
|
|
|
case '.':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalPoint'
|
|
|
|
return
|
|
|
|
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalExponent'
|
|
|
|
return
|
|
|
|
|
|
|
|
case 'x':
|
|
|
|
case 'X':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'hexadecimal'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return newToken('numeric', sign * 0)
|
|
|
|
},
|
|
|
|
|
|
|
|
decimalInteger () {
|
|
|
|
switch (c) {
|
|
|
|
case '.':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalPoint'
|
|
|
|
return
|
|
|
|
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalExponent'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (util.isDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return newToken('numeric', sign * Number(buffer))
|
|
|
|
},
|
|
|
|
|
|
|
|
decimalPointLeading () {
|
|
|
|
if (util.isDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalFraction'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
decimalPoint () {
|
|
|
|
switch (c) {
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalExponent'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (util.isDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalFraction'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return newToken('numeric', sign * Number(buffer))
|
|
|
|
},
|
|
|
|
|
|
|
|
decimalFraction () {
|
|
|
|
switch (c) {
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalExponent'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (util.isDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return newToken('numeric', sign * Number(buffer))
|
|
|
|
},
|
|
|
|
|
|
|
|
decimalExponent () {
|
|
|
|
switch (c) {
|
|
|
|
case '+':
|
|
|
|
case '-':
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalExponentSign'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (util.isDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalExponentInteger'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
decimalExponentSign () {
|
|
|
|
if (util.isDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'decimalExponentInteger'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
decimalExponentInteger () {
|
|
|
|
if (util.isDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return newToken('numeric', sign * Number(buffer))
|
|
|
|
},
|
|
|
|
|
|
|
|
hexadecimal () {
|
|
|
|
if (util.isHexDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'hexadecimalInteger'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
hexadecimalInteger () {
|
|
|
|
if (util.isHexDigit(c)) {
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return newToken('numeric', sign * Number(buffer))
|
|
|
|
},
|
|
|
|
|
|
|
|
string () {
|
|
|
|
switch (c) {
|
|
|
|
case '\\':
|
|
|
|
read()
|
|
|
|
buffer += escape()
|
|
|
|
return
|
|
|
|
|
|
|
|
case '"':
|
|
|
|
if (doubleQuote) {
|
|
|
|
read()
|
|
|
|
return newToken('string', buffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
|
|
|
|
case "'":
|
|
|
|
if (!doubleQuote) {
|
|
|
|
read()
|
|
|
|
return newToken('string', buffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += read()
|
|
|
|
return
|
|
|
|
|
|
|
|
case '\n':
|
|
|
|
case '\r':
|
|
|
|
throw invalidChar(read())
|
|
|
|
|
|
|
|
case '\u2028':
|
|
|
|
case '\u2029':
|
|
|
|
separatorChar(c)
|
|
|
|
break
|
|
|
|
|
|
|
|
case undefined:
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += read()
|
|
|
|
},
|
|
|
|
|
|
|
|
start () {
|
|
|
|
switch (c) {
|
|
|
|
case '{':
|
|
|
|
case '[':
|
|
|
|
return newToken('punctuator', read())
|
|
|
|
|
|
|
|
// This code is unreachable since the default lexState handles eof.
|
|
|
|
// case undefined:
|
|
|
|
// return newToken('eof')
|
|
|
|
}
|
|
|
|
|
|
|
|
lexState = 'value'
|
|
|
|
},
|
|
|
|
|
|
|
|
beforePropertyName () {
|
|
|
|
switch (c) {
|
|
|
|
case '$':
|
|
|
|
case '_':
|
|
|
|
buffer = read()
|
|
|
|
lexState = 'identifierName'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '\\':
|
|
|
|
read()
|
|
|
|
lexState = 'identifierNameStartEscape'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '}':
|
|
|
|
return newToken('punctuator', read())
|
|
|
|
|
|
|
|
case '"':
|
|
|
|
case "'":
|
|
|
|
doubleQuote = (read() === '"')
|
|
|
|
lexState = 'string'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (util.isIdStartChar(c)) {
|
|
|
|
buffer += read()
|
|
|
|
lexState = 'identifierName'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
afterPropertyName () {
|
|
|
|
if (c === ':') {
|
|
|
|
return newToken('punctuator', read())
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
beforePropertyValue () {
|
|
|
|
lexState = 'value'
|
|
|
|
},
|
|
|
|
|
|
|
|
afterPropertyValue () {
|
|
|
|
switch (c) {
|
|
|
|
case ',':
|
|
|
|
case '}':
|
|
|
|
return newToken('punctuator', read())
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
beforeArrayValue () {
|
|
|
|
if (c === ']') {
|
|
|
|
return newToken('punctuator', read())
|
|
|
|
}
|
|
|
|
|
|
|
|
lexState = 'value'
|
|
|
|
},
|
|
|
|
|
|
|
|
afterArrayValue () {
|
|
|
|
switch (c) {
|
|
|
|
case ',':
|
|
|
|
case ']':
|
|
|
|
return newToken('punctuator', read())
|
|
|
|
}
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
|
|
|
|
end () {
|
|
|
|
// This code is unreachable since it's handled by the default lexState.
|
|
|
|
// if (c === undefined) {
|
|
|
|
// read()
|
|
|
|
// return newToken('eof')
|
|
|
|
// }
|
|
|
|
|
|
|
|
throw invalidChar(read())
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
function newToken (type, value) {
|
|
|
|
return {
|
|
|
|
type,
|
|
|
|
value,
|
|
|
|
line,
|
|
|
|
column,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function literal (s) {
|
|
|
|
for (const c of s) {
|
|
|
|
const p = peek()
|
|
|
|
|
|
|
|
if (p !== c) {
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
read()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function escape () {
|
|
|
|
const c = peek()
|
|
|
|
switch (c) {
|
|
|
|
case 'b':
|
|
|
|
read()
|
|
|
|
return '\b'
|
|
|
|
|
|
|
|
case 'f':
|
|
|
|
read()
|
|
|
|
return '\f'
|
|
|
|
|
|
|
|
case 'n':
|
|
|
|
read()
|
|
|
|
return '\n'
|
|
|
|
|
|
|
|
case 'r':
|
|
|
|
read()
|
|
|
|
return '\r'
|
|
|
|
|
|
|
|
case 't':
|
|
|
|
read()
|
|
|
|
return '\t'
|
|
|
|
|
|
|
|
case 'v':
|
|
|
|
read()
|
|
|
|
return '\v'
|
|
|
|
|
|
|
|
case '0':
|
|
|
|
read()
|
|
|
|
if (util.isDigit(peek())) {
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
return '\0'
|
|
|
|
|
|
|
|
case 'x':
|
|
|
|
read()
|
|
|
|
return hexEscape()
|
|
|
|
|
|
|
|
case 'u':
|
|
|
|
read()
|
|
|
|
return unicodeEscape()
|
|
|
|
|
|
|
|
case '\n':
|
|
|
|
case '\u2028':
|
|
|
|
case '\u2029':
|
|
|
|
read()
|
|
|
|
return ''
|
|
|
|
|
|
|
|
case '\r':
|
|
|
|
read()
|
|
|
|
if (peek() === '\n') {
|
|
|
|
read()
|
|
|
|
}
|
|
|
|
|
|
|
|
return ''
|
|
|
|
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
throw invalidChar(read())
|
|
|
|
|
|
|
|
case undefined:
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
return read()
|
|
|
|
}
|
|
|
|
|
|
|
|
function hexEscape () {
|
|
|
|
let buffer = ''
|
|
|
|
let c = peek()
|
|
|
|
|
|
|
|
if (!util.isHexDigit(c)) {
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += read()
|
|
|
|
|
|
|
|
c = peek()
|
|
|
|
if (!util.isHexDigit(c)) {
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += read()
|
|
|
|
|
|
|
|
return String.fromCodePoint(parseInt(buffer, 16))
|
|
|
|
}
|
|
|
|
|
|
|
|
function unicodeEscape () {
|
|
|
|
let buffer = ''
|
|
|
|
let count = 4
|
|
|
|
|
|
|
|
while (count-- > 0) {
|
|
|
|
const c = peek()
|
|
|
|
if (!util.isHexDigit(c)) {
|
|
|
|
throw invalidChar(read())
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer += read()
|
|
|
|
}
|
|
|
|
|
|
|
|
return String.fromCodePoint(parseInt(buffer, 16))
|
|
|
|
}
|
|
|
|
|
|
|
|
const parseStates = {
|
|
|
|
start () {
|
|
|
|
if (token.type === 'eof') {
|
|
|
|
throw invalidEOF()
|
|
|
|
}
|
|
|
|
|
|
|
|
push()
|
|
|
|
},
|
|
|
|
|
|
|
|
beforePropertyName () {
|
|
|
|
switch (token.type) {
|
|
|
|
case 'identifier':
|
|
|
|
case 'string':
|
|
|
|
key = token.value
|
|
|
|
parseState = 'afterPropertyName'
|
|
|
|
return
|
|
|
|
|
|
|
|
case 'punctuator':
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// if (token.value !== '}') {
|
|
|
|
// throw invalidToken()
|
|
|
|
// }
|
|
|
|
|
|
|
|
pop()
|
|
|
|
return
|
|
|
|
|
|
|
|
case 'eof':
|
|
|
|
throw invalidEOF()
|
|
|
|
}
|
|
|
|
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// throw invalidToken()
|
|
|
|
},
|
|
|
|
|
|
|
|
afterPropertyName () {
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// if (token.type !== 'punctuator' || token.value !== ':') {
|
|
|
|
// throw invalidToken()
|
|
|
|
// }
|
|
|
|
|
|
|
|
if (token.type === 'eof') {
|
|
|
|
throw invalidEOF()
|
|
|
|
}
|
|
|
|
|
|
|
|
parseState = 'beforePropertyValue'
|
|
|
|
},
|
|
|
|
|
|
|
|
beforePropertyValue () {
|
|
|
|
if (token.type === 'eof') {
|
|
|
|
throw invalidEOF()
|
|
|
|
}
|
|
|
|
|
|
|
|
push()
|
|
|
|
},
|
|
|
|
|
|
|
|
beforeArrayValue () {
|
|
|
|
if (token.type === 'eof') {
|
|
|
|
throw invalidEOF()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (token.type === 'punctuator' && token.value === ']') {
|
|
|
|
pop()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
push()
|
|
|
|
},
|
|
|
|
|
|
|
|
afterPropertyValue () {
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// if (token.type !== 'punctuator') {
|
|
|
|
// throw invalidToken()
|
|
|
|
// }
|
|
|
|
|
|
|
|
if (token.type === 'eof') {
|
|
|
|
throw invalidEOF()
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (token.value) {
|
|
|
|
case ',':
|
|
|
|
parseState = 'beforePropertyName'
|
|
|
|
return
|
|
|
|
|
|
|
|
case '}':
|
|
|
|
pop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// throw invalidToken()
|
|
|
|
},
|
|
|
|
|
|
|
|
afterArrayValue () {
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// if (token.type !== 'punctuator') {
|
|
|
|
// throw invalidToken()
|
|
|
|
// }
|
|
|
|
|
|
|
|
if (token.type === 'eof') {
|
|
|
|
throw invalidEOF()
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (token.value) {
|
|
|
|
case ',':
|
|
|
|
parseState = 'beforeArrayValue'
|
|
|
|
return
|
|
|
|
|
|
|
|
case ']':
|
|
|
|
pop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// throw invalidToken()
|
|
|
|
},
|
|
|
|
|
|
|
|
end () {
|
|
|
|
// This code is unreachable since it's handled by the lexState.
|
|
|
|
// if (token.type !== 'eof') {
|
|
|
|
// throw invalidToken()
|
|
|
|
// }
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
function push () {
|
|
|
|
let value
|
|
|
|
|
|
|
|
switch (token.type) {
|
|
|
|
case 'punctuator':
|
|
|
|
switch (token.value) {
|
|
|
|
case '{':
|
|
|
|
value = {}
|
|
|
|
break
|
|
|
|
|
|
|
|
case '[':
|
|
|
|
value = []
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
case 'null':
|
|
|
|
case 'boolean':
|
|
|
|
case 'numeric':
|
|
|
|
case 'string':
|
|
|
|
value = token.value
|
|
|
|
break
|
|
|
|
|
|
|
|
// This code is unreachable.
|
|
|
|
// default:
|
|
|
|
// throw invalidToken()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (root === undefined) {
|
|
|
|
root = value
|
|
|
|
} else {
|
|
|
|
const parent = stack[stack.length - 1]
|
|
|
|
if (Array.isArray(parent)) {
|
|
|
|
parent.push(value)
|
|
|
|
} else {
|
|
|
|
Object.defineProperty(parent, key, {
|
|
|
|
value,
|
|
|
|
writable: true,
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value !== null && typeof value === 'object') {
|
|
|
|
stack.push(value)
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
parseState = 'beforeArrayValue'
|
|
|
|
} else {
|
|
|
|
parseState = 'beforePropertyName'
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const current = stack[stack.length - 1]
|
|
|
|
if (current == null) {
|
|
|
|
parseState = 'end'
|
|
|
|
} else if (Array.isArray(current)) {
|
|
|
|
parseState = 'afterArrayValue'
|
|
|
|
} else {
|
|
|
|
parseState = 'afterPropertyValue'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function pop () {
|
|
|
|
stack.pop()
|
|
|
|
|
|
|
|
const current = stack[stack.length - 1]
|
|
|
|
if (current == null) {
|
|
|
|
parseState = 'end'
|
|
|
|
} else if (Array.isArray(current)) {
|
|
|
|
parseState = 'afterArrayValue'
|
|
|
|
} else {
|
|
|
|
parseState = 'afterPropertyValue'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This code is unreachable.
|
|
|
|
// function invalidParseState () {
|
|
|
|
// return new Error(`JSON5: invalid parse state '${parseState}'`)
|
|
|
|
// }
|
|
|
|
|
|
|
|
// This code is unreachable.
|
|
|
|
// function invalidLexState (state) {
|
|
|
|
// return new Error(`JSON5: invalid lex state '${state}'`)
|
|
|
|
// }
|
|
|
|
|
|
|
|
function invalidChar (c) {
|
|
|
|
if (c === undefined) {
|
|
|
|
return syntaxError(`JSON5: invalid end of input at ${line}:${column}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return syntaxError(`JSON5: invalid character '${formatChar(c)}' at ${line}:${column}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
function invalidEOF () {
|
|
|
|
return syntaxError(`JSON5: invalid end of input at ${line}:${column}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This code is unreachable.
|
|
|
|
// function invalidToken () {
|
|
|
|
// if (token.type === 'eof') {
|
|
|
|
// return syntaxError(`JSON5: invalid end of input at ${line}:${column}`)
|
|
|
|
// }
|
|
|
|
|
|
|
|
// const c = String.fromCodePoint(token.value.codePointAt(0))
|
|
|
|
// return syntaxError(`JSON5: invalid character '${formatChar(c)}' at ${line}:${column}`)
|
|
|
|
// }
|
|
|
|
|
|
|
|
function invalidIdentifier () {
|
|
|
|
column -= 5
|
|
|
|
return syntaxError(`JSON5: invalid identifier character at ${line}:${column}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
function separatorChar (c) {
|
|
|
|
console.warn(`JSON5: '${formatChar(c)}' in strings is not valid ECMAScript; consider escaping`)
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatChar (c) {
|
|
|
|
const replacements = {
|
|
|
|
"'": "\\'",
|
|
|
|
'"': '\\"',
|
|
|
|
'\\': '\\\\',
|
|
|
|
'\b': '\\b',
|
|
|
|
'\f': '\\f',
|
|
|
|
'\n': '\\n',
|
|
|
|
'\r': '\\r',
|
|
|
|
'\t': '\\t',
|
|
|
|
'\v': '\\v',
|
|
|
|
'\0': '\\0',
|
|
|
|
'\u2028': '\\u2028',
|
|
|
|
'\u2029': '\\u2029',
|
|
|
|
}
|
|
|
|
|
|
|
|
if (replacements[c]) {
|
|
|
|
return replacements[c]
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c < ' ') {
|
|
|
|
const hexString = c.charCodeAt(0).toString(16)
|
|
|
|
return '\\x' + ('00' + hexString).substring(hexString.length)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
function syntaxError (message) {
|
|
|
|
const err = new SyntaxError(message)
|
|
|
|
err.lineNumber = line
|
|
|
|
err.columnNumber = column
|
|
|
|
return err
|
|
|
|
}
|