Skip to content

Commit

Permalink
Incremental nested serialization.
Browse files Browse the repository at this point in the history
Closes #336.
  • Loading branch information
flatheadmill committed Aug 8, 2015
1 parent e42b5e2 commit 8b53d33
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 1 deletion.
189 changes: 189 additions & 0 deletions 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)
}
106 changes: 106 additions & 0 deletions 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
})()
51 changes: 51 additions & 0 deletions 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')
}
}
2 changes: 1 addition & 1 deletion t/test
Expand Up @@ -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 ""
Expand Down

0 comments on commit 8b53d33

Please sign in to comment.