You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
7.7 KiB
301 lines
7.7 KiB
// 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); |
|
};
|
|
|