From 8b53d33da6ca06811e083d592761962e9aa35936 Mon Sep 17 00:00:00 2001 From: Alan Gutierrez Date: Thu, 6 Aug 2015 12:27:59 -0500 Subject: [PATCH] Incremental nested serialization. Closes #336. --- compose/serializer/inc.js | 189 ++++++++++++++++++ t/generated/array-of-objects.serialize.inc.js | 106 ++++++++++ t/serialize/inc/array-of-objects.t.js | 51 +++++ t/test | 2 +- 4 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 compose/serializer/inc.js create mode 100644 t/generated/array-of-objects.serialize.inc.js create mode 100644 t/serialize/inc/array-of-objects.t.js diff --git a/compose/serializer/inc.js b/compose/serializer/inc.js new file mode 100644 index 00000000..7b6b906e --- /dev/null +++ b/compose/serializer/inc.js @@ -0,0 +1,189 @@ +var Variables = require('../variables') +var explode = require('../explode') +var qualify = require('../qualify') +var $ = require('programmatic') +var joinSources = require('../join-sources') + +Generator.prototype.integer = function (field, property) { + var bites = [], bite = field.bite, stop = field.stop, shift + while (bite != stop) { + var value = bite ? 'value >>> ' + bite * 8 : 'value' + bites.push('buffer[start++] = ' + value + ' & 0xff') + bite += field.direction + } + bites = bites.join('\n') + var direction = field.little ? '++' : '--' + return $(' \n\ + case ' + (this.step++) + ': \n\ + // __blank__ \n\ + this.step = ' + this.step + ' \n\ + this.bite = ' + field.bite + ' \n\ + // __blank__ \n\ + case ' + (this.step++) + ': \n\ + // __blank__ \n\ + while (this.bite != ' + field.stop + ') { \n\ + if (start == end) { \n\ + engine.start = start \n\ + return \n\ + } \n\ + buffer[start++] = ' + property + ' >>> this.bite * 8 & 0xff \n\ + this.bite', direction, ' \n\ + } \n\ + // __blank__ \n\ + this.step = ' + this.step + ' \n\ + ') +} + +Generator.prototype.construct = function (definition) { + var fields = [] + for (var name in definition) { + if (name[0] === '$') { + continue + } + var field = definition[name] + if (Array.isArray(field)) { + fields.push(name + ': new Array') + } else { + fields.push(name + ': null') + } + } + return fields.join(',\n') +} + +Generator.prototype.nested = function (definition) { + return $(' \n\ + ', this.serialize(definition), ' \n\ + ') +} + +Generator.prototype.lengthEncoded = function (name, field, depth) { + var source = '' + var length = qualify('length', depth) + var object = qualify('object', depth) + var subObject = qualify('object', depth + 1) + var i = qualify('i', depth) + this.variables.hoist(i) + this.variables.hoist(length) + this.forever = true + var step = this.step + 2 + source = $(' \n\ + // __reference__ \n\ + ', this.integer(explode(field.$length), 'frame.object.' + name + '.length'), '\n\ + // __blank__ \n\ + case ' + (this.step++) + ': \n\ + // __blank__ \n\ + this.stack.push(frame = {\n\ + object: frame.object.' + name + '[frame.index], \n\ + index: 0 \n\ + }) \n\ + this.step = ' + this.step + ' \n\ + // __blank__ \n\ + ', this.nested(field), ' \n\ + // __blank__ \n\ + this.stack.pop() \n\ + frame = this.stack[this.stack.length - 1] \n\ + if (++frame.index != frame.object.' + name + '.length) { \n\ + this.step = ' + step + ' \n\ + continue \n\ + } \n\ + ') + return source +} + +Generator.prototype.serialize = function (definition) { + var sources = [] + for (var name in definition) { + if (name[0] === '$') { + continue + } + var field = definition[name] + if (Array.isArray(field)) { + field = field[0] + if (field.$length) { + sources.push(this.lengthEncoded(name, field)) + } + } else { + field = explode(field) + if (field.type === 'integer') { + sources.push(this.integer(field, 'frame.object.' + name)) + } + } + } + return joinSources(sources) +} + +function parser (name, definition) { + return new Generator(name, definition).generate() +} + +function Generator (name, definition) { + this.step = 0 + this.name = name + this.definition = definition + this.variables = new Variables +} + +Generator.prototype.generate = function () { + var source = this.serialize(this.definition) + var dispatch = $(' \n\ + switch (this.step) { \n\ + ', source, ' \n\ + } \n\ + ') + if (this.forever) { + dispatch = $(' \n\ + for (;;) { \n\ + ', dispatch, ' \n\ + engine.start = start \n\ + return \n\ + } \n\ + ') + } + return $(' \n\ + serializers.' + this.name + ' = function (object) { \n\ + this.step = 0 \n\ + this.bite = 0 \n\ + this.stop = 0 \n\ + this.stack = [{ \n\ + object: object, \n\ + index: 0, \n\ + length: 0 \n\ + }] \n\ + } \n\ + // __blank__ \n\ + serializers.' + this.name + '.prototype.serialize = function (engine) { \n\ + var buffer = engine.buffer \n\ + var start = engine.start \n\ + var end = engine.end \n\ + // __blank__ \n\ + var frame = this.stack[this.stack.length - 1] \n\ + // __blank__ \n\ + ', String(this.variables), ' \n\ + // __blank__ \n\ + ', dispatch, ' \n\ + // __blank__ \n\ + engine.start = start \n\ + // __blank__ \n\ + return frame.object \n\ + } \n\ + ') +} + +module.exports = function (compiler, definition) { + var source = $(' \n\ + var serializers = {} \n\ + ') + Object.keys(definition).forEach(function (packet) { + source = $(' \n\ + ', source, ' \n\ + // __blank__ \n\ + ', parser(packet, definition[packet]), ' \n\ + ') + }) + source = $(' \n\ + ', source, ' \n\ + // __blank__ \n\ + return serializers \n\ + ') + return compiler(source) +} diff --git a/t/generated/array-of-objects.serialize.inc.js b/t/generated/array-of-objects.serialize.inc.js new file mode 100644 index 00000000..4c19a721 --- /dev/null +++ b/t/generated/array-of-objects.serialize.inc.js @@ -0,0 +1,106 @@ +module.exports = (function () { + var serializers = {} + + serializers.object = function (object) { + this.step = 0 + this.bite = 0 + this.stop = 0 + this.stack = [{ + object: object, + index: 0, + length: 0 + }] + } + + serializers.object.prototype.serialize = function (engine) { + var buffer = engine.buffer + var start = engine.start + var end = engine.end + + var frame = this.stack[this.stack.length - 1] + + var i + var length + + for (;;) { + switch (this.step) { + case 0: + + this.step = 1 + this.bite = 1 + + case 1: + + while (this.bite != -1) { + if (start == end) { + engine.start = start + return + } + buffer[start++] = frame.object.values.length >>> this.bite * 8 & 0xff + this.bite-- + } + + this.step = 2 + + case 2: + + this.stack.push(frame = { + object: frame.object.values[frame.index], + index: 0 + }) + this.step = 3 + + case 3: + + this.step = 4 + this.bite = 1 + + case 4: + + while (this.bite != -1) { + if (start == end) { + engine.start = start + return + } + buffer[start++] = frame.object.key >>> this.bite * 8 & 0xff + this.bite-- + } + + this.step = 5 + + case 5: + + this.step = 6 + this.bite = 1 + + case 6: + + while (this.bite != -1) { + if (start == end) { + engine.start = start + return + } + buffer[start++] = frame.object.value >>> this.bite * 8 & 0xff + this.bite-- + } + + this.step = 7 + + this.stack.pop() + frame = this.stack[this.stack.length - 1] + if (++frame.index != frame.object.values.length) { + this.step = 2 + continue + } + } + engine.start = start + return + } + + engine.start = start + + return frame.object + } + + return serializers +})() diff --git a/t/serialize/inc/array-of-objects.t.js b/t/serialize/inc/array-of-objects.t.js new file mode 100644 index 00000000..3450fcc1 --- /dev/null +++ b/t/serialize/inc/array-of-objects.t.js @@ -0,0 +1,51 @@ +require('proof')(20, prove) + +function prove (assert) { + var path = require('path') + var compiler = require('../../../compiler/require') + var composer = require('../../../compose/serializer/inc.js') + var filename = path.resolve(__filename, '../../../generated/array-of-objects.serialize.inc.js') + + var serializers = composer(compiler(filename), { + object: { + values: [{ + $length: { + endianess: 'b', + bits: 16 + }, + key: { + endianess: 'b', + bits: 16 + }, + value: { + endianess: 'b', + bits: 16 + } + }] + } + }) + var bufferLength = 10 + for (var i = 0; i < bufferLength; i++) { + var buffer = new Buffer(bufferLength) + var object = { + values: [ { key: 2570, value: 1 }, { key: 2, value: 3 } ] + } + var engine = { + buffer: buffer, + start: 0, + end: bufferLength - i + } + var serializer = new serializers.object(object) + serializer.serialize(engine) + var engine = { + buffer: buffer, + start: bufferLength - i, + end: bufferLength + } + serializer.serialize(engine) + assert(engine.buffer.toJSON(), [ + 0x0, 0x2, 0xa, 0xa, 0x0, 0x1, 0x0, 0x2, 0x0, 0x3 + ], 'compiled') + assert(engine.start, buffer.length, 'start moved') + } +} diff --git a/t/test b/t/test index d319cdd5..8b680373 100755 --- a/t/test +++ b/t/test @@ -4,7 +4,7 @@ set -e echo "" -(proof run t/compiler/*.t.js t/*/all/*.t.js | tee .proof.out | proof progress) || (proof errors < .proof.out) || exit 1 +(proof run t/compiler/*.t.js t/*/inc/*.t.js t/*/all/*.t.js | tee .proof.out | proof progress) || (proof errors < .proof.out) || exit 1 if [ $(find t -name "*.t.js.broken" | wc -c) != "0" ]; then echo ""