Spaces:
Running
Running
| import * as text from './text.js'; | |
| const json = {}; | |
| const bson = {}; | |
| json.TextReader = class { | |
| static open(data) { | |
| const decoder = text.Decoder.open(data); | |
| const position = decoder.position; | |
| let state = ''; | |
| for (let i = 0; i < 0x1000; i++) { | |
| const c = decoder.decode(); | |
| if (state === 'match') { | |
| break; | |
| } | |
| if (c === undefined || c === '\0') { | |
| if (state === '') { | |
| return null; | |
| } | |
| break; | |
| } | |
| if (c <= ' ') { | |
| if (c !== ' ' && c !== '\n' && c !== '\r' && c !== '\t') { | |
| return null; | |
| } | |
| continue; | |
| } | |
| switch (state) { | |
| case '': | |
| if (c === '{') { | |
| state = '{}'; | |
| } else if (c === '[') { | |
| state = '[]'; | |
| } else { | |
| return null; | |
| } | |
| break; | |
| case '[]': | |
| if (c !== '"' && c !== '-' && c !== '+' && c !== '{' && c !== '[' && c !== ']' && (c < '0' || c > '9')) { | |
| return null; | |
| } | |
| state = 'match'; | |
| break; | |
| case '{}': | |
| if (c !== '"' && c !== '}') { | |
| return null; | |
| } | |
| state = 'match'; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| decoder.position = position; | |
| return new json.TextReader(decoder); | |
| } | |
| constructor(decoder) { | |
| this._decoder = decoder; | |
| this._start = decoder.position; | |
| this._escape = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' }; | |
| } | |
| read() { | |
| const stack = []; | |
| this._decoder.position = this._start; | |
| this._position = this._start; | |
| this._char = this._decoder.decode(); | |
| this._whitespace(); | |
| let obj = null; | |
| let first = true; | |
| for (;;) { | |
| if (Array.isArray(obj)) { | |
| this._whitespace(); | |
| let c = this._char; | |
| if (c === ']') { | |
| this._next(); | |
| this._whitespace(); | |
| if (stack.length > 0) { | |
| obj = stack.pop(); | |
| first = false; | |
| continue; | |
| } | |
| if (this._char !== undefined) { | |
| this._unexpected(); | |
| } | |
| return obj; | |
| } | |
| if (!first) { | |
| if (this._char !== ',') { | |
| this._unexpected(); | |
| } | |
| this._next(); | |
| this._whitespace(); | |
| c = this._char; | |
| } | |
| first = false; | |
| switch (c) { | |
| case '{': { | |
| this._next(); | |
| stack.push(obj); | |
| const item = {}; | |
| obj.push(item); | |
| obj = item; | |
| first = true; | |
| break; | |
| } | |
| case '[': { | |
| this._next(); | |
| stack.push(obj); | |
| const item = []; | |
| obj.push(item); | |
| obj = item; | |
| first = true; | |
| break; | |
| } | |
| default: { | |
| obj.push(c === '"' ? this._string() : this._literal()); | |
| break; | |
| } | |
| } | |
| } else if (obj instanceof Object) { | |
| this._whitespace(); | |
| let c = this._char; | |
| if (c === '}') { | |
| this._next(); | |
| this._whitespace(); | |
| if (stack.length > 0) { | |
| obj = stack.pop(); | |
| first = false; | |
| continue; | |
| } | |
| if (this._char !== undefined) { | |
| this._unexpected(); | |
| } | |
| return obj; | |
| } | |
| if (!first) { | |
| if (this._char !== ',') { | |
| this._unexpected(); | |
| } | |
| this._next(); | |
| this._whitespace(); | |
| c = this._char; | |
| } | |
| first = false; | |
| if (c === '"') { | |
| const key = this._string(); | |
| switch (key) { | |
| case '__proto__': | |
| throw new json.Error(`Invalid key '${key}' ${this._location()}`); | |
| default: | |
| break; | |
| } | |
| this._whitespace(); | |
| if (this._char !== ':') { | |
| this._unexpected(); | |
| } | |
| this._next(); | |
| this._whitespace(); | |
| c = this._char; | |
| switch (c) { | |
| case '{': { | |
| this._next(); | |
| stack.push(obj); | |
| const value = {}; | |
| obj[key] = value; | |
| obj = value; | |
| first = true; | |
| break; | |
| } | |
| case '[': { | |
| this._next(); | |
| stack.push(obj); | |
| const value = []; | |
| obj[key] = value; | |
| obj = value; | |
| first = true; | |
| break; | |
| } | |
| default: { | |
| obj[key] = c === '"' ? this._string() : this._literal(); | |
| break; | |
| } | |
| } | |
| this._whitespace(); | |
| continue; | |
| } | |
| this._unexpected(); | |
| } else { | |
| const c = this._char; | |
| switch (c) { | |
| case '{': { | |
| this._next(); | |
| this._whitespace(); | |
| obj = {}; | |
| first = true; | |
| break; | |
| } | |
| case '[': { | |
| this._next(); | |
| this._whitespace(); | |
| obj = []; | |
| first = true; | |
| break; | |
| } | |
| default: { | |
| let value = null; | |
| if (c === '"') { | |
| value = this._string(); | |
| } else if (c >= '0' && c <= '9') { | |
| value = this._number(); | |
| } else { | |
| value = this._literal(); | |
| } | |
| this._whitespace(); | |
| if (this._char !== undefined) { | |
| this._unexpected(); | |
| } | |
| return value; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| _next() { | |
| if (this._char === undefined) { | |
| this._unexpected(); | |
| } | |
| this._position = this._decoder.position; | |
| this._char = this._decoder.decode(); | |
| } | |
| _whitespace() { | |
| while (this._char === ' ' || this._char === '\n' || this._char === '\r' || this._char === '\t') { | |
| this._next(); | |
| } | |
| } | |
| _literal() { | |
| const c = this._char; | |
| if (c >= '0' && c <= '9') { | |
| return this._number(); | |
| } | |
| switch (c) { | |
| case 't': this._expect('true'); return true; | |
| case 'f': this._expect('false'); return false; | |
| case 'n': this._expect('null'); return null; | |
| case 'N': this._expect('NaN'); return NaN; | |
| case 'I': this._expect('Infinity'); return Infinity; | |
| case '-': return this._number(); | |
| default: this._unexpected(); | |
| } | |
| return null; | |
| } | |
| _number() { | |
| let value = ''; | |
| if (this._char === '-') { | |
| value = '-'; | |
| this._next(); | |
| } | |
| if (this._char === 'I') { | |
| this._expect('Infinity'); | |
| return -Infinity; | |
| } | |
| const c = this._char; | |
| if (c < '0' || c > '9') { | |
| this._unexpected(); | |
| } | |
| value += c; | |
| this._next(); | |
| if (c === '0') { | |
| const n = this._char; | |
| if (n >= '0' && n <= '9') { | |
| this._unexpected(); | |
| } | |
| } | |
| while (this._char >= '0' && this._char <= '9') { | |
| value += this._char; | |
| this._next(); | |
| } | |
| if (this._char === '.') { | |
| value += '.'; | |
| this._next(); | |
| const n = this._char; | |
| if (n < '0' || n > '9') { | |
| this._unexpected(); | |
| } | |
| while (this._char >= '0' && this._char <= '9') { | |
| value += this._char; | |
| this._next(); | |
| } | |
| } | |
| if (this._char === 'e' || this._char === 'E') { | |
| value += this._char; | |
| this._next(); | |
| const s = this._char; | |
| if (s === '-' || s === '+') { | |
| value += this._char; | |
| this._next(); | |
| } | |
| const c = this._char; | |
| if (c < '0' || c > '9') { | |
| this._unexpected(); | |
| } | |
| value += this._char; | |
| this._next(); | |
| while (this._char >= '0' && this._char <= '9') { | |
| value += this._char; | |
| this._next(); | |
| } | |
| } | |
| return Number(value); | |
| } | |
| _string() { | |
| let value = ''; | |
| this._next(); | |
| while (this._char !== '"') { | |
| if (this._char === '\\') { | |
| this._next(); | |
| if (this._char === 'u') { | |
| this._next(); | |
| let uffff = 0; | |
| for (let i = 0; i < 4; i ++) { | |
| const hex = parseInt(this._char, 16); | |
| if (!isFinite(hex)) { | |
| this._unexpected(); | |
| } | |
| this._next(); | |
| uffff = uffff * 16 + hex; | |
| } | |
| value += String.fromCharCode(uffff); | |
| } else if (this._escape[this._char]) { | |
| value += this._escape[this._char]; | |
| this._next(); | |
| } else { | |
| this._unexpected(); | |
| } | |
| } else if (this._char < ' ') { | |
| this._unexpected(); | |
| } else { | |
| value += this._char; | |
| this._next(); | |
| } | |
| } | |
| this._next(); | |
| return value; | |
| } | |
| _expect(value) { | |
| for (let i = 0; i < value.length; i++) { | |
| if (value[i] !== this._char) { | |
| this._unexpected(); | |
| } | |
| this._next(); | |
| } | |
| } | |
| _unexpected() { | |
| let c = this._char; | |
| if (c === undefined) { | |
| throw new json.Error('Unexpected end of JSON input.'); | |
| } else if (c === '"') { | |
| c = 'string'; | |
| } else if ((c >= '0' && c <= '9') || c === '-') { | |
| c = 'number'; | |
| } else { | |
| if (c < ' ' || c > '\x7F') { | |
| const name = Object.keys(this._escape).filter((key) => this._escape[key] === c); | |
| c = (name.length === 1) ? `\\${name}` : `\\u${(`000${c.charCodeAt(0).toString(16)}`).slice(-4)}`; | |
| } | |
| c = `token '${c}'`; | |
| } | |
| throw new json.Error(`Unexpected ${c} ${this._location()}`); | |
| } | |
| _location() { | |
| let line = 1; | |
| let column = 1; | |
| this._decoder.position = this._start; | |
| let c = ''; | |
| do { | |
| if (this._decoder.position === this._position) { | |
| return `at ${line}:${column}.`; | |
| } | |
| c = this._decoder.decode(); | |
| if (c === '\n') { | |
| line++; | |
| column = 1; | |
| } else { | |
| column++; | |
| } | |
| } | |
| while (c !== undefined); | |
| return `at ${line}:${column}.`; | |
| } | |
| }; | |
| json.BinaryReader = class { | |
| static open(data) { | |
| const length = data.length; | |
| const buffer = data instanceof Uint8Array ? data : data.peek(Math.min(data.length, 8)); | |
| const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); | |
| if (buffer.length >= 4 && view.getInt32(0, true) === length) { | |
| return new json.BinaryReader(data, 'bson'); | |
| } | |
| const [signature] = buffer; | |
| if (signature === 0x7B || signature === 0x5B) { | |
| return new json.BinaryReader(data, 'ubj'); | |
| } | |
| return null; | |
| } | |
| constructor(data, format) { | |
| this._buffer = data instanceof Uint8Array ? data : data.peek(); | |
| this._format = format; | |
| } | |
| read() { | |
| switch (this._format) { | |
| case 'bson': | |
| return this._readBson(); | |
| case 'ubj': | |
| return this._readUbj(); | |
| default: | |
| throw new json.Error(`Unsupported binary JSON format '${this._format}'.`); | |
| } | |
| } | |
| _readBson() { | |
| const buffer = this._buffer; | |
| const length = buffer.length; | |
| const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); | |
| const asciiDecoder = new TextDecoder('ascii'); | |
| const utf8Decoder = new TextDecoder('utf-8'); | |
| let position = 0; | |
| const skip = (offset) => { | |
| position += offset; | |
| if (position > length) { | |
| throw new bson.Error(`Expected ${position + length} more bytes. The file might be corrupted. Unexpected end of file.`); | |
| } | |
| }; | |
| const header = () => { | |
| const start = position; | |
| skip(4); | |
| const size = view.getInt32(start, 4); | |
| if (size < 5 || start + size > length || buffer[start + size - 1] !== 0x00) { | |
| throw new bson.Error('Invalid file size.'); | |
| } | |
| }; | |
| header(); | |
| const stack = []; | |
| let obj = {}; | |
| for (;;) { | |
| skip(1); | |
| const type = buffer[position - 1]; | |
| if (type === 0x00) { | |
| if (stack.length === 0) { | |
| break; | |
| } | |
| obj = stack.pop(); | |
| continue; | |
| } | |
| const start = position; | |
| position = buffer.indexOf(0x00, start); | |
| if (position === -1) { | |
| throw new bson.Error('Missing string terminator.'); | |
| } | |
| position += 1; | |
| const key = asciiDecoder.decode(buffer.subarray(start, position - 1)); | |
| let value = null; | |
| switch (type) { | |
| case 0x01: { // float64 | |
| const start = position; | |
| skip(8); | |
| value = view.getFloat64(start, true); | |
| break; | |
| } | |
| case 0x02: { // string | |
| skip(4); | |
| const size = view.getInt32(position - 4, true); | |
| const start = position; | |
| skip(size); | |
| value = utf8Decoder.decode(buffer.subarray(start, position - 1)); | |
| if (buffer[position - 1] !== 0) { | |
| throw new bson.Error('String missing terminal 0.'); | |
| } | |
| break; | |
| } | |
| case 0x03: { // object | |
| header(); | |
| value = {}; | |
| break; | |
| } | |
| case 0x04: { // array | |
| header(); | |
| value = []; | |
| break; | |
| } | |
| case 0x05: { // bytes | |
| const start = position; | |
| skip(5); | |
| const size = view.getInt32(start, true); | |
| const subtype = buffer[start + 4]; | |
| if (subtype !== 0x00) { | |
| throw new bson.Error(`Unsupported binary subtype '${subtype}'.`); | |
| } | |
| skip(size); | |
| value = buffer.subarray(start + 5, position); | |
| break; | |
| } | |
| case 0x08: { // boolean | |
| skip(1); | |
| value = buffer[position - 1]; | |
| if (value > 1) { | |
| throw new bson.Error(`Invalid boolean value '${value}'.`); | |
| } | |
| value = value === 1 ? true : false; | |
| break; | |
| } | |
| case 0x0A: | |
| value = null; | |
| break; | |
| case 0x10: { | |
| const start = position; | |
| skip(4); | |
| value = view.getInt32(start, true); | |
| break; | |
| } | |
| case 0x11: { // uint64 | |
| const start = position; | |
| skip(8); | |
| value = view.getBigUint64(start, true); | |
| break; | |
| } | |
| case 0x12: { // int64 | |
| const start = position; | |
| skip(8); | |
| value = view.getBigInt64(start, true); | |
| break; | |
| } | |
| default: { | |
| throw new bson.Error(`Unsupported value type '${type}'.`); | |
| } | |
| } | |
| if (Array.isArray(obj)) { | |
| if (obj.length !== parseInt(key, 10)) { | |
| throw new bson.Error(`Invalid array index '${key}'.`); | |
| } | |
| obj.push(value); | |
| } else { | |
| switch (key) { | |
| case '__proto__': | |
| case 'constructor': | |
| case 'prototype': | |
| throw new bson.Error(`Invalid key '${key}' at ${position}'.`); | |
| default: | |
| break; | |
| } | |
| obj[key] = value; | |
| } | |
| if (type === 0x03 || type === 0x04) { | |
| stack.push(obj); | |
| obj = value; | |
| } | |
| } | |
| if (position !== length) { | |
| throw new bson.Error(`Unexpected data at '${position}'.`); | |
| } | |
| return obj; | |
| } | |
| _readUbj() { | |
| throw new json.Error('Unsupported JSON UBJ data.'); | |
| } | |
| }; | |
| json.Error = class extends Error { | |
| constructor(message) { | |
| super(message); | |
| this.name = 'JSON Error'; | |
| } | |
| }; | |
| bson.Error = class extends Error { | |
| constructor(message) { | |
| super(message); | |
| this.name = 'BSON Error'; | |
| } | |
| }; | |
| export const TextReader = json.TextReader; | |
| export const BinaryReader = json.BinaryReader; | |