dissolve
Version:
Parse and consume binary streams with a neat DSL
187 lines (145 loc) • 6.63 kB
Markdown
Dissolve
========
Parse and consume binary streams with a neat DSL.
Overview
--------
Dissolve allows you to parse packed binary data into numbers, buffers, strings
and more*! With a simple syntax inspired by [node-binary](https://github.com/substack/node-binary)
and a solid, minimal implementation, you can be up and running in no time.
(* implementing "more" is left as an exercise to the reader)
If you want to produce binary data, might I suggest [concentrate](https://github.com/deoxxa/concentrate)?
Features
--------
* Accurate handling of [u]int{8,16,32} numbers in both signed and unsigned
variants using fast, built-in [Buffer](http://nodejs.org/docs/latest/api/buffer.html)
methods
* Fast approximation of [u]int64 numbers in signed and unsigned variants
* Extendable base class for building your own parsers and implementing
custom types
* Tiny (~250 LoC) implementation, allowing for easy debugging
Installation
------------
Available via [npm](http://npmjs.org/):
> $ npm install dissolve
Or via git:
> $ git clone git://github.com/deoxxa/dissolve.git node_modules/dissolve
Usage
-----
Also see [example.js](https://github.com/deoxxa/dissolve/blob/master/example.js),
[example-complex.js](https://github.com/deoxxa/dissolve/blob/master/example-complex.js)
and [example-loop.js](https://github.com/deoxxa/dissolve/blob/master/example-loop.js).
```javascript
#!/usr/bin/env node
var Dissolve = require("./index");
var parser = Dissolve().loop(function(end) {
this.uint8("id").tap(function() {
if (this.vars.id === 0x01) {
this.uint16be("a").uint16be("b");
} else if (this.vars.id === 0x02) {
this.uint32be("x").uint32be("y");
}
}).tap(function() {
this.push(this.vars);
this.vars = {};
});
});
parser.on("readable", function() {
var e;
while (e = parser.read()) {
console.log(e);
}
});
parser.write(new Buffer([0x01, 0x00, 0x02, 0x00, 0x03])); // {id: 1, a: 2, b: 3}
parser.write(new Buffer([0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05])); // {id: 2, x: 4, y: 5}
parser.write(new Buffer([0x01]));
parser.write(new Buffer([0x00, 0x02, 0x00]));
parser.write(new Buffer([0x03])); // {id: 1, a: 2, b: 3}
```
Methods
-------
All parser methods are chainable and return the same parser instance they were
called on.
Tap
---
`tap(name, callback)`
This method allows you to "tap" into the parser at an arbitrary point. The
callback will be called bound to the parser instance, so you can use parser
methods on `this`. Any additional parser steps you introduce inside the callback
will be executed before any existing steps that are already scheduled to run
after the `tap` call.
If you provide a `name` parameter, all actions performed in the callback will be
applied to a child object that will be put into a new property named after
`name`. Note that in the callback, even if you provide a `name` parameter, you
can still pretend you were in the outer "scope" because of some prototype
trickery done with the `vars` object under the hood. You don't need to worry
about that too much, the examples should make it a bit clearer.
Loop
----
`loop(name, callback)`
This method is like `tap` except that the callback is called over and over until
signalled to stop. You do this by calling the `end` function that's provided as
the first argument to your callback. When you call the `end` function, you can
provide an optional truthy/non-truthy flag to tell Dissolve to ignore the result
of the iteration of the loop where `end` was called. This is useful if you are
reading until a null entry or similar.
If you provide a `name` parameter, a new array will be placed into a property
named for that parameter, and after each iteration of the loop, any new values
will be appended to the array as an object. As with the `name` stuff on `tap`,
the examples will make that explanation a lot clearer.
The same semantics for job ordering and "scoping" apply as for `tap`.
Basic Parsing Methods
---------------------
For each basic parsing method, the `name` value is the key under which the value
will be attached to `this.vars`.
Buffer/String Methods
---------------------
For these methods, the `length` parameter tells the parser how many bytes to
pull out. If it's a string, it will be assumed that it is the name of a
previously-set `this.vars` entry. If it's a number, it will be used as-is.
* `buffer(name, length)` - binary slice
* `string(name, length)` - utf8 string slice
Numeric Methods
---------------
* `int8(name)` - signed 8 bit integer
* `sint8(name)` - signed 8 bit integer
* `uint8(name)` - unsigned 8 bit integer
* `int16(name)` - signed, little endian 16 bit integer
* `int16le(name)` - signed, little endian 16 bit integer
* `int16be(name)` - signed, big endian 16 bit integer
* `sint16(name)` - signed, little endian 16 bit integer
* `sint16le(name)` - signed, little endian 16 bit integer
* `sint16be(name)` - signed, big endian 16 bit integer
* `uint16(name)` - unsigned, little endian 16 bit integer
* `uint16le(name)` - unsigned, little endian 16 bit integer
* `uint16be(name)` - unsigned, big endian 16 bit integer
* `int32(name)` - signed, little endian 32 bit integer
* `int32le(name)` - signed, little endian 32 bit integer
* `int32be(name)` - signed, big endian 32 bit integer
* `sint32(name)` - signed, little endian 32 bit integer
* `sint32le(name)` - signed, little endian 32 bit integer
* `sint32be(name)` - signed, big endian 32 bit integer
* `uint32(name)` - unsigned, little endian 32 bit integer
* `uint32le(name)` - unsigned, little endian 32 bit integer
* `uint32be(name)` - unsigned, big endian 32 bit integer
* `int64(name)` - signed, little endian 64 bit integer
* `int64le(name)` - signed, little endian 64 bit integer
* `int64be(name)` - signed, big endian 64 bit integer
* `sint64(name)` - signed, little endian 64 bit integer
* `sint64le(name)` - signed, little endian 64 bit integer
* `sint64be(name)` - signed, big endian 64 bit integer
* `uint64(name)` - unsigned, little endian 64 bit integer
* `uint64le(name)` - unsigned, little endian 64 bit integer
* `uint64be(name)` - unsigned, big endian 64 bit integer
* `floatbe(data)` - big endian 32 bit float
* `floatle(data)` - little endian 32 bit float
* `doublebe(data)` - big endian 64 bit double
* `doublele(data)` - little endian 64 bit double
License
-------
3-clause BSD. A copy is included with the source.
Contact
-------
* GitHub ([deoxxa](http://github.com/deoxxa))
* Twitter ([@deoxxa](http://twitter.com/deoxxa))
* ADN ([@deoxxa](https://alpha.app.net/deoxxa))
* Email ([deoxxa@fknsrs.biz](mailto:deoxxa@fknsrs.biz))