binary-parser
Version:
Blazing-fast binary parser builder
1,167 lines • 44.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
class Context {
constructor(importPath, useContextVariables) {
this.code = "";
this.scopes = [["vars"]];
this.bitFields = [];
this.tmpVariableCount = 0;
this.references = new Map();
this.imports = [];
this.reverseImports = new Map();
this.useContextVariables = false;
this.importPath = importPath;
this.useContextVariables = useContextVariables;
}
generateVariable(name) {
const scopes = [...this.scopes[this.scopes.length - 1]];
if (name) {
scopes.push(name);
}
return scopes.join(".");
}
generateOption(val) {
switch (typeof val) {
case "number":
return val.toString();
case "string":
return this.generateVariable(val);
case "function":
return `${this.addImport(val)}.call(${this.generateVariable()}, vars)`;
}
}
generateError(err) {
this.pushCode(`throw new Error(${err});`);
}
generateTmpVariable() {
return "$tmp" + this.tmpVariableCount++;
}
pushCode(code) {
this.code += code + "\n";
}
pushPath(name) {
if (name) {
this.scopes[this.scopes.length - 1].push(name);
}
}
popPath(name) {
if (name) {
this.scopes[this.scopes.length - 1].pop();
}
}
pushScope(name) {
this.scopes.push([name]);
}
popScope() {
this.scopes.pop();
}
addImport(im) {
if (!this.importPath)
return `(${im})`;
let id = this.reverseImports.get(im);
if (!id) {
id = this.imports.push(im) - 1;
this.reverseImports.set(im, id);
}
return `${this.importPath}[${id}]`;
}
addReference(alias) {
if (!this.references.has(alias)) {
this.references.set(alias, { resolved: false, requested: false });
}
}
markResolved(alias) {
const reference = this.references.get(alias);
if (reference) {
reference.resolved = true;
}
}
markRequested(aliasList) {
aliasList.forEach((alias) => {
const reference = this.references.get(alias);
if (reference) {
reference.requested = true;
}
});
}
getUnresolvedReferences() {
return Array.from(this.references)
.filter(([_, reference]) => !reference.resolved && !reference.requested)
.map(([alias, _]) => alias);
}
}
const aliasRegistry = new Map();
const FUNCTION_PREFIX = "___parser_";
const PRIMITIVE_SIZES = {
uint8: 1,
uint16le: 2,
uint16be: 2,
uint32le: 4,
uint32be: 4,
int8: 1,
int16le: 2,
int16be: 2,
int32le: 4,
int32be: 4,
int64be: 8,
int64le: 8,
uint64be: 8,
uint64le: 8,
floatle: 4,
floatbe: 4,
doublele: 8,
doublebe: 8,
};
const PRIMITIVE_NAMES = {
uint8: "Uint8",
uint16le: "Uint16",
uint16be: "Uint16",
uint32le: "Uint32",
uint32be: "Uint32",
int8: "Int8",
int16le: "Int16",
int16be: "Int16",
int32le: "Int32",
int32be: "Int32",
int64be: "BigInt64",
int64le: "BigInt64",
uint64be: "BigUint64",
uint64le: "BigUint64",
floatle: "Float32",
floatbe: "Float32",
doublele: "Float64",
doublebe: "Float64",
};
const PRIMITIVE_LITTLE_ENDIANS = {
uint8: false,
uint16le: true,
uint16be: false,
uint32le: true,
uint32be: false,
int8: false,
int16le: true,
int16be: false,
int32le: true,
int32be: false,
int64be: false,
int64le: true,
uint64be: false,
uint64le: true,
floatle: true,
floatbe: false,
doublele: true,
doublebe: false,
};
class Parser {
constructor() {
this.varName = "";
this.type = "";
this.options = {};
this.endian = "be";
this.useContextVariables = false;
}
static start() {
return new Parser();
}
primitiveGenerateN(type, ctx) {
const typeName = PRIMITIVE_NAMES[type];
const littleEndian = PRIMITIVE_LITTLE_ENDIANS[type];
ctx.pushCode(`${ctx.generateVariable(this.varName)} = dataView.get${typeName}(offset, ${littleEndian});`);
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type]};`);
}
primitiveN(type, varName, options) {
return this.setNextParser(type, varName, options);
}
useThisEndian(type) {
return (type + this.endian.toLowerCase());
}
uint8(varName, options = {}) {
return this.primitiveN("uint8", varName, options);
}
uint16(varName, options = {}) {
return this.primitiveN(this.useThisEndian("uint16"), varName, options);
}
uint16le(varName, options = {}) {
return this.primitiveN("uint16le", varName, options);
}
uint16be(varName, options = {}) {
return this.primitiveN("uint16be", varName, options);
}
uint32(varName, options = {}) {
return this.primitiveN(this.useThisEndian("uint32"), varName, options);
}
uint32le(varName, options = {}) {
return this.primitiveN("uint32le", varName, options);
}
uint32be(varName, options = {}) {
return this.primitiveN("uint32be", varName, options);
}
int8(varName, options = {}) {
return this.primitiveN("int8", varName, options);
}
int16(varName, options = {}) {
return this.primitiveN(this.useThisEndian("int16"), varName, options);
}
int16le(varName, options = {}) {
return this.primitiveN("int16le", varName, options);
}
int16be(varName, options = {}) {
return this.primitiveN("int16be", varName, options);
}
int32(varName, options = {}) {
return this.primitiveN(this.useThisEndian("int32"), varName, options);
}
int32le(varName, options = {}) {
return this.primitiveN("int32le", varName, options);
}
int32be(varName, options = {}) {
return this.primitiveN("int32be", varName, options);
}
bigIntVersionCheck() {
if (!DataView.prototype.getBigInt64)
throw new Error("BigInt64 is unsupported on this runtime");
}
int64(varName, options = {}) {
this.bigIntVersionCheck();
return this.primitiveN(this.useThisEndian("int64"), varName, options);
}
int64be(varName, options = {}) {
this.bigIntVersionCheck();
return this.primitiveN("int64be", varName, options);
}
int64le(varName, options = {}) {
this.bigIntVersionCheck();
return this.primitiveN("int64le", varName, options);
}
uint64(varName, options = {}) {
this.bigIntVersionCheck();
return this.primitiveN(this.useThisEndian("uint64"), varName, options);
}
uint64be(varName, options = {}) {
this.bigIntVersionCheck();
return this.primitiveN("uint64be", varName, options);
}
uint64le(varName, options = {}) {
this.bigIntVersionCheck();
return this.primitiveN("uint64le", varName, options);
}
floatle(varName, options = {}) {
return this.primitiveN("floatle", varName, options);
}
floatbe(varName, options = {}) {
return this.primitiveN("floatbe", varName, options);
}
doublele(varName, options = {}) {
return this.primitiveN("doublele", varName, options);
}
doublebe(varName, options = {}) {
return this.primitiveN("doublebe", varName, options);
}
bitN(size, varName, options) {
options.length = size;
return this.setNextParser("bit", varName, options);
}
bit1(varName, options = {}) {
return this.bitN(1, varName, options);
}
bit2(varName, options = {}) {
return this.bitN(2, varName, options);
}
bit3(varName, options = {}) {
return this.bitN(3, varName, options);
}
bit4(varName, options = {}) {
return this.bitN(4, varName, options);
}
bit5(varName, options = {}) {
return this.bitN(5, varName, options);
}
bit6(varName, options = {}) {
return this.bitN(6, varName, options);
}
bit7(varName, options = {}) {
return this.bitN(7, varName, options);
}
bit8(varName, options = {}) {
return this.bitN(8, varName, options);
}
bit9(varName, options = {}) {
return this.bitN(9, varName, options);
}
bit10(varName, options = {}) {
return this.bitN(10, varName, options);
}
bit11(varName, options = {}) {
return this.bitN(11, varName, options);
}
bit12(varName, options = {}) {
return this.bitN(12, varName, options);
}
bit13(varName, options = {}) {
return this.bitN(13, varName, options);
}
bit14(varName, options = {}) {
return this.bitN(14, varName, options);
}
bit15(varName, options = {}) {
return this.bitN(15, varName, options);
}
bit16(varName, options = {}) {
return this.bitN(16, varName, options);
}
bit17(varName, options = {}) {
return this.bitN(17, varName, options);
}
bit18(varName, options = {}) {
return this.bitN(18, varName, options);
}
bit19(varName, options = {}) {
return this.bitN(19, varName, options);
}
bit20(varName, options = {}) {
return this.bitN(20, varName, options);
}
bit21(varName, options = {}) {
return this.bitN(21, varName, options);
}
bit22(varName, options = {}) {
return this.bitN(22, varName, options);
}
bit23(varName, options = {}) {
return this.bitN(23, varName, options);
}
bit24(varName, options = {}) {
return this.bitN(24, varName, options);
}
bit25(varName, options = {}) {
return this.bitN(25, varName, options);
}
bit26(varName, options = {}) {
return this.bitN(26, varName, options);
}
bit27(varName, options = {}) {
return this.bitN(27, varName, options);
}
bit28(varName, options = {}) {
return this.bitN(28, varName, options);
}
bit29(varName, options = {}) {
return this.bitN(29, varName, options);
}
bit30(varName, options = {}) {
return this.bitN(30, varName, options);
}
bit31(varName, options = {}) {
return this.bitN(31, varName, options);
}
bit32(varName, options = {}) {
return this.bitN(32, varName, options);
}
namely(alias) {
aliasRegistry.set(alias, this);
this.alias = alias;
return this;
}
skip(length, options = {}) {
return this.seek(length, options);
}
seek(relOffset, options = {}) {
if (options.assert) {
throw new Error("assert option on seek is not allowed.");
}
return this.setNextParser("seek", "", { length: relOffset });
}
string(varName, options) {
if (!options.zeroTerminated && !options.length && !options.greedy) {
throw new Error("One of length, zeroTerminated, or greedy must be defined for string.");
}
if ((options.zeroTerminated || options.length) && options.greedy) {
throw new Error("greedy is mutually exclusive with length and zeroTerminated for string.");
}
if (options.stripNull && !(options.length || options.greedy)) {
throw new Error("length or greedy must be defined if stripNull is enabled.");
}
options.encoding = options.encoding || "utf8";
return this.setNextParser("string", varName, options);
}
buffer(varName, options) {
if (!options.length && !options.readUntil) {
throw new Error("length or readUntil must be defined for buffer.");
}
return this.setNextParser("buffer", varName, options);
}
wrapped(varName, options) {
if (typeof options !== "object" && typeof varName === "object") {
options = varName;
varName = "";
}
if (!options || !options.wrapper || !options.type) {
throw new Error("Both wrapper and type must be defined for wrapped.");
}
if (!options.length && !options.readUntil) {
throw new Error("length or readUntil must be defined for wrapped.");
}
return this.setNextParser("wrapper", varName, options);
}
array(varName, options) {
if (!options.readUntil && !options.length && !options.lengthInBytes) {
throw new Error("One of readUntil, length and lengthInBytes must be defined for array.");
}
if (!options.type) {
throw new Error("type is required for array.");
}
if (typeof options.type === "string" &&
!aliasRegistry.has(options.type) &&
!(options.type in PRIMITIVE_SIZES)) {
throw new Error(`Array element type "${options.type}" is unkown.`);
}
return this.setNextParser("array", varName, options);
}
choice(varName, options) {
if (typeof options !== "object" && typeof varName === "object") {
options = varName;
varName = "";
}
if (!options) {
throw new Error("tag and choices are are required for choice.");
}
if (!options.tag) {
throw new Error("tag is requird for choice.");
}
if (!options.choices) {
throw new Error("choices is required for choice.");
}
for (const keyString in options.choices) {
const key = parseInt(keyString, 10);
const value = options.choices[key];
if (isNaN(key)) {
throw new Error(`Choice key "${keyString}" is not a number.`);
}
if (typeof value === "string" &&
!aliasRegistry.has(value) &&
!(value in PRIMITIVE_SIZES)) {
throw new Error(`Choice type "${value}" is unkown.`);
}
}
return this.setNextParser("choice", varName, options);
}
nest(varName, options) {
if (typeof options !== "object" && typeof varName === "object") {
options = varName;
varName = "";
}
if (!options || !options.type) {
throw new Error("type is required for nest.");
}
if (!(options.type instanceof Parser) && !aliasRegistry.has(options.type)) {
throw new Error("type must be a known parser name or a Parser object.");
}
if (!(options.type instanceof Parser) && !varName) {
throw new Error("type must be a Parser object if the variable name is omitted.");
}
return this.setNextParser("nest", varName, options);
}
pointer(varName, options) {
if (!options.offset) {
throw new Error("offset is required for pointer.");
}
if (!options.type) {
throw new Error("type is required for pointer.");
}
if (typeof options.type === "string" &&
!(options.type in PRIMITIVE_SIZES) &&
!aliasRegistry.has(options.type)) {
throw new Error(`Pointer type "${options.type}" is unkown.`);
}
return this.setNextParser("pointer", varName, options);
}
saveOffset(varName, options = {}) {
return this.setNextParser("saveOffset", varName, options);
}
endianness(endianness) {
switch (endianness.toLowerCase()) {
case "little":
this.endian = "le";
break;
case "big":
this.endian = "be";
break;
default:
throw new Error('endianness must be one of "little" or "big"');
}
return this;
}
endianess(endianess) {
return this.endianness(endianess);
}
useContextVars(useContextVariables = true) {
this.useContextVariables = useContextVariables;
return this;
}
create(constructorFn) {
if (!(constructorFn instanceof Function)) {
throw new Error("Constructor must be a Function object.");
}
this.constructorFn = constructorFn;
return this;
}
getContext(importPath) {
const ctx = new Context(importPath, this.useContextVariables);
ctx.pushCode("var dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);");
if (!this.alias) {
this.addRawCode(ctx);
}
else {
this.addAliasedCode(ctx);
ctx.pushCode(`return ${FUNCTION_PREFIX + this.alias}(0).result;`);
}
return ctx;
}
getCode() {
const importPath = "imports";
return this.getContext(importPath).code;
}
addRawCode(ctx) {
ctx.pushCode("var offset = 0;");
ctx.pushCode(`var vars = ${this.constructorFn ? "new constructorFn()" : "{}"};`);
ctx.pushCode("vars.$parent = null;");
ctx.pushCode("vars.$root = vars;");
this.generate(ctx);
this.resolveReferences(ctx);
ctx.pushCode("delete vars.$parent;");
ctx.pushCode("delete vars.$root;");
ctx.pushCode("return vars;");
}
addAliasedCode(ctx) {
ctx.pushCode(`function ${FUNCTION_PREFIX + this.alias}(offset, context) {`);
ctx.pushCode(`var vars = ${this.constructorFn ? "new constructorFn()" : "{}"};`);
ctx.pushCode("var ctx = Object.assign({$parent: null, $root: vars}, context || {});");
ctx.pushCode(`vars = Object.assign(vars, ctx);`);
this.generate(ctx);
ctx.markResolved(this.alias);
this.resolveReferences(ctx);
ctx.pushCode("Object.keys(ctx).forEach(function (item) { delete vars[item]; });");
ctx.pushCode("return { offset: offset, result: vars };");
ctx.pushCode("}");
return ctx;
}
resolveReferences(ctx) {
const references = ctx.getUnresolvedReferences();
ctx.markRequested(references);
references.forEach((alias) => {
var _a;
(_a = aliasRegistry.get(alias)) === null || _a === void 0 ? void 0 : _a.addAliasedCode(ctx);
});
}
compile() {
const importPath = "imports";
const ctx = this.getContext(importPath);
this.compiled = new Function(importPath, "TextDecoder", `return function (buffer, constructorFn) { ${ctx.code} };`)(ctx.imports, TextDecoder);
}
sizeOf() {
let size = NaN;
if (Object.keys(PRIMITIVE_SIZES).indexOf(this.type) >= 0) {
size = PRIMITIVE_SIZES[this.type];
// if this is a fixed length string
}
else if (this.type === "string" &&
typeof this.options.length === "number") {
size = this.options.length;
// if this is a fixed length buffer
}
else if (this.type === "buffer" &&
typeof this.options.length === "number") {
size = this.options.length;
// if this is a fixed length array
}
else if (this.type === "array" &&
typeof this.options.length === "number") {
let elementSize = NaN;
if (typeof this.options.type === "string") {
elementSize = PRIMITIVE_SIZES[this.options.type];
}
else if (this.options.type instanceof Parser) {
elementSize = this.options.type.sizeOf();
}
size = this.options.length * elementSize;
// if this a skip
}
else if (this.type === "seek") {
size = this.options.length;
// if this is a nested parser
}
else if (this.type === "nest") {
size = this.options.type.sizeOf();
}
else if (!this.type) {
size = 0;
}
if (this.next) {
size += this.next.sizeOf();
}
return size;
}
// Follow the parser chain till the root and start parsing from there
parse(buffer) {
if (!this.compiled) {
this.compile();
}
return this.compiled(buffer, this.constructorFn);
}
setNextParser(type, varName, options) {
const parser = new Parser();
parser.type = type;
parser.varName = varName;
parser.options = options;
parser.endian = this.endian;
if (this.head) {
this.head.next = parser;
}
else {
this.next = parser;
}
this.head = parser;
return this;
}
// Call code generator for this parser
generate(ctx) {
if (this.type) {
switch (this.type) {
case "uint8":
case "uint16le":
case "uint16be":
case "uint32le":
case "uint32be":
case "int8":
case "int16le":
case "int16be":
case "int32le":
case "int32be":
case "int64be":
case "int64le":
case "uint64be":
case "uint64le":
case "floatle":
case "floatbe":
case "doublele":
case "doublebe":
this.primitiveGenerateN(this.type, ctx);
break;
case "bit":
this.generateBit(ctx);
break;
case "string":
this.generateString(ctx);
break;
case "buffer":
this.generateBuffer(ctx);
break;
case "seek":
this.generateSeek(ctx);
break;
case "nest":
this.generateNest(ctx);
break;
case "array":
this.generateArray(ctx);
break;
case "choice":
this.generateChoice(ctx);
break;
case "pointer":
this.generatePointer(ctx);
break;
case "saveOffset":
this.generateSaveOffset(ctx);
break;
case "wrapper":
this.generateWrapper(ctx);
break;
}
if (this.type !== "bit")
this.generateAssert(ctx);
}
const varName = ctx.generateVariable(this.varName);
if (this.options.formatter && this.type !== "bit") {
this.generateFormatter(ctx, varName, this.options.formatter);
}
return this.generateNext(ctx);
}
generateAssert(ctx) {
if (!this.options.assert) {
return;
}
const varName = ctx.generateVariable(this.varName);
switch (typeof this.options.assert) {
case "function":
{
const func = ctx.addImport(this.options.assert);
ctx.pushCode(`if (!${func}.call(vars, ${varName})) {`);
}
break;
case "number":
ctx.pushCode(`if (${this.options.assert} !== ${varName}) {`);
break;
case "string":
ctx.pushCode(`if (${JSON.stringify(this.options.assert)} !== ${varName}) {`);
break;
default:
throw new Error("assert option must be a string, number or a function.");
}
ctx.generateError(`"Assertion error: ${varName} is " + ${JSON.stringify(this.options.assert.toString())}`);
ctx.pushCode("}");
}
// Recursively call code generators and append results
generateNext(ctx) {
if (this.next) {
ctx = this.next.generate(ctx);
}
return ctx;
}
generateBit(ctx) {
// TODO find better method to handle nested bit fields
const parser = JSON.parse(JSON.stringify(this));
parser.options = this.options;
parser.generateAssert = this.generateAssert.bind(this);
parser.generateFormatter = this.generateFormatter.bind(this);
parser.varName = ctx.generateVariable(parser.varName);
ctx.bitFields.push(parser);
if (!this.next ||
(this.next && ["bit", "nest"].indexOf(this.next.type) < 0)) {
const val = ctx.generateTmpVariable();
ctx.pushCode(`var ${val} = 0;`);
const getMaxBits = (from = 0) => {
let sum = 0;
for (let i = from; i < ctx.bitFields.length; i++) {
const length = ctx.bitFields[i].options.length;
if (sum + length > 32)
break;
sum += length;
}
return sum;
};
const getBytes = (sum) => {
if (sum <= 8) {
ctx.pushCode(`${val} = dataView.getUint8(offset);`);
sum = 8;
}
else if (sum <= 16) {
ctx.pushCode(`${val} = dataView.getUint16(offset);`);
sum = 16;
}
else if (sum <= 24) {
ctx.pushCode(`${val} = (dataView.getUint16(offset) << 8) | dataView.getUint8(offset + 2);`);
sum = 24;
}
else {
ctx.pushCode(`${val} = dataView.getUint32(offset);`);
sum = 32;
}
ctx.pushCode(`offset += ${sum / 8};`);
return sum;
};
let bitOffset = 0;
const isBigEndian = this.endian === "be";
let sum = 0;
let rem = 0;
ctx.bitFields.forEach((parser, i) => {
let length = parser.options.length;
if (length > rem) {
if (rem) {
const mask = -1 >>> (32 - rem);
ctx.pushCode(`${parser.varName} = (${val} & 0x${mask.toString(16)}) << ${length - rem};`);
length -= rem;
}
bitOffset = 0;
rem = sum = getBytes(getMaxBits(i) - rem);
}
const offset = isBigEndian ? sum - bitOffset - length : bitOffset;
const mask = -1 >>> (32 - length);
ctx.pushCode(`${parser.varName} ${length < parser.options.length ? "|=" : "="} ${val} >> ${offset} & 0x${mask.toString(16)};`);
// Ensure value is unsigned
if (parser.options.length === 32) {
ctx.pushCode(`${parser.varName} >>>= 0`);
}
if (parser.options.assert) {
parser.generateAssert(ctx);
}
if (parser.options.formatter) {
parser.generateFormatter(ctx, parser.varName, parser.options.formatter);
}
bitOffset += length;
rem -= length;
});
ctx.bitFields = [];
}
}
generateSeek(ctx) {
const length = ctx.generateOption(this.options.length);
ctx.pushCode(`offset += ${length};`);
}
generateString(ctx) {
const name = ctx.generateVariable(this.varName);
const start = ctx.generateTmpVariable();
const encoding = this.options.encoding;
const isHex = encoding.toLowerCase() === "hex";
const toHex = 'b => b.toString(16).padStart(2, "0")';
if (this.options.length && this.options.zeroTerminated) {
const len = this.options.length;
ctx.pushCode(`var ${start} = offset;`);
ctx.pushCode(`while(dataView.getUint8(offset++) !== 0 && offset - ${start} < ${len});`);
const end = `offset - ${start} < ${len} ? offset - 1 : offset`;
ctx.pushCode(isHex
? `${name} = Array.from(buffer.subarray(${start}, ${end}), ${toHex}).join('');`
: `${name} = new TextDecoder('${encoding}').decode(buffer.subarray(${start}, ${end}));`);
}
else if (this.options.length) {
const len = ctx.generateOption(this.options.length);
ctx.pushCode(isHex
? `${name} = Array.from(buffer.subarray(offset, offset + ${len}), ${toHex}).join('');`
: `${name} = new TextDecoder('${encoding}').decode(buffer.subarray(offset, offset + ${len}));`);
ctx.pushCode(`offset += ${len};`);
}
else if (this.options.zeroTerminated) {
ctx.pushCode(`var ${start} = offset;`);
ctx.pushCode("while(dataView.getUint8(offset++) !== 0);");
ctx.pushCode(isHex
? `${name} = Array.from(buffer.subarray(${start}, offset - 1), ${toHex}).join('');`
: `${name} = new TextDecoder('${encoding}').decode(buffer.subarray(${start}, offset - 1));`);
}
else if (this.options.greedy) {
ctx.pushCode(`var ${start} = offset;`);
ctx.pushCode("while(buffer.length > offset++);");
ctx.pushCode(isHex
? `${name} = Array.from(buffer.subarray(${start}, offset), ${toHex}).join('');`
: `${name} = new TextDecoder('${encoding}').decode(buffer.subarray(${start}, offset));`);
}
if (this.options.stripNull) {
ctx.pushCode(`${name} = ${name}.replace(/\\x00+$/g, '')`);
}
}
generateBuffer(ctx) {
const varName = ctx.generateVariable(this.varName);
if (typeof this.options.readUntil === "function") {
const pred = this.options.readUntil;
const start = ctx.generateTmpVariable();
const cur = ctx.generateTmpVariable();
ctx.pushCode(`var ${start} = offset;`);
ctx.pushCode(`var ${cur} = 0;`);
ctx.pushCode(`while (offset < buffer.length) {`);
ctx.pushCode(`${cur} = dataView.getUint8(offset);`);
const func = ctx.addImport(pred);
ctx.pushCode(`if (${func}.call(${ctx.generateVariable()}, ${cur}, buffer.subarray(offset))) break;`);
ctx.pushCode(`offset += 1;`);
ctx.pushCode(`}`);
ctx.pushCode(`${varName} = buffer.subarray(${start}, offset);`);
}
else if (this.options.readUntil === "eof") {
ctx.pushCode(`${varName} = buffer.subarray(offset);`);
}
else {
const len = ctx.generateOption(this.options.length);
ctx.pushCode(`${varName} = buffer.subarray(offset, offset + ${len});`);
ctx.pushCode(`offset += ${len};`);
}
if (this.options.clone) {
ctx.pushCode(`${varName} = buffer.constructor.from(${varName});`);
}
}
generateArray(ctx) {
const length = ctx.generateOption(this.options.length);
const lengthInBytes = ctx.generateOption(this.options.lengthInBytes);
const type = this.options.type;
const counter = ctx.generateTmpVariable();
const lhs = ctx.generateVariable(this.varName);
const item = ctx.generateTmpVariable();
const key = this.options.key;
const isHash = typeof key === "string";
if (isHash) {
ctx.pushCode(`${lhs} = {};`);
}
else {
ctx.pushCode(`${lhs} = [];`);
}
if (typeof this.options.readUntil === "function") {
ctx.pushCode("do {");
}
else if (this.options.readUntil === "eof") {
ctx.pushCode(`for (var ${counter} = 0; offset < buffer.length; ${counter}++) {`);
}
else if (lengthInBytes !== undefined) {
ctx.pushCode(`for (var ${counter} = offset + ${lengthInBytes}; offset < ${counter}; ) {`);
}
else {
ctx.pushCode(`for (var ${counter} = ${length}; ${counter} > 0; ${counter}--) {`);
}
if (typeof type === "string") {
if (!aliasRegistry.get(type)) {
const typeName = PRIMITIVE_NAMES[type];
const littleEndian = PRIMITIVE_LITTLE_ENDIANS[type];
ctx.pushCode(`var ${item} = dataView.get${typeName}(offset, ${littleEndian});`);
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type]};`);
}
else {
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + type}(offset, {`);
if (ctx.useContextVariables) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`$parent: ${parentVar},`);
ctx.pushCode(`$root: ${parentVar}.$root,`);
if (!this.options.readUntil && lengthInBytes === undefined) {
ctx.pushCode(`$index: ${length} - ${counter},`);
}
}
ctx.pushCode(`});`);
ctx.pushCode(`var ${item} = ${tempVar}.result; offset = ${tempVar}.offset;`);
if (type !== this.alias)
ctx.addReference(type);
}
}
else if (type instanceof Parser) {
ctx.pushCode(`var ${item} = {};`);
const parentVar = ctx.generateVariable();
ctx.pushScope(item);
if (ctx.useContextVariables) {
ctx.pushCode(`${item}.$parent = ${parentVar};`);
ctx.pushCode(`${item}.$root = ${parentVar}.$root;`);
if (!this.options.readUntil && lengthInBytes === undefined) {
ctx.pushCode(`${item}.$index = ${length} - ${counter};`);
}
}
type.generate(ctx);
if (ctx.useContextVariables) {
ctx.pushCode(`delete ${item}.$parent;`);
ctx.pushCode(`delete ${item}.$root;`);
ctx.pushCode(`delete ${item}.$index;`);
}
ctx.popScope();
}
if (isHash) {
ctx.pushCode(`${lhs}[${item}.${key}] = ${item};`);
}
else {
ctx.pushCode(`${lhs}.push(${item});`);
}
ctx.pushCode("}");
if (typeof this.options.readUntil === "function") {
const pred = this.options.readUntil;
const func = ctx.addImport(pred);
ctx.pushCode(`while (!${func}.call(${ctx.generateVariable()}, ${item}, buffer.subarray(offset)));`);
}
}
generateChoiceCase(ctx, varName, type) {
if (typeof type === "string") {
const varName = ctx.generateVariable(this.varName);
if (!aliasRegistry.has(type)) {
const typeName = PRIMITIVE_NAMES[type];
const littleEndian = PRIMITIVE_LITTLE_ENDIANS[type];
ctx.pushCode(`${varName} = dataView.get${typeName}(offset, ${littleEndian});`);
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type]}`);
}
else {
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + type}(offset, {`);
if (ctx.useContextVariables) {
ctx.pushCode(`$parent: ${varName}.$parent,`);
ctx.pushCode(`$root: ${varName}.$root,`);
}
ctx.pushCode(`});`);
ctx.pushCode(`${varName} = ${tempVar}.result; offset = ${tempVar}.offset;`);
if (type !== this.alias)
ctx.addReference(type);
}
}
else if (type instanceof Parser) {
ctx.pushPath(varName);
type.generate(ctx);
ctx.popPath(varName);
}
}
generateChoice(ctx) {
const tag = ctx.generateOption(this.options.tag);
const nestVar = ctx.generateVariable(this.varName);
if (this.varName) {
ctx.pushCode(`${nestVar} = {};`);
if (ctx.useContextVariables) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`${nestVar}.$parent = ${parentVar};`);
ctx.pushCode(`${nestVar}.$root = ${parentVar}.$root;`);
}
}
ctx.pushCode(`switch(${tag}) {`);
for (const tagString in this.options.choices) {
const tag = parseInt(tagString, 10);
const type = this.options.choices[tag];
ctx.pushCode(`case ${tag}:`);
this.generateChoiceCase(ctx, this.varName, type);
ctx.pushCode("break;");
}
ctx.pushCode("default:");
if (this.options.defaultChoice) {
this.generateChoiceCase(ctx, this.varName, this.options.defaultChoice);
}
else {
ctx.generateError(`"Met undefined tag value " + ${tag} + " at choice"`);
}
ctx.pushCode("}");
if (this.varName && ctx.useContextVariables) {
ctx.pushCode(`delete ${nestVar}.$parent;`);
ctx.pushCode(`delete ${nestVar}.$root;`);
}
}
generateNest(ctx) {
const nestVar = ctx.generateVariable(this.varName);
if (this.options.type instanceof Parser) {
if (this.varName) {
ctx.pushCode(`${nestVar} = {};`);
if (ctx.useContextVariables) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`${nestVar}.$parent = ${parentVar};`);
ctx.pushCode(`${nestVar}.$root = ${parentVar}.$root;`);
}
}
ctx.pushPath(this.varName);
this.options.type.generate(ctx);
ctx.popPath(this.varName);
if (this.varName && ctx.useContextVariables) {
if (ctx.useContextVariables) {
ctx.pushCode(`delete ${nestVar}.$parent;`);
ctx.pushCode(`delete ${nestVar}.$root;`);
}
}
}
else if (aliasRegistry.has(this.options.type)) {
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(offset, {`);
if (ctx.useContextVariables) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`$parent: ${parentVar},`);
ctx.pushCode(`$root: ${parentVar}.$root,`);
}
ctx.pushCode(`});`);
ctx.pushCode(`${nestVar} = ${tempVar}.result; offset = ${tempVar}.offset;`);
if (this.options.type !== this.alias) {
ctx.addReference(this.options.type);
}
}
}
generateWrapper(ctx) {
const wrapperVar = ctx.generateVariable(this.varName);
const wrappedBuf = ctx.generateTmpVariable();
if (typeof this.options.readUntil === "function") {
const pred = this.options.readUntil;
const start = ctx.generateTmpVariable();
const cur = ctx.generateTmpVariable();
ctx.pushCode(`var ${start} = offset;`);
ctx.pushCode(`var ${cur} = 0;`);
ctx.pushCode(`while (offset < buffer.length) {`);
ctx.pushCode(`${cur} = dataView.getUint8(offset);`);
const func = ctx.addImport(pred);
ctx.pushCode(`if (${func}.call(${ctx.generateVariable()}, ${cur}, buffer.subarray(offset))) break;`);
ctx.pushCode(`offset += 1;`);
ctx.pushCode(`}`);
ctx.pushCode(`${wrappedBuf} = buffer.subarray(${start}, offset);`);
}
else if (this.options.readUntil === "eof") {
ctx.pushCode(`${wrappedBuf} = buffer.subarray(offset);`);
}
else {
const len = ctx.generateOption(this.options.length);
ctx.pushCode(`${wrappedBuf} = buffer.subarray(offset, offset + ${len});`);
ctx.pushCode(`offset += ${len};`);
}
if (this.options.clone) {
ctx.pushCode(`${wrappedBuf} = buffer.constructor.from(${wrappedBuf});`);
}
const tempBuf = ctx.generateTmpVariable();
const tempOff = ctx.generateTmpVariable();
const tempView = ctx.generateTmpVariable();
const func = ctx.addImport(this.options.wrapper);
ctx.pushCode(`${wrappedBuf} = ${func}.call(this, ${wrappedBuf}).subarray(0);`);
ctx.pushCode(`var ${tempBuf} = buffer;`);
ctx.pushCode(`var ${tempOff} = offset;`);
ctx.pushCode(`var ${tempView} = dataView;`);
ctx.pushCode(`buffer = ${wrappedBuf};`);
ctx.pushCode(`offset = 0;`);
ctx.pushCode(`dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);`);
if (this.options.type instanceof Parser) {
if (this.varName) {
ctx.pushCode(`${wrapperVar} = {};`);
}
ctx.pushPath(this.varName);
this.options.type.generate(ctx);
ctx.popPath(this.varName);
}
else if (aliasRegistry.has(this.options.type)) {
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(0);`);
ctx.pushCode(`${wrapperVar} = ${tempVar}.result;`);
if (this.options.type !== this.alias) {
ctx.addReference(this.options.type);
}
}
ctx.pushCode(`buffer = ${tempBuf};`);
ctx.pushCode(`dataView = ${tempView};`);
ctx.pushCode(`offset = ${tempOff};`);
}
generateFormatter(ctx, varName, formatter) {
if (typeof formatter === "function") {
const func = ctx.addImport(formatter);
ctx.pushCode(`${varName} = ${func}.call(${ctx.generateVariable()}, ${varName});`);
}
}
generatePointer(ctx) {
const type = this.options.type;
const offset = ctx.generateOption(this.options.offset);
const tempVar = ctx.generateTmpVariable();
const nestVar = ctx.generateVariable(this.varName);
// Save current offset
ctx.pushCode(`var ${tempVar} = offset;`);
// Move offset
ctx.pushCode(`offset = ${offset};`);
if (this.options.type instanceof Parser) {
ctx.pushCode(`${nestVar} = {};`);
if (ctx.useContextVariables) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`${nestVar}.$parent = ${parentVar};`);
ctx.pushCode(`${nestVar}.$root = ${parentVar}.$root;`);
}
ctx.pushPath(this.varName);
this.options.type.generate(ctx);
ctx.popPath(this.varName);
if (ctx.useContextVariables) {
ctx.pushCode(`delete ${nestVar}.$parent;`);
ctx.pushCode(`delete ${nestVar}.$root;`);
}
}
else if (aliasRegistry.has(this.options.type)) {
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(offset, {`);
if (ctx.useContextVariables) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`$parent: ${parentVar},`);
ctx.pushCode(`$root: ${parentVar}.$root,`);
}
ctx.pushCode(`});`);
ctx.pushCode(`${nestVar} = ${tempVar}.result; offset = ${tempVar}.offset;`);
if (this.options.type !== this.alias) {
ctx.addReference(this.options.type);
}
}
else if (Object.keys(PRIMITIVE_SIZES).indexOf(this.options.type) >= 0) {
const typeName = PRIMITIVE_NAMES[type];
const littleEndian = PRIMITIVE_LITTLE_ENDIANS[type];
ctx.pushCode(`${nestVar} = dataView.get${typeName}(offset, ${littleEndian});`);
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type]};`);
}
// Restore offset
ctx.pushCode(`offset = ${tempVar};`);
}
generateSaveOffset(ctx) {
const varName = ctx.generateVariable(this.varName);
ctx.pushCode(`${varName} = offset`);
}
}
exports.Parser = Parser;
//# sourceMappingURL=binary_parser.js.map