const enum State {
TopLevel,
Array,
Object,
}
const enum Char {
Newline = 0x0A,
Space = 0x20,
Quote = 0x22,
Plus = 0x2B,
Comma = 0x2C,
Minus = 0x2D,
Dot = 0x2E,
Slash = 0x2F,
Colon = 0x3A,
OpenBracket = 0x5B,
Backslash = 0x5C,
CloseBracket = 0x5D,
OpenBrace = 0x7B,
CloseBrace = 0x7D,
Digit0 = 0x30,
Digit1 = 0x31,
Digit2 = 0x32,
Digit3 = 0x33,
Digit4 = 0x34,
Digit5 = 0x35,
Digit6 = 0x36,
Digit7 = 0x37,
Digit8 = 0x38,
Digit9 = 0x39,
UpperA = 0x41,
UpperE = 0x45,
UpperF = 0x46,
LowerA = 0x61,
LowerB = 0x62,
LowerE = 0x65,
LowerF = 0x66,
LowerL = 0x6C,
LowerN = 0x6E,
LowerR = 0x72,
LowerS = 0x73,
LowerT = 0x74,
LowerU = 0x75,
}
const fromCharCode = String.fromCharCode
function throwSyntaxError(bytes: Uint8Array, index: number, message?: string): void {
const c = bytes[index]
let line = 1
let column = 0
for (let i = 0; i < index; i++) {
if (bytes[i] === Char.Newline) {
line++
column = 0
} else {
column++
}
}
throw new SyntaxError(
message ? message :
index === bytes.length ? 'Unexpected end of input while parsing JSON' :
c >= 0x20 && c <= 0x7E ? `Unexpected character ${fromCharCode(c)} in JSON at position ${index} (line ${line}, column ${column})` :
`Unexpected byte 0x${c.toString(16)} in JSON at position ${index} (line ${line}, column ${column})`)
}
export function JSON_parse(bytes: Uint8Array): any {
if (!(bytes instanceof Uint8Array)) {
throw new Error(`JSON input must be a Uint8Array`)
}
const propertyStack: (string | null)[] = []
const objectStack: any[] = []
const stateStack: State[] = []
const length = bytes.length
let property: string | null = null
let state = State.TopLevel
let object: any
let i = 0
while (i < length) {
let c = bytes[i++]
if (c <= Char.Space) {
continue
}
let value: any
if (state === State.Object && property === null && c !== Char.Quote && c !== Char.CloseBrace) {
throwSyntaxError(bytes, --i)
}
switch (c) {
case Char.LowerT: {
if (bytes[i++] !== Char.LowerR || bytes[i++] !== Char.LowerU || bytes[i++] !== Char.LowerE) {
throwSyntaxError(bytes, --i)
}
value = true
break
}
case Char.LowerF: {
if (bytes[i++] !== Char.LowerA || bytes[i++] !== Char.LowerL || bytes[i++] !== Char.LowerS || bytes[i++] !== Char.LowerE) {
throwSyntaxError(bytes, --i)
}
value = false
break
}
case Char.LowerN: {
if (bytes[i++] !== Char.LowerU || bytes[i++] !== Char.LowerL || bytes[i++] !== Char.LowerL) {
throwSyntaxError(bytes, --i)
}
value = null
break
}
case Char.Minus:
case Char.Dot:
case Char.Digit0:
case Char.Digit1:
case Char.Digit2:
case Char.Digit3:
case Char.Digit4:
case Char.Digit5:
case Char.Digit6:
case Char.Digit7:
case Char.Digit8:
case Char.Digit9: {
let index = i
value = fromCharCode(c)
c = bytes[i]
while (true) {
switch (c) {
case Char.Plus:
case Char.Minus:
case Char.Dot:
case Char.Digit0:
case Char.Digit1:
case Char.Digit2:
case Char.Digit3:
case Char.Digit4:
case Char.Digit5:
case Char.Digit6:
case Char.Digit7:
case Char.Digit8:
case Char.Digit9:
case Char.LowerE:
case Char.UpperE: {
value += fromCharCode(c)
c = bytes[++i]
continue
}
}
break
}
value = +value
if (isNaN(value)) {
throwSyntaxError(bytes, --index, 'Invalid number')
}
break
}
case Char.Quote: {
value = ''
while (true) {
if (i >= length) {
throwSyntaxError(bytes, length)
}
c = bytes[i++]
if (c === Char.Quote) {
break
}
else if (c === Char.Backslash) {
switch (bytes[i++]) {
case Char.Quote: value += '\"'; break
case Char.Slash: value += '\/'; break
case Char.Backslash: value += '\\'; break
case Char.LowerB: value += '\b'; break
case Char.LowerF: value += '\f'; break
case Char.LowerN: value += '\n'; break
case Char.LowerR: value += '\r'; break
case Char.LowerT: value += '\t'; break
case Char.LowerU: {
let code = 0
for (let j = 0; j < 4; j++) {
c = bytes[i++]
code <<= 4
if (c >= Char.Digit0 && c <= Char.Digit9) code |= c - Char.Digit0
else if (c >= Char.LowerA && c <= Char.LowerF) code |= c + (10 - Char.LowerA)
else if (c >= Char.UpperA && c <= Char.UpperF) code |= c + (10 - Char.UpperA)
else throwSyntaxError(bytes, --i)
}
value += fromCharCode(code)
break
}
default: throwSyntaxError(bytes, --i); break
}
}
else if (c <= 0x7F) {
value += fromCharCode(c)
}
else if ((c & 0xE0) === 0xC0) {
value += fromCharCode(((c & 0x1F) << 6) | (bytes[i++] & 0x3F))
}
else if ((c & 0xF0) === 0xE0) {
value += fromCharCode(((c & 0x0F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F))
}
else if ((c & 0xF8) == 0xF0) {
let codePoint = ((c & 0x07) << 18) | ((bytes[i++] & 0x3F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F)
if (codePoint > 0xFFFF) {
codePoint -= 0x10000
value += fromCharCode(((codePoint >> 10) & 0x3FF) | 0xD800)
codePoint = 0xDC00 | (codePoint & 0x3FF)
}
value += fromCharCode(codePoint)
}
}
value[0]
break
}
case Char.OpenBracket: {
value = []
propertyStack.push(property)
objectStack.push(object)
stateStack.push(state)
property = null
object = value
state = State.Array
continue
}
case Char.OpenBrace: {
value = {}
propertyStack.push(property)
objectStack.push(object)
stateStack.push(state)
property = null
object = value
state = State.Object
continue
}
case Char.CloseBracket: {
if (state !== State.Array) {
throwSyntaxError(bytes, --i)
}
value = object
property = propertyStack.pop() as string | null
object = objectStack.pop()
state = stateStack.pop() as State
break
}
case Char.CloseBrace: {
if (state !== State.Object) {
throwSyntaxError(bytes, --i)
}
value = object
property = propertyStack.pop() as string | null
object = objectStack.pop()
state = stateStack.pop() as State
break
}
default: {
throwSyntaxError(bytes, --i)
}
}
c = bytes[i]
while (c <= Char.Space) {
c = bytes[++i]
}
switch (state) {
case State.TopLevel: {
if (i === length) {
return value
}
break
}
case State.Array: {
object.push(value)
if (c === Char.Comma) {
i++
continue
}
if (c === Char.CloseBracket) {
continue
}
break
}
case State.Object: {
if (property === null) {
property = value
if (c === Char.Colon) {
i++
continue
}
}
else {
object[property] = value
property = null
if (c === Char.Comma) {
i++
continue
}
if (c === Char.CloseBrace) {
continue
}
}
break
}
}
break
}
throwSyntaxError(bytes, i)
}