123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- // -*- 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'),
- debug = require('debug')('bitsyntax-Interpreter');
- 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;
|