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 }