| // 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); |
| }; |