diff --git a/package-lock.json b/package-lock.json index 0b34b2d..e7ba096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -280,9 +280,8 @@ } }, "llparse-builder": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/llparse-builder/-/llparse-builder-1.3.2.tgz", - "integrity": "sha512-0l2slf0O7TpzR+uWsENSCbMv+kdAgLC9eBuaLZYpXKYGg5bD4o4xX9MRKw8a51Prce+HvkpEWpR+8d/VYGpQpg==", + "version": "git+https://github.com/arthurschreiber/llparse-builder.git#591065e9eb043a068547f35c951b533e91a58b99", + "from": "git+https://github.com/arthurschreiber/llparse-builder.git#arthur/binary-parsing", "requires": { "@types/debug": "0.0.30", "binary-search": "^1.3.3", diff --git a/package.json b/package.json index c2b010e..9f1a05d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "homepage": "https://github.com/indutny/llparse-frontend#readme", "dependencies": { "debug": "^3.2.6", - "llparse-builder": "^1.3.2" + "llparse-builder": "git+https://github.com/arthurschreiber/llparse-builder.git#arthur/binary-parsing" }, "devDependencies": { "@types/debug": "0.0.30", diff --git a/src/container/index.ts b/src/container/index.ts index 5087d62..64b0936 100644 --- a/src/container/index.ts +++ b/src/container/index.ts @@ -45,6 +45,7 @@ export class Container { Consume: this.combine((impl) => impl.node.Consume), Empty: this.combine((impl) => impl.node.Empty), Error: this.combine((impl) => impl.node.Error), + Int: this.combine((impl) => impl.node.Int), Invoke: this.combine((impl) => impl.node.Invoke), Pause: this.combine((impl) => impl.node.Pause), Sequence: this.combine((impl) => impl.node.Sequence), diff --git a/src/frontend.ts b/src/frontend.ts index fa17fb9..95fe117 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -163,6 +163,8 @@ export class Frontend { } else if (node instanceof source.node.Consume) { result = new nodeImpl.Consume( new frontend.node.Consume(id(), node.field)); + } else if (node instanceof source.node.Int) { + result = this.translateInt(node); } else if (node instanceof source.node.SpanStart) { result = new nodeImpl.SpanStart( new frontend.node.SpanStart(id(), this.spanMap.get(node.span)!, @@ -184,27 +186,37 @@ export class Frontend { // Initialize result const otherwise = node.getOtherwiseEdge(); - if (Array.isArray(result)) { - assert(node instanceof source.node.Match); - const match = node as source.node.Match; + assert(node instanceof source.node.Match || node instanceof source.node.Int); - // TODO(indutny): move this to llparse-builder? - assert.notStrictEqual(otherwise, undefined, - `Node "${node.name}" has no \`.otherwise()\``); + if (node instanceof source.node.Match) { + // TODO(indutny): move this to llparse-builder? + assert.notStrictEqual(otherwise, undefined, + `Node "${node.name}" has no \`.otherwise()\``); - // Assign otherwise to every node of Trie - if (otherwise !== undefined) { + // Assign otherwise to every node of Trie + if (otherwise !== undefined) { + for (const child of result) { + child.ref.setOtherwise(this.translate(otherwise.node), + otherwise.noAdvance); + } + } + + // Assign transform to every node of Trie + const transform = this.translateTransform(node.getTransform()); for (const child of result) { - child.ref.setOtherwise(this.translate(otherwise.node), - otherwise.noAdvance); + child.ref.setTransform(transform); } - } + } else if (node instanceof source.node.Int) { + // TODO(indutny): move this to llparse-builder? + assert.notStrictEqual(otherwise, undefined, + `Node "${node.name}" has no \`.otherwise()\``); - // Assign transform to every node of Trie - const transform = this.translateTransform(match.getTransform()); - for (const child of result) { - child.ref.setTransform(transform); + // Assign otherwise to last node of int expansion + if (otherwise !== undefined) { + result[result.length - 1].ref.setOtherwise(this.translate(otherwise.node), + otherwise.noAdvance) + } } assert(result.length >= 1); @@ -256,6 +268,28 @@ export class Frontend { } } + private translateInt(node: source.node.Int) : ReadonlyArray> { + const { name, field, bytes, signed, littleEndian } = node; + + const inner = new frontend.node.Int(this.id.id(name), field, bytes, signed, littleEndian, 0); + let result = [ new this.implementation.node.Int(inner) ]; + + // Break loops + this.map.set(node, result[0]); + + let next = result[0]; + for (let offset = 1; offset < node.bytes; offset++) { + const uniqueName = this.id.id(`${name}_byte${offset + 1}`); + const inner = new frontend.node.Int(uniqueName, field, bytes, signed, littleEndian, offset); + result[offset] = new this.implementation.node.Int(inner); + + next.ref.setOtherwise(result[offset], false); + next = result[offset]; + } + + return result; + } + private translateMatch(node: source.node.Match): MatchResult { const trie = new Trie(node.name); diff --git a/src/implementation/node.ts b/src/implementation/node.ts index af0b3df..9b50876 100644 --- a/src/implementation/node.ts +++ b/src/implementation/node.ts @@ -5,6 +5,7 @@ export interface INodeImplementation { readonly Consume: new(n: node.Consume) => IWrap; readonly Empty: new(n: node.Empty) => IWrap; readonly Error: new(n: node.Error) => IWrap; + readonly Int: new(n: node.Int) => IWrap; readonly Invoke: new(n: node.Invoke) => IWrap; readonly Pause: new(n: node.Pause) => IWrap; readonly Sequence: new(n: node.Sequence) => IWrap; diff --git a/src/node/index.ts b/src/node/index.ts index bd11015..bfebd65 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -2,6 +2,7 @@ export * from './base'; export * from './consume'; export * from './empty'; export * from './error'; +export * from './int'; export * from './invoke'; export * from './match'; export * from './pause'; diff --git a/src/node/int.ts b/src/node/int.ts new file mode 100644 index 0000000..f75af7a --- /dev/null +++ b/src/node/int.ts @@ -0,0 +1,8 @@ +import { IUniqueName } from '../utils'; +import { Node } from './base'; + +export class Int extends Node { + constructor(id: IUniqueName, readonly field: string, public readonly bytes: number, public readonly signed: boolean, public readonly littleEndian: boolean, public readonly byteOffset: number) { + super(id); + } +} diff --git a/test/fixtures/a-implementation/node/byte.ts b/test/fixtures/a-implementation/node/byte.ts new file mode 100644 index 0000000..55e9ec5 --- /dev/null +++ b/test/fixtures/a-implementation/node/byte.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Byte extends Node { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/test/fixtures/a-implementation/node/index.ts b/test/fixtures/a-implementation/node/index.ts index 31dbc5e..a90ba38 100644 --- a/test/fixtures/a-implementation/node/index.ts +++ b/test/fixtures/a-implementation/node/index.ts @@ -1,6 +1,7 @@ import { Consume } from './consume'; import { Empty } from './empty'; import { Error } from './error'; +import { Int } from './int'; import { Invoke } from './invoke'; import { Pause } from './pause'; import { Sequence } from './sequence'; @@ -10,6 +11,6 @@ import { SpanStart } from './span-start'; import { TableLookup } from './table-lookup'; export default { - Consume, Empty, Error, Invoke, Pause, Sequence, Single, SpanEnd, + Consume, Empty, Error, Invoke, Int, Pause, Sequence, Single, SpanEnd, SpanStart, TableLookup, }; diff --git a/test/fixtures/a-implementation/node/int.ts b/test/fixtures/a-implementation/node/int.ts new file mode 100644 index 0000000..86403db --- /dev/null +++ b/test/fixtures/a-implementation/node/int.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Int extends Node { + protected doBuild(out: string[]): void { + out.push(this.format(`byteOffset=${this.ref.byteOffset}`)); + } +} diff --git a/test/fixtures/implementation/node/byte.ts b/test/fixtures/implementation/node/byte.ts new file mode 100644 index 0000000..55e9ec5 --- /dev/null +++ b/test/fixtures/implementation/node/byte.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Byte extends Node { + protected doBuild(out: string[]): void { + out.push(this.format('')); + } +} diff --git a/test/fixtures/implementation/node/index.ts b/test/fixtures/implementation/node/index.ts index 31dbc5e..41c7014 100644 --- a/test/fixtures/implementation/node/index.ts +++ b/test/fixtures/implementation/node/index.ts @@ -1,6 +1,7 @@ import { Consume } from './consume'; import { Empty } from './empty'; import { Error } from './error'; +import { Int } from './int'; import { Invoke } from './invoke'; import { Pause } from './pause'; import { Sequence } from './sequence'; @@ -10,6 +11,6 @@ import { SpanStart } from './span-start'; import { TableLookup } from './table-lookup'; export default { - Consume, Empty, Error, Invoke, Pause, Sequence, Single, SpanEnd, + Consume, Empty, Error, Int, Invoke, Pause, Sequence, Single, SpanEnd, SpanStart, TableLookup, }; diff --git a/test/fixtures/implementation/node/int.ts b/test/fixtures/implementation/node/int.ts new file mode 100644 index 0000000..86403db --- /dev/null +++ b/test/fixtures/implementation/node/int.ts @@ -0,0 +1,8 @@ +import { node } from '../../../../src/frontend'; +import { Node } from './base'; + +export class Int extends Node { + protected doBuild(out: string[]): void { + out.push(this.format(`byteOffset=${this.ref.byteOffset}`)); + } +} diff --git a/test/frontend-test.ts b/test/frontend-test.ts index c55589e..8488405 100644 --- a/test/frontend-test.ts +++ b/test/frontend-test.ts @@ -63,6 +63,38 @@ describe('llparse-frontend', () => { ]); }); + it('should expand int nodes based on their number of required bytes', () => { + b.property('i8', 'byte'); + + const byte = b.intLE('byte', 1); + byte.otherwise(b.error(123, 'hello')); + + checkNodes(f, byte, [ + '', + '' + ]); + + const short = b.intLE('short', 2); + short.otherwise(b.error(123, 'hello')); + + checkNodes(f, short, [ + '', + '', + '' + ]); + + const word = b.intLE('word', 4); + word.otherwise(b.error(123, 'hello')); + + checkNodes(f, word, [ + '', + '', + '', + '', + '' + ]); + }); + it('should do peephole optimization', () => { const root = b.node('root'); const root1 = b.node('a');