123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- // 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 Buffer = require('safe-buffer').Buffer;
- 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 = Buffer.alloc(buffersize);');
- $line('var offset = 0;');
- emit_write(segments);
- $line('return buf;');
- $line('}'); // end function
- return new Function('write_int', 'write_float', 'Buffer',
- $result())(write_int, write_float, Buffer);
- }
- 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);
- };
|