bitsyntax
Advanced tools
Comparing version 0.0.2 to 0.0.3
module.exports.parse = require('./lib/parse').parse; | ||
module.exports.match = require('./lib/interp').match; | ||
module.exports.compile = require('./lib/compile').compile; | ||
module.exports.construct = require('./lib/constructor').construct; | ||
module.exports.build = require('./lib/constructor').build; | ||
module.exports.write = require('./lib/constructor').write; | ||
module.exports.constructor = require('./lib/constructor').constructor; | ||
module.exports.matcher = module.exports.compile = | ||
require('./lib/compile').compile; | ||
module.exports.builder = require('./lib/compile').compile_builder; |
// Compile patterns to recognisers | ||
require('buffer-more-ints'); | ||
var $ = require('util').format; | ||
@@ -9,9 +10,23 @@ var parse = require('./parse').parse; | ||
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 var_name(segment.size) + " * " + segment.unit; | ||
return $('%s * %d', var_name(segment.size), segment.unit); | ||
} | ||
else { | ||
return segment.size * segment.unit; | ||
return (segment.size * segment.unit).toString(); | ||
} | ||
@@ -21,23 +36,24 @@ } | ||
function get_number(segment) { | ||
var expr = "bits = " + bits_expr(segment) + ";\n"; | ||
var parser = (segment.type === 'integer') ? 'parse_int' : 'parse_float'; | ||
$line('bits = %s;\n', bits_expr(segment)); | ||
var parser = (segment.type === 'integer') ? | ||
'parse_int' : 'parse_float'; | ||
var be = segment.bigendian, sg = segment.signed; | ||
return expr + | ||
"byteoffset = offset / 8; offset += bits\n" + | ||
"if (offset > binsize) { return false; }\n" + | ||
"else { result = " + parser + | ||
"(bin, byteoffset, bits, " + be + ", " + sg + "); }\n"; | ||
$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) { | ||
var expr = "byteoffset = offset / 8;\n"; | ||
$line("byteoffset = offset / 8;"); | ||
if (segment.size === true) { | ||
return expr + "offset = binsize;\n" + | ||
"result = bin.slice(byteoffset);\n"; | ||
$line("offset = binsize;"); | ||
$line("result = bin.slice(byteoffset);"); | ||
} | ||
else { | ||
expr += "bits = " + bits_expr(segment) + ";\n"; | ||
expr += "offset += bits;\n"; | ||
return expr + "if (offset > binsize) { return false; }\n" + | ||
"else { result = bin.slice(byteoffset, byteoffset + bits / 8); }\n"; | ||
$line("bits = %s;", bits_expr(segment)); | ||
$line("offset += bits;"); | ||
$line("if (offset > binsize) { return false; }"); | ||
$line("else { result = bin.slice(byteoffset,", | ||
"byteoffset + bits / 8); }"); | ||
} | ||
@@ -47,8 +63,9 @@ } | ||
function get_string(segment) { | ||
var expr = "byteoffset = offset / 8;\n"; | ||
$line("byteoffset = offset / 8;"); | ||
var strlen = segment.value.length; | ||
var strlenbits = strlen * 8; | ||
expr += "offset += " + strlenbits + ";\n"; | ||
return expr + "if (offset > binsize) { return false; }\n" + | ||
"else { result = bin.toString(byteoffset, byteoffset + " + strlen + "); }\n"; | ||
$line("offset += %d;", strlenbits); | ||
$line("if (offset > binsize) { return false; }"); | ||
$line("else { result = bin.toString(byteoffset,", | ||
$("byteoffset + %d); }", strlen)); | ||
} | ||
@@ -59,16 +76,15 @@ | ||
// Damn. Have to look up the size. | ||
var lookup = "var skipbits = " + var_name(segment.size) + | ||
" * " + segment.unit + ";\n"; | ||
var test = "if (offset + skipbits > binsize) { return false; }\n" + | ||
"else { offset += skipbits; }\n"; | ||
return lookup + test; | ||
$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) { | ||
return "if (offset % 8 === 0) { offset = binsize; }\n" + | ||
"else { return false; }\n"; | ||
$line("if (offset % 8 === 0) { offset = binsize; }"); | ||
$line("else { return false; }"); | ||
} | ||
else { | ||
var bits = segment.unit * segment.size; | ||
return "if (offset + " + bits + " > binsize) { return false; }\n" + | ||
"else { offset += " + bits + "; }\n"; | ||
$line("if (offset + %d > binsize) { return false; }", bits); | ||
$line("else { offset += %d; }", bits); | ||
} | ||
@@ -79,3 +95,3 @@ } | ||
if (segment.name === '_') { | ||
return skip_bits(segment); | ||
skip_bits(segment); | ||
} | ||
@@ -87,20 +103,19 @@ else { | ||
case 'float': | ||
assign_result = get_number(segment); | ||
get_number(segment); | ||
break; | ||
case 'binary': | ||
assign_result = get_binary(segment); | ||
get_binary(segment); | ||
break; | ||
case 'string': | ||
assign_result = get_string(segment); | ||
get_string(segment); | ||
break; | ||
} | ||
var handle_result = "if (result === false) { return false; }\n"; | ||
$line("if (result === false) { return false; }"); | ||
if (segment.name) { | ||
handle_result += "else { " + var_name(segment.name) + " = result; }\n"; | ||
$line("else { %s = result; }", var_name(segment.name)); | ||
} | ||
else { | ||
var repr = JSON.stringify(segment.value); | ||
handle_result += "else if (result != " + repr + ") { return false; }\n"; | ||
$line("else if (result != %s) { return false; }", repr); | ||
} | ||
return assign_result + handle_result; | ||
} | ||
@@ -114,24 +129,22 @@ } | ||
function variables(segments) { | ||
var names = []; | ||
var names = {}; | ||
for (var i = 0; i < segments.length; i++) { | ||
name = segments[i].name; | ||
if (name && name !== '_') { | ||
names.push(name); | ||
names[name] = true; | ||
} | ||
name = segments[i].size; | ||
if (typeof name === 'string') { | ||
names.push(name); | ||
names[name] = true; | ||
} | ||
} | ||
return names; | ||
return Object.keys(names); | ||
} | ||
function compile_pattern(segments) { | ||
var len = segments.length; | ||
var func = | ||
"return function(binary, vars) {\n" + | ||
"var bin = binary, scope = vars || {};\n" + | ||
"var offset = 0, binsize = bin.length * 8;\n" + | ||
"var bits, result, byteoffset;\n"; | ||
$start(); | ||
$line("return function(binary, vars) {"); | ||
$line("var bin = binary, scope = vars || {};"); | ||
$line("var offset = 0, binsize = bin.length * 8;"); | ||
$line("var bits, result, byteoffset;"); | ||
var varnames = variables(segments); | ||
@@ -141,26 +154,155 @@ var bindings = ""; | ||
var name = varnames[v]; | ||
func += "var " + var_name(name) + " = scope['" + name + "'];\n"; | ||
bindings += "bindings['" + name + "'] = " + var_name(name) + ";\n"; | ||
$line("var %s = scope['%s'];", var_name(name), name); | ||
} | ||
var len = segments.length; | ||
for (var i = 0; i < len; i++) { | ||
var segment = segments[i]; | ||
func += "// " + JSON.stringify(segment) + "\n"; | ||
func += match_seg(segment); | ||
$line("// " + JSON.stringify(segment)); | ||
match_seg(segment); | ||
} | ||
func += "if (offset == binsize) {\n" + | ||
"var bindings = {};\n" + bindings + | ||
"return bindings; }\n"; | ||
func += "else { return false; }\n"; | ||
func += "}\n"; | ||
$line("if (offset == binsize) {"); | ||
$line("var bindings = {"); | ||
for (var v = 0; v < varnames.length; v++) { | ||
var name = varnames[v]; | ||
$line("%s: %s,", name, var_name(name)); | ||
} | ||
$line('}'); | ||
$line("return bindings; }"); | ||
$line("else { return false; }"); | ||
$line("}"); // end function | ||
return new Function('parse_int', 'parse_float', func)(parse_int, parse_float); | ||
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 { | ||
// could do this statically of course | ||
$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 compile_write(segments) { | ||
$start(); | ||
$line('return function(buf, offset, bindings) {'); | ||
$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); | ||
} | ||
$line('return offset;'); | ||
$line('}'); // end function | ||
var fn = new Function('write_int', 'write_float', $result()); | ||
return fn(write_int, write_float); | ||
} | ||
function compile_ctor(segments) { | ||
var writer = compile_write(segments); | ||
$start(); | ||
$line('return function(bindings) {'); | ||
size_of(segments); | ||
$line('var buf = new Buffer(buffersize);'); | ||
$line('write(buf, 0, bindings);'); | ||
$line('return buf;'); | ||
$line('}'); // end function | ||
return new Function('write', $result())(writer); | ||
} | ||
module.exports.compile_pattern = compile_pattern; | ||
module.exports.compile = function(str) { | ||
str = (arguments.length > 1) ? [].join.call(arguments, ',') : str; | ||
module.exports.compile = function() { | ||
var str = [].join.call(arguments, ','); | ||
var p = parse(str); | ||
return compile_pattern(p); | ||
}; | ||
module.exports.compile_write = compile_write; | ||
module.exports.compile_builder = function() { | ||
var str = [].join.call(arguments, ','); | ||
var p = parse(str); | ||
return compile_ctor(p); | ||
}; |
@@ -5,2 +5,4 @@ // -*- js-indent-level: 2 -*- | ||
var ints = require('buffer-more-ints'); | ||
// Interpret the pattern, writing values into a buffer | ||
@@ -10,23 +12,15 @@ function write(buf, offset, pattern, bindings) { | ||
var segment = pattern[i]; | ||
switch (segment.type) { | ||
case 'string': | ||
var str = segment.value; | ||
buf.write(str, offset); | ||
offset += Buffer.byteLength(str); | ||
offset += buf.write(segment.value, offset, 'utf8'); | ||
break; | ||
case 'binary': | ||
// must be a variable reference | ||
var bin = bindings[segment.name]; | ||
var size = segment.size; | ||
size = (size === true) ? bin.length : size; | ||
bin.copy(buf, offset, 0, size); | ||
offset += size; | ||
offset += writeBinary(segment, buf, offset, bindings); | ||
break; | ||
case 'integer': | ||
writeInteger(segment, buf, offset, bindings); | ||
offset += (segment.size * segment.unit) / 8; | ||
offset += writeInteger(segment, buf, offset, bindings); | ||
break; | ||
case 'float': | ||
writeFloat(segment, buf, offset, bindings); | ||
offset += (segment.size * segment.unit) / 8; | ||
offset += writeFloat(segment, buf, offset, bindings); | ||
break; | ||
@@ -38,3 +32,3 @@ } | ||
function construct(pattern, bindings) { | ||
function build(pattern, bindings) { | ||
var bufsize = size_of(pattern, bindings); | ||
@@ -46,2 +40,18 @@ var buf = new Buffer(bufsize); | ||
// In bytes | ||
function size_of_segment(segment, bindings) { | ||
// size refers to a variable | ||
if (typeof segment.size === 'string') { | ||
return (bindings[segment.size] * segment.unit) / 8; | ||
} | ||
if (segment.type === 'string') { | ||
return Buffer.byteLength(segment.value, 'utf8'); | ||
} | ||
if (segment.type === 'binary' && segment.size === true) { | ||
var val = bindings[segment.name]; | ||
return val.length; | ||
} | ||
return (segment.size * segment.unit) / 8; | ||
} | ||
// size of the to-be-constructed binary, in bytes | ||
@@ -51,13 +61,3 @@ function size_of(segments, bindings) { | ||
for (var i=0, len = segments.length; i < len; i++) { | ||
var segment = segments[i]; | ||
if (segment.type === 'string') { | ||
size += segment.value.length; // FIXME encoding | ||
} | ||
else if (segment.size === true) { | ||
var val = bindings[segment.name]; | ||
size += val.length; | ||
} | ||
else { | ||
size += (segment.size * segment.unit) / 8; | ||
} | ||
size += size_of_segment(segments[i], bindings); | ||
} | ||
@@ -67,2 +67,9 @@ return size; | ||
function writeBinary(segment, buf, offset, bindings) { | ||
var bin = bindings[segment.name]; | ||
var size = size_of_segment(segment, bindings); | ||
bin.copy(buf, offset, 0, size); | ||
return size; | ||
} | ||
// TODO in ff might use the noAssert argument to Buffer.write*() but | ||
@@ -73,21 +80,25 @@ // need to check that it does the right thing wrt little-endian | ||
var value = (segment.name) ? bindings[segment.name] : segment.value; | ||
var size = segment.size * segment.unit; | ||
var size = size_of_segment(segment, bindings); | ||
return write_int(buf, value, offset, size, segment.bigendian); | ||
} | ||
function write_int(buf, value, offset, size, bigendian) { | ||
switch (size) { | ||
case 8: | ||
case 1: | ||
buf.writeUInt8(value, offset); | ||
break; | ||
case 16: | ||
(segment.bigendian) ? | ||
case 2: | ||
(bigendian) ? | ||
buf.writeUInt16BE(value, offset) : | ||
buf.writeUInt16LE(value, offset); | ||
break; | ||
case 32: | ||
(segment.bigendian) ? | ||
case 4: | ||
(bigendian) ? | ||
buf.writeUInt32BE(value, offset) : | ||
buf.writeUInt32LE(value, offset); | ||
break; | ||
case 64: | ||
(segment.bigendian) ? | ||
buf.writeUInt64BE(value, offset) : | ||
buf.writeUInt64LE(value, offset); | ||
case 8: | ||
(bigendian) ? | ||
ints.writeUInt64BE(buf, value, offset) : | ||
ints.writeUInt64LE(buf, value, offset); | ||
break; | ||
@@ -97,2 +108,3 @@ default: | ||
} | ||
return size; | ||
} | ||
@@ -102,10 +114,14 @@ | ||
var value = (segment.name) ? bindings[segment.name] : segment.value; | ||
var size = segment.size * segment.unit; | ||
if (size === 32) { | ||
(segment.bigendian) ? | ||
var size = size_of_segment(segment, bindings); | ||
return write_float(buf, value, offset, size, segment.bigendian); | ||
} | ||
function write_float(buf, value, offset, size, bigendian) { | ||
if (size === 4) { | ||
(bigendian) ? | ||
buf.writeFloatBE(value, offset) : | ||
buf.writeFloatLE(value, offset); | ||
} | ||
else if (size === 64) { | ||
(segment.bigendian) ? | ||
else if (size === 8) { | ||
(bigendian) ? | ||
buf.writeDoubleBE(value, offset) : | ||
@@ -117,2 +133,3 @@ buf.writeDoubleLE(value, offset); | ||
} | ||
return size; | ||
} | ||
@@ -123,9 +140,12 @@ | ||
module.exports.write = write; | ||
module.exports.construct = construct; | ||
module.exports.constructor = function(pstr) { | ||
module.exports.build = build; | ||
module.exports.write_int = write_int; | ||
module.exports.write_float = write_float; | ||
module.exports.builder = function(pstr) { | ||
pstr = (arguments.length > 1) ? [].join.call(arguments, ',') : pstr; | ||
var pattern = parse(pstr); | ||
return function(vars) { | ||
return exports.construct(pattern, vars); | ||
return build(pattern, vars); | ||
}; | ||
}; |
@@ -34,3 +34,3 @@ // -*- js-indent: 2 -*- | ||
require('buffer-more-ints'); | ||
var ints = require('buffer-more-ints'); | ||
@@ -40,17 +40,28 @@ var debug = (process.env.DEBUG) ? | ||
function parse_int(bin, off, sizeinbits, bigendian, signed) { | ||
var sizeInBytes = sizeinbits / 8; | ||
if (bigendian) { | ||
return (signed) ? bin.readIntBE(sizeInBytes, off) : bin.readUIntBE(sizeInBytes, off); | ||
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"; | ||
} | ||
else { | ||
return (signed) ? bin.readIntLE(sizeInBytes, off) : bin.readUIntLE(sizeInBytes, off); | ||
} | ||
} | ||
function parse_float(bin, off, sizeinbits, bigendian) { | ||
switch (sizeinbits) { | ||
case 32: | ||
function parse_float(bin, off, sizeInBytes, bigendian) { | ||
switch (sizeInBytes) { | ||
case 4: | ||
return (bigendian) ? bin.readFloatBE(off) : bin.readFloatLE(off); | ||
case 64: | ||
case 8: | ||
return (bigendian) ? bin.readDoubleBE(off) : bin.readDoubleLE(off); | ||
@@ -125,3 +136,3 @@ default: | ||
else { | ||
return parse_int(binary, byteoffset, bitsize, | ||
return parse_int(binary, byteoffset, bitsize / 8, | ||
segment.bigendian, segment.signed); | ||
@@ -141,3 +152,4 @@ } | ||
else { | ||
return parse_float(binary, byteoffset, bitsize, segment.bigendian); | ||
return parse_float(binary, byteoffset, | ||
bitsize / 8, segment.bigendian); | ||
} | ||
@@ -144,0 +156,0 @@ } |
// Parse patterns in string form into the form we use for interpreting | ||
// (and later, for compiling). | ||
var peg = require('pegjs'), | ||
ast = require('./pattern'), | ||
path = require('path'); | ||
var ast = require('./pattern'); | ||
var parser = require('./parser'); | ||
var grammar = require('fs').readFileSync( | ||
path.join(path.dirname(module.filename), 'grammar.pegjs')).toString(); | ||
var parser = peg.buildParser(grammar); | ||
function parse_pattern(string) { | ||
@@ -32,5 +27,5 @@ var segments = parser.parse(string); | ||
module.exports.parse = function(str) { | ||
str = (arguments.length > 1) ? [].join.call(arguments, ',') : str; | ||
module.exports.parse = function() { | ||
var str = [].join.call(arguments, ','); | ||
return parse_pattern(str); | ||
}; |
@@ -8,3 +8,3 @@ { | ||
"description": "Pattern-matching on byte buffers", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"repository": { | ||
@@ -16,3 +16,4 @@ "type": "git", | ||
"scripts": { | ||
"test": "make test" | ||
"test": "make test", | ||
"prepublish": "make all" | ||
}, | ||
@@ -23,6 +24,8 @@ "engines": { | ||
"dependencies": { | ||
"pegjs": "~0.7", | ||
"buffer-more-ints": "" | ||
"buffer-more-ints": "0.0.2" | ||
}, | ||
"devDependencies": {} | ||
"devDependencies": { | ||
"pegjs": "0.7.x", | ||
"mocha": "0.9.x" | ||
} | ||
} |
171
README.md
# Byte-wise matching for Node.JS | ||
[![Build Status](https://travis-ci.org/squaremo/bitsyntax-js.png?branch=master)](https://travis-ci.org/squaremo/bitsyntax-js) | ||
Gives a compact syntax for parsing and constructing byte buffers, | ||
@@ -7,7 +9,9 @@ derived from [Erlang's bit | ||
var bitsyntax = require('bitsyntax'); | ||
var pattern = bitsyntax.compile('len:8/integer, string:len/binary'); | ||
var bound = pattern(new Buffer([4, 0x41, 0x42, 0x43, 0x44])); | ||
bound.string | ||
// => <Buffer 41 42 43 44> | ||
```js | ||
var bitsyntax = require('bitsyntax'); | ||
var pattern = bitsyntax.matcher('len:8/integer, str:len/binary'); | ||
var bound = pattern(new Buffer([4, 0x41, 0x42, 0x43, 0x44])); | ||
bound.str | ||
// => <Buffer 41 42 43 44> | ||
``` | ||
@@ -17,20 +21,25 @@ A typical use of this is parsing byte streams from sockets. For | ||
var framePattern = bitsyntax.compile('len:32/integer, frame:len/binary, rest/binary'); | ||
socket.on('data', function process(data) { | ||
var m; | ||
if (m = framePattern(data)) { | ||
emit('frame', m.frame); | ||
process(m.rest); | ||
} | ||
else { | ||
stashForNextData(data); | ||
} | ||
}); | ||
```js | ||
var framePattern = bitsyntax.matcher('len:32/integer, frame:len/binary, rest/binary'); | ||
socket.on('data', function process(data) { | ||
var m; | ||
if (m = framePattern(data)) { | ||
emit('frame', m.frame); | ||
process(m.rest); | ||
} | ||
else { | ||
stashForNextData(data); | ||
} | ||
}); | ||
``` | ||
Patterns can also be used to construct binaries from supplied values: | ||
Patterns can also be used to construct byte buffers from supplied | ||
values: | ||
var spdyDataFrame = require('bitsyntax') | ||
.constructor('streamId:32, flags:8, length:24, data/binary'); | ||
```js | ||
var spdyDataFrame = require('bitsyntax') | ||
.builder('streamId:32, flags:8, length:24, data/binary'); | ||
spdyDataFrame({streamId:5, flags:0, length:bin.length, data:bin}); | ||
spdyDataFrame({streamId:5, flags:0, length:bin.length, data:bin}); | ||
``` | ||
@@ -41,37 +50,53 @@ One or more segments of a pattern may also be supplied in multiple | ||
var p = bitsyntax.compile('size:8, payload:size/binary', | ||
'rest/binary'); | ||
```js | ||
var p = bitsyntax.matcher('size:8, payload:size/binary', | ||
'rest/binary'); | ||
``` | ||
## API | ||
### `compile` | ||
### `matcher` | ||
Compiles a pattern to a function that will return either a map of | ||
bindings, or `false`, given a buffer and optionally an | ||
environment. The environment contains values for the bound variables | ||
in the pattern (if there are any). | ||
Compiles a pattern as a string (or strings), to a function that will | ||
return either a map of bindings, or `false`, given a buffer and | ||
optionally an environment. The environment contains values for the | ||
bound variables in the pattern (if there are any). | ||
var p = bitsyntax.compile('header:headerSize/binary, rest/binary'); | ||
var b = p(new Buffer([1, 2, 3, 4, 5]), {headerSize: 3}); | ||
b.header | ||
// => <Buffer 01 02 03> | ||
```js | ||
var p = bitsyntax.matcher('header:headerSize/binary, rest/binary'); | ||
var b = p(new Buffer([1, 2, 3, 4, 5]), {headerSize: 3}); | ||
b.header | ||
// => <Buffer 01 02 03> | ||
``` | ||
A matcher will return `false` if the supplied buffer does not match | ||
the pattern; for example, if it has too few bytes, or a literal is not | ||
present. | ||
```js | ||
var p = bitsyntax.matcher('"foo:", str/binary'); | ||
p(new Buffer("bar:humbug")); | ||
// => false | ||
``` | ||
### `parse` and `match` | ||
In combination, equivalent to `compile`; may be useful if you want to | ||
When composed, equivalent to `matcher`; may be useful if you want to | ||
examine the internal structure of patterns. | ||
`parse` takes strings as for `compile`, and returns the internal | ||
`parse` takes strings as for `matcher`, and returns the internal | ||
representation of the pattern. `match` takes this representation, a | ||
buffer, and optionally an environment, and returns the bindings or | ||
false (as with `compile`). | ||
`false` (as with `matcher`). | ||
var p = bitsyntax.parse('header:headerSize/binary', | ||
'rest/binary'); | ||
var b = bitsyntax.match(p, new Buffer([1, 2, 3, 4, 5]), | ||
{headerSize: 3}); | ||
b.header | ||
// => <Buffer 01 02 03> | ||
```js | ||
var p = bitsyntax.parse('header:headerSize/binary', | ||
'rest/binary'); | ||
var b = bitsyntax.match(p, new Buffer([1, 2, 3, 4, 5]), | ||
{headerSize: 3}); | ||
b.header | ||
// => <Buffer 01 02 03> | ||
``` | ||
### `constructor` | ||
### `builder` | ||
@@ -81,9 +106,38 @@ Takes a pattern and returns a function that will construct a byte | ||
var cons = bitsyntax.constructor('size:8, bin/binary'); | ||
cons({size:6, bin:newBuffer('foobar')}); | ||
// => <Buffer 06 66 6f 6f 62 61 72> | ||
```js | ||
var cons = bitsyntax.builder('size:8, bin/binary'); | ||
cons({size:6, bin:new Buffer('foobar')}); | ||
// => <Buffer 06 66 6f 6f 62 61 72> | ||
``` | ||
Patterns supplied to constructors are slightly different to patterns | ||
Patterns supplied to builders are slightly different to patterns | ||
supplied for matching, as noted below. | ||
### `build` | ||
Takes a parsed pattern and a map of variable values, and returns a | ||
buffer. As with `match`, may be useful to debug patterns. | ||
```js | ||
var pattern = bitsyntax.parse('size:8, bin:size/binary'); | ||
bitsyntax.build(pattern, {size:6, bin: new Buffer('foobar')}); | ||
// => <Buffer 06 66 6f 6f 62 61 72> | ||
``` | ||
### `write` | ||
Writes variable values into a buffer, at an offset, according to the | ||
parsed pattern given. Returns the finishing offset, i.e., the supplied | ||
offset plus the number of bytes written. | ||
```js | ||
var pattern = bitsyntax.parse('size:8, bin/binary'); | ||
var buf = new Buffer(7); | ||
bitsyntax.write(buf, 0, pattern, | ||
{size:6, bin: new Buffer('foobar')}); | ||
// => 7 | ||
buf | ||
// => <Buffer 06 66 6f 6f 62 61 72> | ||
``` | ||
## Patterns | ||
@@ -116,9 +170,9 @@ | ||
In constructors, the literal value will be copied into the result | ||
binary according to the type it is given. A variable name indicates a | ||
space into which a value supplied to the constructor will be copied. | ||
When used in a builder, the literal value will be copied into the | ||
buffer according to the type it is given. A variable name indicates a | ||
slot into which a value supplied to the builder will be copied. | ||
The special variable name `_` discards the value matched; i.e., it | ||
simply skips over the appropriate number of bits in the input. '_' is | ||
not allowed in constructors. | ||
not allowed in builder patterns. | ||
@@ -149,11 +203,12 @@ ### Size and unit | ||
match all remaining bytes in the input; such a segment may only be | ||
used at the end of a pattern. | ||
used at the end of a pattern, when matching. | ||
The size may also be given as an integer variable matched earlier in | ||
the pattern, as in the example given at the top. | ||
the pattern, as in the example given at the top. When constructing, a | ||
size may be a variable referring to the supplied environment. | ||
In constructors, numbers will be rounded, masked or padded to fit the | ||
size and units given; for example, `'256:8'` gives the binary | ||
`Buffer<00>` because the lowest eight bits are 0; `'255:16` gives the | ||
binary `Buffer<00 ff>`. | ||
In builders, numbers will be rounded, masked or padded to fit the size | ||
and units given; for example, `'256:8'` gives the binary `Buffer<00>` | ||
because the lowest eight bits are 0; `'255:16` gives the binary | ||
`Buffer<00 ff>`. | ||
@@ -197,3 +252,3 @@ ### Type name specifier | ||
Signedness is ignored in constructors. | ||
Signedness is ignored in builders. | ||
@@ -210,4 +265,4 @@ ### Literal strings | ||
When used in a constructor, a quoted string is copied verbatim into | ||
the result. | ||
When used in a builder, a quoted string is copied verbatim into the | ||
result. | ||
@@ -214,0 +269,0 @@ ## Examples |
var assert = require('assert'); | ||
var parse = require('../').parse; | ||
var construct = require('../').construct; | ||
var constructor = require('../').constructor; | ||
var build = require('../').build; | ||
var builder = require('../').builder; | ||
var write = require('../').write; | ||
@@ -15,2 +15,6 @@ | ||
['n:8, "foobar", m:8', {n: 255, m:0}, [255, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0]], | ||
['n:8, s:n/binary', {n:6, s: new Buffer('foobar')}, [6, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]], | ||
['n:8, s:n/binary', {n:4, s: new Buffer('foobar')}, [4, 0x66, 0x6f, 0x6f, 0x62]], | ||
['n:size', {n:4, size:8}, [4]], | ||
['206:n/unit:8', {n:1}, [206]] | ||
]; | ||
@@ -26,3 +30,3 @@ | ||
test(c[0], function() { | ||
assert.deepEqual(c[2], bufferToArray(construct(p, c[1]))); | ||
assert.deepEqual(c[2], bufferToArray(build(p, c[1]))); | ||
}); | ||
@@ -36,3 +40,3 @@ test(c[0], function() { | ||
test(c[0], function() { | ||
var cons = constructor(c[0]); | ||
var cons = builder(c[0]); | ||
var buf = cons(c[1]); | ||
@@ -39,0 +43,0 @@ assert.deepEqual(c[2], bufferToArray(buf)); |
var match = require('../').match; | ||
var parse = require('../').parse; | ||
var compile = require('../').compile; | ||
var compile = require('../').matcher; | ||
var assert = require('assert'); | ||
@@ -5,0 +5,0 @@ |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Bad dependency semver
QualityPackage has dependencies with an invalid semantic version. This could be a sign of beta, low quality, or unmaintained dependencies.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
74867
1
15
2052
1
300
1
2
4
+ Addedbuffer-more-ints@0.0.2(transitive)
- Removedpegjs@~0.7
- Removedpegjs@0.7.0(transitive)
Updatedbuffer-more-ints@0.0.2