Skip to content

Commit

Permalink
Merge 8ea006c into fb569e0
Browse files Browse the repository at this point in the history
  • Loading branch information
theqabalist committed Mar 9, 2018
2 parents fb569e0 + 8ea006c commit 66459f7
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 29 deletions.
37 changes: 37 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,43 @@ parser.parse('accccc');
//=> {status: true, value: ['a', ['c', 'c', 'c', 'c', 'c']]}
```

# Binary constructors.

The purpose of the following constructors is to allow the consumption of Buffer types in node to allow for attoparsec style consumption of binary input.
As these constructors yield regular values within parsers, they can then be combined in the same fashion as the above string-based constructors to produce
robust binary parsers. These constructors live in the Parsimmon.Binary namespace.

## Parsimmon.byte(int)

Returns a parser that yields a byte that matches the given input. Similar to digit/letter.

## Parsimmon.bitSeq(...alignments)

Specify a series of bit alignments that do not have to be byte aligned and consume them from a buffer. The bits must
sum to a byte boundary.

```javascript
var parser = Parsimmon.Binary.bitSeq([3, 5, 5, 3]);
parser.parse(Buffer.from([0x04, 0xFF]));
//=> {status: true, value: [ 0, 4, 31, 7 ]}
```

## Parsimmon.bitSeqObj(...namedAlignments)

Specify a series of bit alignments with names that will output an object with those alignments. Very similar to seqObj,
however, but only accepts numeric values. Will discard unnamed alignments.

```javascript
var parser = Parsimmon.Binary.bitSeqObj([
["a", 3],
5,
["b", 5],
["c", 3]
]);
parser.parse(Buffer.from([0x04, 0xFF]));
//=> { status: true, value: { a: 0, b: 31, c: 7 } }
```

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

# Parser methods
Expand Down
36 changes: 18 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

176 changes: 174 additions & 2 deletions src/parsimmon.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,161 @@ function Parsimmon(action) {

var _ = Parsimmon.prototype;

function times(n, f) {
var i = 0;
for (i; i < n; i++) {
f(i);
}
}

function forEach(f, arr) {
times(arr.length, function(i) {
f(arr[i], i, arr);
});
}

function reduce(f, seed, arr) {
forEach(function(elem, i, arr) {
seed = f(seed, elem, i, arr);
}, arr);
return seed;
}

function map(f, arr) {
return reduce(
function(acc, elem, i, a) {
return acc.concat([f(elem, i, a)]);
},
[],
arr
);
}

function lshiftBuffer(input) {
var asTwoBytes = reduce(
function(a, v, i, b) {
return a.concat(
i === b.length - 1
? Buffer.from([v, 0]).readUInt16BE(0)
: b.readUInt16BE(i)
);
},
[],
input
);
return Buffer.from(
map(function(x) {
return ((x << 1) & 0xffff) >> 8;
}, asTwoBytes)
);
}

function consumeBitsFromBuffer(n, input) {
var state = { v: 0, buf: input };
times(n, function() {
state = {
v: (state.v << 1) | bitPeekBuffer(state.buf),
buf: lshiftBuffer(state.buf)
};
});
return state;
}

function bitPeekBuffer(input) {
return input[0] >> 7;
}

function sum(numArr) {
return reduce(
function(x, y) {
return x + y;
},
0,
numArr
);
}

function find(pred, arr) {
return reduce(
function(found, elem) {
return found || (pred(elem) ? elem : found);
},
null,
arr
);
}

function bitSeq(alignments) {
var totalBits = sum(alignments);
if (totalBits % 8 !== 0) {
throw new Error("Bits do not sum to byte boundary.");
}
var bytes = totalBits / 8;

var tooBigRange = find(function(x) {
return x > 48;
}, alignments);
if (tooBigRange) {
throw new Error(
tooBigRange.toString() +
" bit range requested exceeds 48 bit (6 byte) Number max."
);
}

return new Parsimmon(function(input, i) {
if (bytes + i > input.length) {
return makeFailure(i, bytes.toString() + " bytes");
}
return makeSuccess(
i + bytes,
reduce(
function(acc, bits) {
var state = consumeBitsFromBuffer(bits, acc.buf);
return {
coll: acc.coll.concat(state.v),
buf: state.buf
};
},
{ coll: [], buf: input },
alignments
).coll
);
});
}

function bitSeqObj(namedAlignments) {
var fullAlignments = map(function(pair) {
return isArray(pair) ? pair : [null, pair];
}, namedAlignments);

var namesOnly = map(function(pair) {
return pair[0];
}, fullAlignments);
var alignmentsOnly = map(function(pair) {
return pair[1];
}, fullAlignments);

return bitSeq(alignmentsOnly).map(function(parsed) {
var namedParsed = map(function(name, i) {
return [name, parsed[i]];
}, namesOnly);

return reduce(
function(obj, kv) {
if (kv[0] !== null) {
obj[kv[0]] = kv[1];
}
return obj;
},
{},
namedParsed
);
});
}

function toArray(arrLike) {
return Array.prototype.slice.call(arrLike);
}
// -*- Helpers -*-

function isParser(obj) {
Expand Down Expand Up @@ -240,7 +395,7 @@ function seq() {
function seqObj() {
var seenKeys = {};
var totalKeys = 0;
var parsers = [].slice.call(arguments);
var parsers = toArray(arguments);
var numParsers = parsers.length;
for (var j = 0; j < numParsers; j += 1) {
var p = parsers[j];
Expand Down Expand Up @@ -802,7 +957,6 @@ var whitespace = regexp(/\s+/).desc("whitespace");
Parsimmon.all = all;
Parsimmon.alt = alt;
Parsimmon.any = any;
Parsimmon.byte = byte;
Parsimmon.createLanguage = createLanguage;
Parsimmon.custom = custom;
Parsimmon.digit = digit;
Expand Down Expand Up @@ -841,4 +995,22 @@ Parsimmon.whitespace = whitespace;
Parsimmon["fantasy-land/empty"] = empty;
Parsimmon["fantasy-land/of"] = succeed;

function noBufferError() {
throw new Error(
"Buffer global does not exist; please consider using https://github.com/feross/buffer if you are running Parsimmon in a browser."
);
}

Parsimmon.Binary = hasBuffer
? {
bitSeq: bitSeq,
bitSeqObj: bitSeqObj,
byte: byte
}
: {
bitSeq: noBufferError,
bitSeqObj: noBufferError,
byte: noBufferError
};

module.exports = Parsimmon;
9 changes: 3 additions & 6 deletions test/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
{
"env": {
"node": true
"node": true,
"mocha": true
},
"globals":{
"Parsimmon": true,
"assert": true,
"suite": true,
"setup": true,
"teardown": true,
"test": true
"assert": true
}
}
27 changes: 27 additions & 0 deletions test/core/bitSeq.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use strict";

suite("bitSeq", function() {
test("it consumes bits into a sequence from a buffer", function() {
var b = Buffer.from([0xff, 0xff]);
var p = Parsimmon.Binary.bitSeq([3, 5, 5, 3]);
assert.deepEqual(p.parse(b).value, [7, 31, 31, 7]);
});

test("it disallows construction of parsers that don't align to byte boundaries", function() {
assert.throws(function() {
Parsimmon.Binary.bitSeq([1, 2]);
});
});

test("fails if requesting too much", function() {
var b = Buffer.from([]);
var p = Parsimmon.Binary.bitSeq([3, 5, 5, 3]);
assert.deepEqual(p.parse(b).expected, ["2 bytes"]);
});

test("it throws an exception for too large of a range request", function() {
assert.throws(function() {
Parsimmon.Binary.bitSeq([1, 2, 4, 49]);
});
});
});
37 changes: 37 additions & 0 deletions test/core/bitSeqObj.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use strict";

suite("bitSeqObj", function() {
test("it consumes bits into an object from a buffer", function() {
var b = Buffer.from([0xff, 0xff]);
var p = Parsimmon.Binary.bitSeqObj([
["a", 3],
["b", 5],
["c", 5],
["d", 3]
]);
assert.deepEqual(p.parse(b).value, { a: 7, b: 31, c: 31, d: 7 });
});

test("it disallows construction of parsers that don't align to byte boundaries", function() {
assert.throws(function() {
Parsimmon.Binary.bitSeqObj([["a", 1], ["b", 2]]);
});
});

test("fails if requesting too much", function() {
var b = Buffer.from([]);
var p = Parsimmon.Binary.bitSeqObj([
["a", 3],
["b", 5],
["c", 5],
["d", 3]
]);
assert.deepEqual(p.parse(b).expected, ["2 bytes"]);
});

test("it ignores unnamed ranges", function() {
var b = Buffer.from([0xff, 0xff]);
var p = Parsimmon.Binary.bitSeqObj([["a", 3], 5, ["c", 5], ["d", 3]]);
assert.deepEqual(p.parse(b).value, { a: 7, c: 31, d: 7 });
});
});
Loading

0 comments on commit 66459f7

Please sign in to comment.