blob: c62e74685785771b440efb373e22ccba339487b8 [file] [log] [blame]
// -*- 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;