141 lines
4.5 KiB
JavaScript
141 lines
4.5 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||
|
|
|
||
|
|
var common = require('./common.js');
|
||
|
|
var token = require('./token.js');
|
||
|
|
var jump = require('./jump.js');
|
||
|
|
|
||
|
|
const defaultDecodeOptions = {
|
||
|
|
strict: false,
|
||
|
|
allowIndefinite: true,
|
||
|
|
allowUndefined: true,
|
||
|
|
allowBigInt: true
|
||
|
|
};
|
||
|
|
class Tokeniser {
|
||
|
|
constructor(data, options = {}) {
|
||
|
|
this.pos = 0;
|
||
|
|
this.data = data;
|
||
|
|
this.options = options;
|
||
|
|
}
|
||
|
|
done() {
|
||
|
|
return this.pos >= this.data.length;
|
||
|
|
}
|
||
|
|
next() {
|
||
|
|
const byt = this.data[this.pos];
|
||
|
|
let token = jump.quick[byt];
|
||
|
|
if (token === undefined) {
|
||
|
|
const decoder = jump.jump[byt];
|
||
|
|
if (!decoder) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } no decoder for major type ${ byt >>> 5 } (byte 0x${ byt.toString(16).padStart(2, '0') })`);
|
||
|
|
}
|
||
|
|
const minor = byt & 31;
|
||
|
|
token = decoder(this.data, this.pos, minor, this.options);
|
||
|
|
}
|
||
|
|
this.pos += token.encodedLength;
|
||
|
|
return token;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const DONE = Symbol.for('DONE');
|
||
|
|
const BREAK = Symbol.for('BREAK');
|
||
|
|
function tokenToArray(token, tokeniser, options) {
|
||
|
|
const arr = [];
|
||
|
|
for (let i = 0; i < token.value; i++) {
|
||
|
|
const value = tokensToObject(tokeniser, options);
|
||
|
|
if (value === BREAK) {
|
||
|
|
if (token.value === Infinity) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } got unexpected break to lengthed array`);
|
||
|
|
}
|
||
|
|
if (value === DONE) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } found array but not enough entries (got ${ i }, expected ${ token.value })`);
|
||
|
|
}
|
||
|
|
arr[i] = value;
|
||
|
|
}
|
||
|
|
return arr;
|
||
|
|
}
|
||
|
|
function tokenToMap(token, tokeniser, options) {
|
||
|
|
const useMaps = options.useMaps === true;
|
||
|
|
const obj = useMaps ? undefined : {};
|
||
|
|
const m = useMaps ? new Map() : undefined;
|
||
|
|
for (let i = 0; i < token.value; i++) {
|
||
|
|
const key = tokensToObject(tokeniser, options);
|
||
|
|
if (key === BREAK) {
|
||
|
|
if (token.value === Infinity) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } got unexpected break to lengthed map`);
|
||
|
|
}
|
||
|
|
if (key === DONE) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } found map but not enough entries (got ${ i } [no key], expected ${ token.value })`);
|
||
|
|
}
|
||
|
|
if (useMaps !== true && typeof key !== 'string') {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } non-string keys not supported (got ${ typeof key })`);
|
||
|
|
}
|
||
|
|
if (options.rejectDuplicateMapKeys === true) {
|
||
|
|
if (useMaps && m.has(key) || !useMaps && key in obj) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } found repeat map key "${ key }"`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const value = tokensToObject(tokeniser, options);
|
||
|
|
if (value === DONE) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } found map but not enough entries (got ${ i } [no value], expected ${ token.value })`);
|
||
|
|
}
|
||
|
|
if (useMaps) {
|
||
|
|
m.set(key, value);
|
||
|
|
} else {
|
||
|
|
obj[key] = value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return useMaps ? m : obj;
|
||
|
|
}
|
||
|
|
function tokensToObject(tokeniser, options) {
|
||
|
|
if (tokeniser.done()) {
|
||
|
|
return DONE;
|
||
|
|
}
|
||
|
|
const token$1 = tokeniser.next();
|
||
|
|
if (token$1.type === token.Type.break) {
|
||
|
|
return BREAK;
|
||
|
|
}
|
||
|
|
if (token$1.type.terminal) {
|
||
|
|
return token$1.value;
|
||
|
|
}
|
||
|
|
if (token$1.type === token.Type.array) {
|
||
|
|
return tokenToArray(token$1, tokeniser, options);
|
||
|
|
}
|
||
|
|
if (token$1.type === token.Type.map) {
|
||
|
|
return tokenToMap(token$1, tokeniser, options);
|
||
|
|
}
|
||
|
|
if (token$1.type === token.Type.tag) {
|
||
|
|
if (options.tags && typeof options.tags[token$1.value] === 'function') {
|
||
|
|
const tagged = tokensToObject(tokeniser, options);
|
||
|
|
return options.tags[token$1.value](tagged);
|
||
|
|
}
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } tag not supported (${ token$1.value })`);
|
||
|
|
}
|
||
|
|
throw new Error('unsupported');
|
||
|
|
}
|
||
|
|
function decode(data, options) {
|
||
|
|
if (!(data instanceof Uint8Array)) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } data to decode must be a Uint8Array`);
|
||
|
|
}
|
||
|
|
options = Object.assign({}, defaultDecodeOptions, options);
|
||
|
|
const tokeniser = options.tokenizer || new Tokeniser(data, options);
|
||
|
|
const decoded = tokensToObject(tokeniser, options);
|
||
|
|
if (decoded === DONE) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } did not find any content to decode`);
|
||
|
|
}
|
||
|
|
if (decoded === BREAK) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } got unexpected break`);
|
||
|
|
}
|
||
|
|
if (!tokeniser.done()) {
|
||
|
|
throw new Error(`${ common.decodeErrPrefix } too many terminals, data makes no sense`);
|
||
|
|
}
|
||
|
|
return decoded;
|
||
|
|
}
|
||
|
|
|
||
|
|
exports.Tokeniser = Tokeniser;
|
||
|
|
exports.decode = decode;
|
||
|
|
exports.tokensToObject = tokensToObject;
|