compile.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // Compile patterns to recognisers and constructors
  2. 'use strict';
  3. require('buffer-more-ints');
  4. var $ = require('util').format;
  5. var parse = require('./parse').parse;
  6. var interp = require('./interp'),
  7. parse_int = interp.parse_int,
  8. parse_float = interp.parse_float;
  9. var construct = require('./constructor'),
  10. write_int = construct.write_int,
  11. write_float = construct.write_float;
  12. var Buffer = require('safe-buffer').Buffer;
  13. var lines = [];
  14. function $start() {
  15. lines = [];
  16. }
  17. function $line(/* format , args */) {
  18. lines.push($.apply(null, arguments));
  19. }
  20. function $result() {
  21. return lines.join('\n');
  22. }
  23. function bits_expr(segment) {
  24. if (typeof segment.size === 'string') {
  25. return $('%s * %d', var_name(segment.size), segment.unit);
  26. }
  27. else {
  28. return (segment.size * segment.unit).toString();
  29. }
  30. }
  31. function get_number(segment) {
  32. $line('bits = %s;\n', bits_expr(segment));
  33. var parser = (segment.type === 'integer') ?
  34. 'parse_int' : 'parse_float';
  35. var be = segment.bigendian, sg = segment.signed;
  36. $line("byteoffset = offset / 8; offset += bits");
  37. $line("if (offset > binsize) { return false; }");
  38. $line("else { result = %s(bin, byteoffset, bits / 8, %s, %s); }",
  39. parser, be, sg);
  40. }
  41. function get_binary(segment) {
  42. $line("byteoffset = offset / 8;");
  43. if (segment.size === true) {
  44. $line("offset = binsize;");
  45. $line("result = bin.slice(byteoffset);");
  46. }
  47. else {
  48. $line("bits = %s;", bits_expr(segment));
  49. $line("offset += bits;");
  50. $line("if (offset > binsize) { return false; }");
  51. $line("else { result = bin.slice(byteoffset,",
  52. "byteoffset + bits / 8); }");
  53. }
  54. }
  55. function get_string(segment) {
  56. $line("byteoffset = offset / 8;");
  57. var strlen = segment.value.length;
  58. var strlenbits = strlen * 8;
  59. $line("offset += %d;", strlenbits);
  60. $line("if (offset > binsize) { return false; }");
  61. $line("else { result = bin.toString(byteoffset,",
  62. $("byteoffset + %d); }", strlen));
  63. }
  64. function skip_bits(segment) {
  65. if (typeof segment.size === 'string') {
  66. // Damn. Have to look up the size.
  67. $line("var skipbits = %s * %d;",
  68. var_name(segment.size), segment.unit);
  69. $line("if (offset + skipbits > binsize) { return false; }");
  70. $line("else { offset += skipbits; }");
  71. }
  72. else if (segment.size === true) {
  73. $line("if (offset % 8 === 0) { offset = binsize; }");
  74. $line("else { return false; }");
  75. }
  76. else {
  77. var bits = segment.unit * segment.size;
  78. $line("if (offset + %d > binsize) { return false; }", bits);
  79. $line("else { offset += %d; }", bits);
  80. }
  81. }
  82. function match_seg(segment) {
  83. if (segment.name === '_') {
  84. skip_bits(segment);
  85. }
  86. else {
  87. var assign_result;
  88. switch (segment.type) {
  89. case 'integer':
  90. case 'float':
  91. get_number(segment);
  92. break;
  93. case 'binary':
  94. get_binary(segment);
  95. break;
  96. case 'string':
  97. get_string(segment);
  98. break;
  99. }
  100. $line("if (result === false) return false;");
  101. if (segment.name) {
  102. // variable is given a value in the environment
  103. $line("else if (%s !== undefined) {", var_name(segment.name));
  104. // .. and it is not the same as that matched
  105. $line("if (%s != result) return false;",
  106. var_name(segment.name));
  107. $line("}");
  108. // variable is free
  109. $line('else %s = result;', var_name(segment.name));
  110. }
  111. else {
  112. var repr = JSON.stringify(segment.value);
  113. $line("else if (result != %s) return false;", repr);
  114. }
  115. }
  116. }
  117. function var_name(name) {
  118. return 'var_' + name;
  119. }
  120. function variables(segments) {
  121. var names = {};
  122. for (var i = 0; i < segments.length; i++) {
  123. var name = segments[i].name;
  124. if (name && name !== '_') {
  125. names[name] = true;
  126. }
  127. name = segments[i].size;
  128. if (typeof name === 'string') {
  129. names[name] = true;
  130. }
  131. }
  132. return Object.keys(names);
  133. }
  134. function compile_pattern(segments) {
  135. $start();
  136. $line("return function(binary, env) {");
  137. $line("'use strict';");
  138. $line("var bin = binary, env = env || {};");
  139. $line("var offset = 0, binsize = bin.length * 8;");
  140. $line("var bits, result, byteoffset;");
  141. var varnames = variables(segments);
  142. for (var v = 0; v < varnames.length; v++) {
  143. var name = varnames[v];
  144. $line("var %s = env['%s'];", var_name(name), name);
  145. }
  146. var len = segments.length;
  147. for (var i = 0; i < len; i++) {
  148. var segment = segments[i];
  149. $line("// " + JSON.stringify(segment));
  150. match_seg(segment);
  151. }
  152. $line("if (offset == binsize) {");
  153. $line("return {");
  154. for (var v = 0; v < varnames.length; v++) {
  155. var name = varnames[v];
  156. $line("%s: %s,", name, var_name(name));
  157. }
  158. $line('};');
  159. $line('}'); // if offset == binsize
  160. $line("else return false;");
  161. $line("}"); // end function
  162. var fn = new Function('parse_int', 'parse_float', $result());
  163. return fn(parse_int, parse_float);
  164. }
  165. function write_seg(segment) {
  166. switch (segment.type) {
  167. case 'string':
  168. $line("offset += buf.write(%s, offset, 'utf8');",
  169. JSON.stringify(segment.value));
  170. break;
  171. case 'binary':
  172. $line("val = bindings['%s'];", segment.name);
  173. if (segment.size === true) {
  174. $line('size = val.length;');
  175. }
  176. else if (typeof segment.size === 'string') {
  177. $line("size = (bindings['%s'] * %d) / 8;",
  178. segment.size, segment.unit);
  179. }
  180. else {
  181. $line("size = %d;", (segment.size * segment.unit) / 8);
  182. }
  183. $line('val.copy(buf, offset, 0, size);');
  184. $line('offset += size;');
  185. break;
  186. case 'integer':
  187. case 'float':
  188. write_number(segment);
  189. break;
  190. }
  191. }
  192. function write_number(segment) {
  193. if (segment.name) {
  194. $line("val = bindings['%s'];", segment.name);
  195. }
  196. else {
  197. $line("val = %d", segment.value);
  198. }
  199. var writer = (segment.type === 'integer') ?
  200. 'write_int' : 'write_float';
  201. if (typeof segment.size === 'string') {
  202. $line("size = (bindings['%s'] * %d) / 8;",
  203. segment.size, segment.unit);
  204. }
  205. else {
  206. $line('size = %d;', (segment.size * segment.unit) / 8);
  207. }
  208. $line('%s(buf, val, offset, size, %s);',
  209. writer, segment.bigendian);
  210. $line('offset += size;');
  211. }
  212. function size_of(segments) {
  213. var variable = [];
  214. var fixed = 0;
  215. for (var i = 0; i < segments.length; i++) {
  216. var segment = segments[i];
  217. if (typeof segment.size === 'string' ||
  218. segment.size === true) {
  219. variable.push(segment);
  220. }
  221. else if (segment.type === 'string') {
  222. fixed += Buffer.byteLength(segment.value);
  223. }
  224. else {
  225. fixed += (segment.size * segment.unit) / 8;
  226. }
  227. }
  228. $line('var buffersize = %d;', fixed);
  229. if (variable.length > 0) {
  230. for (var j = 0; j < variable.length; j++) {
  231. var segment = variable[j];
  232. if (segment.size === true) {
  233. $line("buffersize += bindings['%s'].length;", segment.name);
  234. }
  235. else {
  236. $line("buffersize += (bindings['%s'] * %d) / 8;",
  237. segment.size, segment.unit);
  238. }
  239. }
  240. }
  241. }
  242. function emit_write(segments) {
  243. $line('var val, size;');
  244. var len = segments.length;
  245. for (var i = 0; i < len; i++) {
  246. var segment = segments[i];
  247. $line('// %s', JSON.stringify(segment));
  248. write_seg(segment);
  249. }
  250. }
  251. function compile_ctor(segments) {
  252. $start();
  253. $line('return function(bindings) {');
  254. $line("'use strict';");
  255. size_of(segments);
  256. $line('var buf = Buffer.alloc(buffersize);');
  257. $line('var offset = 0;');
  258. emit_write(segments);
  259. $line('return buf;');
  260. $line('}'); // end function
  261. return new Function('write_int', 'write_float', 'Buffer',
  262. $result())(write_int, write_float, Buffer);
  263. }
  264. module.exports.compile_pattern = compile_pattern;
  265. module.exports.compile = function() {
  266. var str = [].join.call(arguments, ',');
  267. var p = parse(str);
  268. return compile_pattern(p);
  269. };
  270. module.exports.compile_builder = function() {
  271. var str = [].join.call(arguments, ',');
  272. var p = parse(str);
  273. return compile_ctor(p);
  274. };