blob: dbcfa3843b3ecabc16d6d30fe4119f75e183c64b [file] [log] [blame]
// Compile patterns to recognisers and constructors
'use strict';
require('buffer-more-ints');
var $ = require('util').format;
var parse = require('./parse').parse;
var interp = require('./interp'),
parse_int = interp.parse_int,
parse_float = interp.parse_float;
var construct = require('./constructor'),
write_int = construct.write_int,
write_float = construct.write_float;
var lines = [];
function $start() {
lines = [];
}
function $line(/* format , args */) {
lines.push($.apply(null, arguments));
}
function $result() {
return lines.join('\n');
}
function bits_expr(segment) {
if (typeof segment.size === 'string') {
return $('%s * %d', var_name(segment.size), segment.unit);
}
else {
return (segment.size * segment.unit).toString();
}
}
function get_number(segment) {
$line('bits = %s;\n', bits_expr(segment));
var parser = (segment.type === 'integer') ?
'parse_int' : 'parse_float';
var be = segment.bigendian, sg = segment.signed;
$line("byteoffset = offset / 8; offset += bits");
$line("if (offset > binsize) { return false; }");
$line("else { result = %s(bin, byteoffset, bits / 8, %s, %s); }",
parser, be, sg);
}
function get_binary(segment) {
$line("byteoffset = offset / 8;");
if (segment.size === true) {
$line("offset = binsize;");
$line("result = bin.slice(byteoffset);");
}
else {
$line("bits = %s;", bits_expr(segment));
$line("offset += bits;");
$line("if (offset > binsize) { return false; }");
$line("else { result = bin.slice(byteoffset,",
"byteoffset + bits / 8); }");
}
}
function get_string(segment) {
$line("byteoffset = offset / 8;");
var strlen = segment.value.length;
var strlenbits = strlen * 8;
$line("offset += %d;", strlenbits);
$line("if (offset > binsize) { return false; }");
$line("else { result = bin.toString(byteoffset,",
$("byteoffset + %d); }", strlen));
}
function skip_bits(segment) {
if (typeof segment.size === 'string') {
// Damn. Have to look up the size.
$line("var skipbits = %s * %d;",
var_name(segment.size), segment.unit);
$line("if (offset + skipbits > binsize) { return false; }");
$line("else { offset += skipbits; }");
}
else if (segment.size === true) {
$line("if (offset % 8 === 0) { offset = binsize; }");
$line("else { return false; }");
}
else {
var bits = segment.unit * segment.size;
$line("if (offset + %d > binsize) { return false; }", bits);
$line("else { offset += %d; }", bits);
}
}
function match_seg(segment) {
if (segment.name === '_') {
skip_bits(segment);
}
else {
var assign_result;
switch (segment.type) {
case 'integer':
case 'float':
get_number(segment);
break;
case 'binary':
get_binary(segment);
break;
case 'string':
get_string(segment);
break;
}
$line("if (result === false) return false;");
if (segment.name) {
// variable is given a value in the environment
$line("else if (%s !== undefined) {", var_name(segment.name));
// .. and it is not the same as that matched
$line("if (%s != result) return false;",
var_name(segment.name));
$line("}");
// variable is free
$line('else %s = result;', var_name(segment.name));
}
else {
var repr = JSON.stringify(segment.value);
$line("else if (result != %s) return false;", repr);
}
}
}
function var_name(name) {
return 'var_' + name;
}
function variables(segments) {
var names = {};
for (var i = 0; i < segments.length; i++) {
var name = segments[i].name;
if (name && name !== '_') {
names[name] = true;
}
name = segments[i].size;
if (typeof name === 'string') {
names[name] = true;
}
}
return Object.keys(names);
}
function compile_pattern(segments) {
$start();
$line("return function(binary, env) {");
$line("'use strict';");
$line("var bin = binary, env = env || {};");
$line("var offset = 0, binsize = bin.length * 8;");
$line("var bits, result, byteoffset;");
var varnames = variables(segments);
for (var v = 0; v < varnames.length; v++) {
var name = varnames[v];
$line("var %s = env['%s'];", var_name(name), name);
}
var len = segments.length;
for (var i = 0; i < len; i++) {
var segment = segments[i];
$line("// " + JSON.stringify(segment));
match_seg(segment);
}
$line("if (offset == binsize) {");
$line("return {");
for (var v = 0; v < varnames.length; v++) {
var name = varnames[v];
$line("%s: %s,", name, var_name(name));
}
$line('};');
$line('}'); // if offset == binsize
$line("else return false;");
$line("}"); // end function
var fn = new Function('parse_int', 'parse_float', $result());
return fn(parse_int, parse_float);
}
function write_seg(segment) {
switch (segment.type) {
case 'string':
$line("offset += buf.write(%s, offset, 'utf8');",
JSON.stringify(segment.value));
break;
case 'binary':
$line("val = bindings['%s'];", segment.name);
if (segment.size === true) {
$line('size = val.length;');
}
else if (typeof segment.size === 'string') {
$line("size = (bindings['%s'] * %d) / 8;",
segment.size, segment.unit);
}
else {
$line("size = %d;", (segment.size * segment.unit) / 8);
}
$line('val.copy(buf, offset, 0, size);');
$line('offset += size;');
break;
case 'integer':
case 'float':
write_number(segment);
break;
}
}
function write_number(segment) {
if (segment.name) {
$line("val = bindings['%s'];", segment.name);
}
else {
$line("val = %d", segment.value);
}
var writer = (segment.type === 'integer') ?
'write_int' : 'write_float';
if (typeof segment.size === 'string') {
$line("size = (bindings['%s'] * %d) / 8;",
segment.size, segment.unit);
}
else {
$line('size = %d;', (segment.size * segment.unit) / 8);
}
$line('%s(buf, val, offset, size, %s);',
writer, segment.bigendian);
$line('offset += size;');
}
function size_of(segments) {
var variable = [];
var fixed = 0;
for (var i = 0; i < segments.length; i++) {
var segment = segments[i];
if (typeof segment.size === 'string' ||
segment.size === true) {
variable.push(segment);
}
else if (segment.type === 'string') {
fixed += Buffer.byteLength(segment.value);
}
else {
fixed += (segment.size * segment.unit) / 8;
}
}
$line('var buffersize = %d;', fixed);
if (variable.length > 0) {
for (var j = 0; j < variable.length; j++) {
var segment = variable[j];
if (segment.size === true) {
$line("buffersize += bindings['%s'].length;", segment.name);
}
else {
$line("buffersize += (bindings['%s'] * %d) / 8;",
segment.size, segment.unit);
}
}
}
}
function emit_write(segments) {
$line('var val, size;');
var len = segments.length;
for (var i = 0; i < len; i++) {
var segment = segments[i];
$line('// %s', JSON.stringify(segment));
write_seg(segment);
}
}
function compile_ctor(segments) {
$start();
$line('return function(bindings) {');
$line("'use strict';");
size_of(segments);
$line('var buf = new Buffer(buffersize);');
$line('var offset = 0;');
emit_write(segments);
$line('return buf;');
$line('}'); // end function
return new Function('write_int', 'write_float',
$result())(write_int, write_float);
}
module.exports.compile_pattern = compile_pattern;
module.exports.compile = function() {
var str = [].join.call(arguments, ',');
var p = parse(str);
return compile_pattern(p);
};
module.exports.compile_builder = function() {
var str = [].join.call(arguments, ',');
var p = parse(str);
return compile_ctor(p);
};