123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- /**
- * Copyright (c) 2019 GraphQL Contributors
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
- /**
- * This JSON parser simply walks the input, generating an AST. Use this in lieu
- * of JSON.parse if you need character offset parse errors and an AST parse tree
- * with location information.
- *
- * If an error is encountered, a SyntaxError will be thrown, with properties:
- *
- * - message: string
- * - start: int - the start inclusive offset of the syntax error
- * - end: int - the end exclusive offset of the syntax error
- *
- */
- type JSONEOFValue = {
- kind: "EOF"
- start: number
- end: number
- }
- type JSONNullValue = {
- kind: "Null"
- start: number
- end: number
- }
- type JSONNumberValue = {
- kind: "Number"
- start: number
- end: number
- value: number
- }
- type JSONStringValue = {
- kind: "String"
- start: number
- end: number
- value: string
- }
- type JSONBooleanValue = {
- kind: "Boolean"
- start: number
- end: number
- value: boolean
- }
- type JSONPrimitiveValue =
- | JSONNullValue
- | JSONEOFValue
- | JSONStringValue
- | JSONNumberValue
- | JSONBooleanValue
- export type JSONObjectValue = {
- kind: "Object"
- start: number
- end: number
- // eslint-disable-next-line no-use-before-define
- members: JSONObjectMember[]
- }
- export type JSONArrayValue = {
- kind: "Array"
- start: number
- end: number
- // eslint-disable-next-line no-use-before-define
- values: JSONValue[]
- }
- export type JSONValue = JSONObjectValue | JSONArrayValue | JSONPrimitiveValue
- export type JSONObjectMember = {
- kind: "Member"
- start: number
- end: number
- key: JSONStringValue
- value: JSONValue
- }
- export default function jsonParse(
- str: string
- ): JSONObjectValue | JSONArrayValue {
- string = str
- strLen = str.length
- start = end = lastEnd = -1
- ch()
- lex()
- try {
- const ast = parseObj()
- expect("EOF")
- return ast
- } catch (e) {
- // Try parsing expecting a root array
- const ast = parseArr()
- expect("EOF")
- return ast
- }
- }
- let string: string
- let strLen: number
- let start: number
- let end: number
- let lastEnd: number
- let code: number
- let kind: string
- function parseObj(): JSONObjectValue {
- const nodeStart = start
- const members = []
- expect("{")
- if (!skip("}")) {
- do {
- members.push(parseMember())
- } while (skip(","))
- expect("}")
- }
- return {
- kind: "Object",
- start: nodeStart,
- end: lastEnd,
- members,
- }
- }
- function parseMember(): JSONObjectMember {
- const nodeStart = start
- const key = kind === "String" ? (curToken() as JSONStringValue) : null
- expect("String")
- expect(":")
- const value = parseVal()
- return {
- kind: "Member",
- start: nodeStart,
- end: lastEnd,
- key: key!,
- value,
- }
- }
- function parseArr(): JSONArrayValue {
- const nodeStart = start
- const values: JSONValue[] = []
- expect("[")
- if (!skip("]")) {
- do {
- values.push(parseVal())
- } while (skip(","))
- expect("]")
- }
- return {
- kind: "Array",
- start: nodeStart,
- end: lastEnd,
- values,
- }
- }
- function parseVal(): JSONValue {
- switch (kind) {
- case "[":
- return parseArr()
- case "{":
- return parseObj()
- case "String":
- case "Number":
- case "Boolean":
- case "Null":
- // eslint-disable-next-line no-case-declarations
- const token = curToken()
- lex()
- return token
- }
- return expect("Value") as never
- }
- function curToken(): JSONPrimitiveValue {
- return {
- kind: kind as any,
- start,
- end,
- value: JSON.parse(string.slice(start, end)),
- }
- }
- function expect(str: string) {
- if (kind === str) {
- lex()
- return
- }
- let found
- if (kind === "EOF") {
- found = "[end of file]"
- } else if (end - start > 1) {
- found = `\`${string.slice(start, end)}\``
- } else {
- const match = string.slice(start).match(/^.+?\b/)
- found = `\`${match ? match[0] : string[start]}\``
- }
- throw syntaxError(`Expected ${str} but found ${found}.`)
- }
- type SyntaxError = {
- message: string
- start: number
- end: number
- }
- function syntaxError(message: string): SyntaxError {
- return { message, start, end }
- }
- function skip(k: string) {
- if (kind === k) {
- lex()
- return true
- }
- }
- function ch() {
- if (end < strLen) {
- end++
- code = end === strLen ? 0 : string.charCodeAt(end)
- }
- }
- function lex() {
- lastEnd = end
- while (code === 9 || code === 10 || code === 13 || code === 32) {
- ch()
- }
- if (code === 0) {
- kind = "EOF"
- return
- }
- start = end
- switch (code) {
- // "
- case 34:
- kind = "String"
- return readString()
- // -, 0-9
- case 45:
- case 48:
- case 49:
- case 50:
- case 51:
- case 52:
- case 53:
- case 54:
- case 55:
- case 56:
- case 57:
- kind = "Number"
- return readNumber()
- // f
- case 102:
- if (string.slice(start, start + 5) !== "false") {
- break
- }
- end += 4
- ch()
- kind = "Boolean"
- return
- // n
- case 110:
- if (string.slice(start, start + 4) !== "null") {
- break
- }
- end += 3
- ch()
- kind = "Null"
- return
- // t
- case 116:
- if (string.slice(start, start + 4) !== "true") {
- break
- }
- end += 3
- ch()
- kind = "Boolean"
- return
- }
- kind = string[start]
- ch()
- }
- function readString() {
- ch()
- while (code !== 34 && code > 31) {
- if (code === (92 as any)) {
- // \
- ch()
- switch (code) {
- case 34: // "
- case 47: // /
- case 92: // \
- case 98: // b
- case 102: // f
- case 110: // n
- case 114: // r
- case 116: // t
- ch()
- break
- case 117: // u
- ch()
- readHex()
- readHex()
- readHex()
- readHex()
- break
- default:
- throw syntaxError("Bad character escape sequence.")
- }
- } else if (end === strLen) {
- throw syntaxError("Unterminated string.")
- } else {
- ch()
- }
- }
- if (code === 34) {
- ch()
- return
- }
- throw syntaxError("Unterminated string.")
- }
- function readHex() {
- if (
- (code >= 48 && code <= 57) || // 0-9
- (code >= 65 && code <= 70) || // A-F
- (code >= 97 && code <= 102) // a-f
- ) {
- return ch()
- }
- throw syntaxError("Expected hexadecimal digit.")
- }
- function readNumber() {
- if (code === 45) {
- // -
- ch()
- }
- if (code === 48) {
- // 0
- ch()
- } else {
- readDigits()
- }
- if (code === 46) {
- // .
- ch()
- readDigits()
- }
- if (code === 69 || code === 101) {
- // E e
- ch()
- if (code === (43 as any) || code === (45 as any)) {
- // + -
- ch()
- }
- readDigits()
- }
- }
- function readDigits() {
- if (code < 48 || code > 57) {
- // 0 - 9
- throw syntaxError("Expected decimal digit.")
- }
- do {
- ch()
- } while (code >= 48 && code <= 57) // 0 - 9
- }
|