Skip to content

Added parsing context variables #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,10 @@ parser.parse(buffer);
```

### wrapped(name[, options])
Read data then wrap it by transforming it by a function for further parsing.
It works similarly to a buffer where it reads a block of data. But instead of returning the buffer it
Read data then wrap it by transforming it by a function for further parsing.
It works similarly to a buffer where it reads a block of data. But instead of returning the buffer it
will pass it on to a parser for further processing.
- `wrapper` - (Required) A function taking a buffer and returning a buffer (`(x: Buffer | Uint8Array ) => Buffer | Uint8Array`)
- `wrapper` - (Required) A function taking a buffer and returning a buffer (`(x: Buffer | Uint8Array ) => Buffer | Uint8Array`)
transforming the buffer into a buffer expected by `type`.
- `type` - (Required) A `Parser` object to parse the result of wrapper.
- `length ` - (either `length` or `readUntil` is required) Length of the
Expand All @@ -455,11 +455,11 @@ var textParser = Parser.start()
var mainParser = Parser.start()
// Read length of the data to wrap
.uint32le('length')
// Read wrapped data
// Read wrapped data
.wrapped('wrappedData', {
// Indicate how much data to read, like buffer()
length: 'length',
// Define function to pre-process the data buffer
// Define function to pre-process the data buffer
wrapper: function (buffer) {
// E.g. decompress data and return it for further parsing
return zlib.inflateRawSync(buffer);
Expand Down Expand Up @@ -519,6 +519,42 @@ These options can be used in all parsers.
});
```

### Context variables
You can use some special fields while parsing to traverse your structure. These
context variables will be removed after the parsing process:
- `$parent` - This field references the parent structure. This variable will be
`null` while parsing the root structure.
```js
var parser = new Parser()
.nest("header", {
type: new Parser().uint32("length"),
})
.array("data", {
type: "int32",
length: function() {
return this.$parent.header.length
}
})
```
- `$root` - This field references the root structure.
```js
var parser = new Parser()
.nest("header", {
type: new Parser().uint32("length"),
})
.nest("data", {
type: new Parser()
.uint32("value")
.array("data", {
type: "int32",
length: function() {
return this.$root.header.length
}
}),
})

```

## Examples
See `example/` for real-world examples.

Expand Down
89 changes: 68 additions & 21 deletions lib/binary_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,11 @@ export class Parser {
return this;
}

skip(length: number, options?: ParserOptions) {
skip(length: ParserOptions['length'], options?: ParserOptions) {
return this.seek(length, options);
}

seek(relOffset: number, options?: ParserOptions) {
seek(relOffset: ParserOptions['length'], options?: ParserOptions) {
if (options && options.assert) {
throw new Error('assert option on seek is not allowed.');
}
Expand Down Expand Up @@ -571,7 +571,7 @@ export class Parser {
return this.setNextParser('choice', varName as string, options);
}

nest(varName: string | ParserOptions, options: ParserOptions) {
nest(varName: string | ParserOptions, options?: ParserOptions) {
if (typeof options !== 'object' && typeof varName === 'object') {
options = varName;
varName = null;
Expand Down Expand Up @@ -670,34 +670,38 @@ export class Parser {

private addRawCode(ctx: Context) {
ctx.pushCode('var offset = 0;');

if (this.constructorFn) {
ctx.pushCode('var vars = new constructorFn();');
} else {
ctx.pushCode('var vars = {};');
}
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;');
}

private addAliasedCode(ctx: Context) {
ctx.pushCode(`function ${FUNCTION_PREFIX + this.alias}(offset) {`);

if (this.constructorFn) {
ctx.pushCode('var vars = new constructorFn();');
} else {
ctx.pushCode('var vars = {};');
}
ctx.pushCode(
`function ${FUNCTION_PREFIX + this.alias}(offset, parent, root) {`
);
ctx.pushCode(
`var vars = ${this.constructorFn ? 'new constructorFn()' : '{}'};`
);
ctx.pushCode('vars.$parent = parent || null;');
ctx.pushCode('vars.$root = root || vars;');

this.generate(ctx);

ctx.markResolved(this.alias);
this.resolveReferences(ctx);

ctx.pushCode('delete vars.$parent;');
ctx.pushCode('delete vars.$root;');
ctx.pushCode('return { offset: offset, result: vars };');
ctx.pushCode('}');

Expand Down Expand Up @@ -1088,18 +1092,28 @@ export class Parser {
);
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type as PrimitiveTypes]};`);
} else {
const parentVar = ctx.generateVariable();
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + type}(offset);`);
ctx.pushCode(
`var ${tempVar} = ${
FUNCTION_PREFIX + type
}(offset, ${parentVar}, ${parentVar}.$root);`
);
ctx.pushCode(
`var ${item} = ${tempVar}.result; offset = ${tempVar}.offset;`
);
if (type !== this.alias) ctx.addReference(type);
}
} else if (type instanceof Parser) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`var ${item} = {};`);

ctx.pushScope(item);
ctx.pushCode(`${item}.$parent = ${parentVar};`);
ctx.pushCode(`${item}.$root = ${parentVar}.$root;`);
type.generate(ctx);
ctx.pushCode(`delete ${item}.$parent`);
ctx.pushCode(`delete ${item}.$root`);
ctx.popScope();
}

Expand Down Expand Up @@ -1136,7 +1150,11 @@ export class Parser {
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type as PrimitiveTypes]}`);
} else {
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + type}(offset);`);
ctx.pushCode(
`var ${tempVar} = ${
FUNCTION_PREFIX + type
}(offset, ${varName}.$parent, ${varName}.$root);`
);
ctx.pushCode(
`${varName} = ${tempVar}.result; offset = ${tempVar}.offset;`
);
Expand All @@ -1151,8 +1169,14 @@ export class Parser {

private generateChoice(ctx: Context) {
const tag = ctx.generateOption(this.options.tag);
const nestVar = ctx.generateVariable(this.varName);

if (this.varName) {
ctx.pushCode(`${ctx.generateVariable(this.varName)} = {};`);
ctx.pushCode(`${nestVar} = {};`);

const parentVar = ctx.generateVariable();
ctx.pushCode(`${nestVar}.$parent = ${parentVar};`);
ctx.pushCode(`${nestVar}.$root = ${parentVar}.$root;`);
}
ctx.pushCode(`switch(${tag}) {`);
Object.keys(this.options.choices).forEach((tag) => {
Expand All @@ -1169,22 +1193,37 @@ export class Parser {
ctx.generateError(`"Met undefined tag value " + ${tag} + " at choice"`);
}
ctx.pushCode('}');

if (this.varName) {
ctx.pushCode(`delete ${nestVar}.$parent`);
ctx.pushCode(`delete ${nestVar}.$root`);
}
}

private generateNest(ctx: Context) {
const nestVar = ctx.generateVariable(this.varName);

if (this.options.type instanceof Parser) {
if (this.varName) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`${nestVar} = {};`);
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.pushCode(`delete ${nestVar}.$parent`);
ctx.pushCode(`delete ${nestVar}.$root`);
}
} else if (aliasRegistry[this.options.type]) {
const parentVar = ctx.generateVariable();
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(
`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(offset);`
`var ${tempVar} = ${
FUNCTION_PREFIX + this.options.type
}(offset, ${parentVar}, ${parentVar}.$root);`
);
ctx.pushCode(
`${nestVar} = ${tempVar}.result; offset = ${tempVar}.offset;`
Expand Down Expand Up @@ -1283,14 +1322,22 @@ export class Parser {
ctx.pushCode(`offset = ${offset};`);

if (this.options.type instanceof Parser) {
const parentVar = ctx.generateVariable();
ctx.pushCode(`${nestVar} = {};`);
ctx.pushCode(`${nestVar}.$parent = ${parentVar};`);
ctx.pushCode(`${nestVar}.$root = ${parentVar}.$root;`);
ctx.pushPath(this.varName);
this.options.type.generate(ctx);
ctx.popPath(this.varName);
ctx.pushCode(`delete ${nestVar}.$parent`);
ctx.pushCode(`delete ${nestVar}.$root`);
} else if (aliasRegistry[this.options.type]) {
const parentVar = ctx.generateVariable();
const tempVar = ctx.generateTmpVariable();
ctx.pushCode(
`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(offset);`
`var ${tempVar} = ${
FUNCTION_PREFIX + this.options.type
}(offset, ${parentVar}, ${parentVar}.$root);`
);
ctx.pushCode(
`${nestVar} = ${tempVar}.result; offset = ${tempVar}.offset;`
Expand Down
Loading