| // -*- js-indent: 2 -*- |
| // Interpreter for bit syntax AST. |
| // Grammar: |
| // |
| // pattern := segment ("," segment)* |
| // segment := (value | var) (":" size)? ("/" specifier ("-" specifier)*)? | string |
| // var := "_" | identifier |
| // size := integer | var |
| // specifier := "little" | "big" | "signed" | "unsigned" | "unit" ":" 0..256 | type |
| // type := "integer" | "binary" | "float" |
| // |
| // where integer has the obvious meaning, and identifier is anything |
| // other than "_" that fits the JavaScript identifier specification. |
| // |
| |
| // We'll use an object to represent each segment, and an array of |
| // segments for a pattern. We won't try to optimise for groups of |
| // patterns; we'll just step through each to see if it works. We rely |
| // a hypothetical prior step to check that it's a valid pattern. |
| |
| // ? compile to intermediate instructions ? |
| |
| // A segment looks like |
| // { |
| // type: string, // 'string' is special case |
| // size: integer | true, // true means 'all remaining' |
| // name: string | null, // (may be '_') |
| // value: value | null, // either name OR value |
| // unit: integer, |
| // signed: boolean, |
| // bigendian: boolean |
| // } |
| |
| 'use strict'; |
| |
| var ints = require('buffer-more-ints'); |
| |
| var debug = (process.env.DEBUG) ? |
| function(s) { console.log(s); } : function () {}; |
| |
| function parse_int(bin, off, sizeInBytes, bigendian, signed) { |
| switch (sizeInBytes) { |
| case 1: |
| return (signed) ? bin.readInt8(off) : bin.readUInt8(off); |
| case 2: |
| return (bigendian) ? |
| (signed) ? bin.readInt16BE(off) : bin.readUInt16BE(off) : |
| (signed) ? bin.readInt16LE(off) : bin.readUInt16LE(off); |
| case 4: |
| return (bigendian) ? |
| (signed) ? bin.readInt32BE(off) : bin.readUInt32BE(off) : |
| (signed) ? bin.readInt32LE(off) : bin.readUInt32LE(off); |
| case 8: |
| return (bigendian) ? |
| ((signed) ? ints.readInt64BE : ints.readUInt64BE)(bin, off) : |
| ((signed) ? ints.readInt64LE : ints.readUInt64LE)(bin, off); |
| default: |
| throw "Integers must be 8-, 16-, 32- or 64-bit"; |
| } |
| } |
| |
| function parse_float(bin, off, sizeInBytes, bigendian) { |
| switch (sizeInBytes) { |
| case 4: |
| return (bigendian) ? bin.readFloatBE(off) : bin.readFloatLE(off); |
| case 8: |
| return (bigendian) ? bin.readDoubleBE(off) : bin.readDoubleLE(off); |
| default: |
| throw "Floats must be 32- or 64-bit"; |
| } |
| } |
| |
| function size_of(segment, bound) { |
| var size = segment.size; |
| if (typeof size === 'string') { |
| return bound[size]; |
| } |
| else { |
| return size; |
| } |
| } |
| |
| function new_scope(env) { |
| function scope() {}; |
| scope.prototype = env; |
| return new scope(); |
| } |
| |
| function bindings(scope) { |
| var s = {}; |
| for (var k in scope) { |
| if (scope.hasOwnProperty(k)) { |
| s[k] = scope[k]; |
| } |
| } |
| return s; |
| } |
| |
| function match(pattern, binary, boundvars) { |
| var offset = 0, vars = new_scope(boundvars); |
| var binsize = binary.length * 8; |
| |
| function skip_bits(segment) { |
| debug("skip bits"); debug(segment); |
| var size = size_of(segment, vars); |
| if (size === true) { |
| if (offset % 8 === 0) { |
| offset = binsize; |
| return true; |
| } |
| else { |
| return false; |
| } |
| } |
| |
| var bits = segment.unit * size; |
| if (offset + bits > binsize) { |
| return false; |
| } |
| else { |
| offset += bits; |
| } |
| } |
| |
| function get_integer(segment) { |
| debug("get_integer"); debug(segment); |
| // let's do only multiples of eight bits for now |
| var unit = segment.unit, size = size_of(segment, vars); |
| var bitsize = size * unit; |
| var byteoffset = offset / 8; // NB assumes aligned |
| offset += bitsize; |
| if (bitsize % 8 > 0 || (offset > binsize)) { |
| return false; |
| } |
| else { |
| return parse_int(binary, byteoffset, bitsize / 8, |
| segment.bigendian, segment.signed); |
| } |
| } |
| |
| function get_float(segment) { |
| debug("get_float"); debug(segment); |
| var unit = segment.unit; var size = size_of(segment, vars); |
| var bitsize = size * unit; |
| var byteoffset = offset / 8; // assume aligned |
| offset += bitsize; |
| if (offset > binsize) { |
| return false; |
| } |
| else { |
| return parse_float(binary, byteoffset, |
| bitsize / 8, segment.bigendian); |
| } |
| } |
| |
| function get_binary(segment) { |
| debug("get_binary"); debug(segment); |
| var unit = segment.unit, size = size_of(segment, vars); |
| var byteoffset = offset / 8; // NB alignment |
| |
| if (size === true) { |
| offset = binsize; |
| return binary.slice(byteoffset); |
| } |
| else { |
| var bitsize = size * unit; |
| if (bitsize % 8 > 0 || (offset + bitsize) > binsize) { |
| return false; |
| } |
| else { |
| offset += bitsize; |
| return binary.slice(byteoffset, byteoffset + bitsize / 8); |
| } |
| } |
| } |
| |
| function get_string(segment) { |
| debug("get_string"); debug(segment); |
| var len = segment.value.length; |
| var byteoffset = offset / 8; |
| |
| offset += len * 8; |
| if (offset > binsize) { |
| return false; |
| } |
| // FIXME bytes vs UTF8 characters |
| return binary.slice(byteoffset, byteoffset + len).toString('utf8'); |
| } |
| |
| var patternlen = pattern.length; |
| for (var i = 0; i < patternlen; i++) { |
| var segment = pattern[i]; |
| var result = false; |
| if (segment.name === '_') { |
| result = skip_bits(segment); |
| } |
| else { |
| switch (segment.type) { |
| case 'string': |
| result = get_string(segment); |
| break; |
| case 'integer': |
| result = get_integer(segment); |
| break; |
| case 'float': |
| result = get_float(segment); |
| break; |
| case 'binary': |
| result = get_binary(segment); |
| break; |
| } |
| |
| if (result === false) { |
| return false; |
| } |
| else if (segment.name) { |
| vars[segment.name] = result; |
| } |
| else if (segment.value != result) { |
| return false; |
| } |
| } |
| } |
| if (offset == binsize) { |
| return bindings(vars); |
| } |
| else { |
| return false; |
| } |
| } |
| |
| module.exports.match = match; |
| module.exports.parse_int = parse_int; |
| module.exports.parse_float = parse_float; |