Skip to content

Commit fafd573

Browse files
authored
Merge pull request #169 from Rzial/feature/index-context-variable
Index parsing context variable
2 parents d826b26 + 7c46a82 commit fafd573

File tree

3 files changed

+177
-50
lines changed

3 files changed

+177
-50
lines changed

README.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,8 @@ You can use some special fields while parsing to traverse your structure. These
524524
context variables will be removed after the parsing process:
525525
- `$parent` - This field references the parent structure. This variable will be
526526
`null` while parsing the root structure.
527-
```js
527+
528+
```javascript
528529
var parser = new Parser()
529530
.nest("header", {
530531
type: new Parser().uint32("length"),
@@ -534,10 +535,12 @@ context variables will be removed after the parsing process:
534535
length: function() {
535536
return this.$parent.header.length
536537
}
537-
})
538+
});
538539
```
540+
539541
- `$root` - This field references the root structure.
540-
```js
542+
543+
```javascript
541544
var parser = new Parser()
542545
.nest("header", {
543546
type: new Parser().uint32("length"),
@@ -551,8 +554,30 @@ context variables will be removed after the parsing process:
551554
return this.$root.header.length
552555
}
553556
}),
554-
})
557+
});
558+
```
555559

560+
- `$index` - This field references the actual index in array parsing. This
561+
variable will be available only when using the `length` mode for arrays.
562+
563+
```javascript
564+
var parser = new Parser()
565+
.nest("header", {
566+
type: new Parser().uint32("length"),
567+
})
568+
.nest("data", {
569+
type: new Parser()
570+
.uint32("value")
571+
.array("data", {
572+
type: new Parser().nest({
573+
type: new Parser().uint8("_tmp"),
574+
formatter: function(item) {
575+
return this.$index % 2 === 0 ? item._tmp : String.fromCharCode(item._tmp);
576+
}
577+
}),
578+
length: "$root.header.length"
579+
}),
580+
});
556581
```
557582

558583
## Examples

lib/binary_parser.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ interface ParserOptions {
99
assert?: number | string | ((item: number | string) => boolean);
1010
lengthInBytes?: number | string | ((item: any) => number);
1111
type?: string | Parser;
12-
formatter?: (item: any) => string | number;
12+
formatter?: (item: any) => any;
1313
encoding?: string;
1414
readUntil?: 'eof' | ((item: any, buffer: Buffer) => boolean);
1515
greedy?: boolean;
@@ -686,22 +686,23 @@ export class Parser {
686686
}
687687

688688
private addAliasedCode(ctx: Context) {
689+
ctx.pushCode(`function ${FUNCTION_PREFIX + this.alias}(offset, context) {`);
689690
ctx.pushCode(
690-
`function ${FUNCTION_PREFIX + this.alias}(offset, parent, root) {`
691+
`var vars = ${this.constructorFn ? 'new constructorFn()' : '{}'};`
691692
);
692693
ctx.pushCode(
693-
`var vars = ${this.constructorFn ? 'new constructorFn()' : '{}'};`
694+
`var ctx = Object.assign({$parent: null, $root: vars}, context || {})`
694695
);
695-
ctx.pushCode('vars.$parent = parent || null;');
696-
ctx.pushCode('vars.$root = root || vars;');
696+
ctx.pushCode(`vars = Object.assign(vars, ctx)`);
697697

698698
this.generate(ctx);
699699

700700
ctx.markResolved(this.alias);
701701
this.resolveReferences(ctx);
702702

703-
ctx.pushCode('delete vars.$parent;');
704-
ctx.pushCode('delete vars.$root;');
703+
ctx.pushCode(
704+
'Object.keys(ctx).forEach(function (item) { delete vars[item]; });'
705+
);
705706
ctx.pushCode('return { offset: offset, result: vars };');
706707
ctx.pushCode('}');
707708

@@ -1094,11 +1095,13 @@ export class Parser {
10941095
} else {
10951096
const parentVar = ctx.generateVariable();
10961097
const tempVar = ctx.generateTmpVariable();
1097-
ctx.pushCode(
1098-
`var ${tempVar} = ${
1099-
FUNCTION_PREFIX + type
1100-
}(offset, ${parentVar}, ${parentVar}.$root);`
1101-
);
1098+
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + type}(offset, {`);
1099+
ctx.pushCode(`$parent: ${parentVar},`);
1100+
ctx.pushCode(`$root: ${parentVar}.$root,`);
1101+
if (!this.options.readUntil && lengthInBytes === undefined) {
1102+
ctx.pushCode(`$index: ${length} - ${counter},`);
1103+
}
1104+
ctx.pushCode(`});`);
11021105
ctx.pushCode(
11031106
`var ${item} = ${tempVar}.result; offset = ${tempVar}.offset;`
11041107
);
@@ -1111,9 +1114,13 @@ export class Parser {
11111114
ctx.pushScope(item);
11121115
ctx.pushCode(`${item}.$parent = ${parentVar};`);
11131116
ctx.pushCode(`${item}.$root = ${parentVar}.$root;`);
1117+
if (!this.options.readUntil && lengthInBytes === undefined) {
1118+
ctx.pushCode(`${item}.$index = ${length} - ${counter},`);
1119+
}
11141120
type.generate(ctx);
11151121
ctx.pushCode(`delete ${item}.$parent`);
11161122
ctx.pushCode(`delete ${item}.$root`);
1123+
ctx.pushCode(`delete ${item}.$index`);
11171124
ctx.popScope();
11181125
}
11191126

@@ -1150,11 +1157,10 @@ export class Parser {
11501157
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type as PrimitiveTypes]}`);
11511158
} else {
11521159
const tempVar = ctx.generateTmpVariable();
1153-
ctx.pushCode(
1154-
`var ${tempVar} = ${
1155-
FUNCTION_PREFIX + type
1156-
}(offset, ${varName}.$parent, ${varName}.$root);`
1157-
);
1160+
ctx.pushCode(`var ${tempVar} = ${FUNCTION_PREFIX + type}(offset, {`);
1161+
ctx.pushCode(`$parent: ${varName}.$parent,`);
1162+
ctx.pushCode(`$root: ${varName}.$root,`);
1163+
ctx.pushCode(`});`);
11581164
ctx.pushCode(
11591165
`${varName} = ${tempVar}.result; offset = ${tempVar}.offset;`
11601166
);
@@ -1221,10 +1227,11 @@ export class Parser {
12211227
const parentVar = ctx.generateVariable();
12221228
const tempVar = ctx.generateTmpVariable();
12231229
ctx.pushCode(
1224-
`var ${tempVar} = ${
1225-
FUNCTION_PREFIX + this.options.type
1226-
}(offset, ${parentVar}, ${parentVar}.$root);`
1230+
`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(offset, {`
12271231
);
1232+
ctx.pushCode(`$parent: ${parentVar},`);
1233+
ctx.pushCode(`$root: ${parentVar}.$root,`);
1234+
ctx.pushCode(`});`);
12281235
ctx.pushCode(
12291236
`${nestVar} = ${tempVar}.result; offset = ${tempVar}.offset;`
12301237
);
@@ -1337,10 +1344,11 @@ export class Parser {
13371344
const parentVar = ctx.generateVariable();
13381345
const tempVar = ctx.generateTmpVariable();
13391346
ctx.pushCode(
1340-
`var ${tempVar} = ${
1341-
FUNCTION_PREFIX + this.options.type
1342-
}(offset, ${parentVar}, ${parentVar}.$root);`
1347+
`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(offset, {`
13431348
);
1349+
ctx.pushCode(`$parent: ${parentVar},`);
1350+
ctx.pushCode(`$root: ${parentVar}.$root,`);
1351+
ctx.pushCode(`});`);
13441352
ctx.pushCode(
13451353
`${nestVar} = ${tempVar}.result; offset = ${tempVar}.offset;`
13461354
);

test/composite_parser.js

Lines changed: 117 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const suite = (Buffer) =>
1111
hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
1212
);
1313
}
14+
1415
describe('Array parser', function () {
1516
it('should parse array of primitive types', function () {
1617
var parser = Parser.start().uint8('length').array('message', {
@@ -64,16 +65,19 @@ const suite = (Buffer) =>
6465
});
6566
it('should parse array of user defined types and have access to parent context', function () {
6667
var elementParser = new Parser().uint8('key').array('value', {
67-
type: "uint8",
68+
type: 'uint8',
6869
length: function () {
6970
return this.$parent.valueLength;
70-
}
71+
},
7172
});
7273

73-
var parser = Parser.start().uint16le('length').uint16le('valueLength').array('message', {
74-
length: 'length',
75-
type: elementParser,
76-
});
74+
var parser = Parser.start()
75+
.uint16le('length')
76+
.uint16le('valueLength')
77+
.array('message', {
78+
length: 'length',
79+
type: elementParser,
80+
});
7781

7882
var buffer = Buffer.from([
7983
0x02,
@@ -97,17 +101,20 @@ const suite = (Buffer) =>
97101
});
98102
});
99103
it('should parse array of user defined types and have access to root context', function () {
100-
var elementParser = new Parser().uint8('key').nest("data", {
104+
var elementParser = new Parser().uint8('key').nest('data', {
101105
type: new Parser().array('value', {
102-
type: "uint8",
103-
length: "$root.valueLength"
104-
})
106+
type: 'uint8',
107+
length: '$root.valueLength',
108+
}),
105109
});
106110

107-
var parser = Parser.start().uint16le('length').uint16le('valueLength').array('message', {
108-
length: 'length',
109-
type: elementParser,
110-
});
111+
var parser = Parser.start()
112+
.uint16le('length')
113+
.uint16le('valueLength')
114+
.array('message', {
115+
length: 'length',
116+
type: elementParser,
117+
});
111118

112119
var buffer = Buffer.from([
113120
0x02,
@@ -125,8 +132,8 @@ const suite = (Buffer) =>
125132
length: 0x02,
126133
valueLength: 0x02,
127134
message: [
128-
{ key: 0xca, data: {value: [0xd2, 0x04]} },
129-
{ key: 0xbe, data: {value: [0xd3, 0x04]} },
135+
{ key: 0xca, data: { value: [0xd2, 0x04] } },
136+
{ key: 0xbe, data: { value: [0xd3, 0x04] } },
130137
],
131138
});
132139
});
@@ -474,7 +481,6 @@ const suite = (Buffer) =>
474481
],
475482
});
476483
});
477-
478484
it('should allow parent parser attributes as choice key', function () {
479485
var ChildParser = Parser.start().choice('data', {
480486
tag: function (vars) {
@@ -502,6 +508,93 @@ const suite = (Buffer) =>
502508
child: { data: { v2: 0x0304 } },
503509
});
504510
});
511+
it('should be able to access to index context variable when using length', function () {
512+
var elementParser = new Parser()
513+
.uint8('key', {
514+
formatter: function (item) {
515+
return this.$index % 2 === 0 ? item : String.fromCharCode(item);
516+
},
517+
})
518+
.nest('data', {
519+
type: new Parser().array('value', {
520+
type: 'uint8',
521+
length: '$root.valueLength',
522+
}),
523+
});
524+
525+
var parser = Parser.start()
526+
.uint16le('length')
527+
.uint16le('valueLength')
528+
.array('message', {
529+
length: 'length',
530+
type: elementParser,
531+
});
532+
533+
var buffer = Buffer.from([
534+
0x02,
535+
0x00,
536+
0x02,
537+
0x00,
538+
0x50,
539+
0xd2,
540+
0x04,
541+
0x51,
542+
0xd3,
543+
0x04,
544+
]);
545+
assert.deepStrictEqual(parser.parse(buffer), {
546+
length: 0x02,
547+
valueLength: 0x02,
548+
message: [
549+
{ key: 0x50, data: { value: [0xd2, 0x04] } },
550+
{ key: 'Q', data: { value: [0xd3, 0x04] } },
551+
],
552+
});
553+
});
554+
it('should be able to access to index context variable when using length on named parser', function () {
555+
var elementParser = new Parser()
556+
.uint8('key', {
557+
formatter: function (item) {
558+
return this.$index % 2 === 0 ? item : String.fromCharCode(item);
559+
},
560+
})
561+
.nest('data', {
562+
type: new Parser().array('value', {
563+
type: 'uint8',
564+
length: '$root.valueLength',
565+
}),
566+
})
567+
.namely('ArrayLengthIndexTest');
568+
569+
var parser = Parser.start()
570+
.uint16le('length')
571+
.uint16le('valueLength')
572+
.array('message', {
573+
length: 'length',
574+
type: 'ArrayLengthIndexTest',
575+
});
576+
577+
var buffer = Buffer.from([
578+
0x02,
579+
0x00,
580+
0x02,
581+
0x00,
582+
0x50,
583+
0xd2,
584+
0x04,
585+
0x51,
586+
0xd3,
587+
0x04,
588+
]);
589+
assert.deepStrictEqual(parser.parse(buffer), {
590+
length: 0x02,
591+
valueLength: 0x02,
592+
message: [
593+
{ key: 0x50, data: { value: [0xd2, 0x04] } },
594+
{ key: 'Q', data: { value: [0xd3, 0x04] } },
595+
],
596+
});
597+
});
505598
});
506599

507600
describe('Choice parser', function () {
@@ -924,7 +1017,7 @@ const suite = (Buffer) =>
9241017
1: Parser.start()
9251018
.uint8('length')
9261019
.string('message', { length: 'length' })
927-
.array('value', { type: "uint8", length: "$parent.items"}),
1020+
.array('value', { type: 'uint8', length: '$parent.items' }),
9281021
3: Parser.start().int32le('number'),
9291022
},
9301023
});
@@ -1050,11 +1143,11 @@ const suite = (Buffer) =>
10501143
var parser = Parser.start()
10511144
.uint8('items')
10521145
.nest('data', {
1053-
type: Parser.start()
1054-
.uint8('length')
1055-
.string('message', { length: 'length' })
1056-
.array('value', { type: "uint8", length: "$parent.items"}),
1057-
});
1146+
type: Parser.start()
1147+
.uint8('length')
1148+
.string('message', { length: 'length' })
1149+
.array('value', { type: 'uint8', length: '$parent.items' }),
1150+
});
10581151

10591152
var buffer = Buffer.from([
10601153
0x2,
@@ -1092,6 +1185,7 @@ const suite = (Buffer) =>
10921185
function Person() {
10931186
this.name = '';
10941187
}
1188+
10951189
Person.prototype.toString = function () {
10961190
return '[object Person]';
10971191
};

0 commit comments

Comments
 (0)