Skip to content

Commit

Permalink
Merge a00014f into fb569e0
Browse files Browse the repository at this point in the history
  • Loading branch information
theqabalist committed Mar 8, 2018
2 parents fb569e0 + a00014f commit 6d7be00
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 23 deletions.
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.

163 changes: 161 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,11 @@ var whitespace = regexp(/\s+/).desc("whitespace");
Parsimmon.all = all;
Parsimmon.alt = alt;
Parsimmon.any = any;
Parsimmon.byte = byte;
Parsimmon.buffers = {
bitSeq: bitSeq,
bitSeqObj: bitSeqObj,
byte: byte
};
Parsimmon.createLanguage = createLanguage;
Parsimmon.custom = custom;
Parsimmon.digit = digit;
Expand Down
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.buffers.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.buffers.bitSeq([1, 2]);
});
});

test("fails if requesting too much", function() {
var b = Buffer.from([]);
var p = Parsimmon.buffers.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.buffers.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.buffers.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.buffers.bitSeqObj([["a", 1], ["b", 2]]);
});
});

test("fails if requesting too much", function() {
var b = Buffer.from([]);
var p = Parsimmon.buffers.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.buffers.bitSeqObj([["a", 3], 5, ["c", 5], ["d", 3]]);
assert.deepEqual(p.parse(b).value, { a: 7, c: 31, d: 7 });
});
});
6 changes: 3 additions & 3 deletions test/core/byte.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
suite("byte", function() {
test("it matches a buffer byte", function() {
var b = Buffer.from([0xf]);
var p = Parsimmon.byte(0xf);
var p = Parsimmon.buffers.byte(0xf);
assert.ok(p.parse(b).value);
});

test("it formats single digit bytes like 0x0f", function() {
var b = Buffer.from([0xa]);
var p = Parsimmon.byte(0xf);
var p = Parsimmon.buffers.byte(0xf);
assert.deepEqual(p.parse(b).expected, ["0x0f"]);
});

test("it formats double digit bytes like 0xff", function() {
var b = Buffer.from([0x12]);
var p = Parsimmon.byte(0xff);
var p = Parsimmon.buffers.byte(0xff);
assert.deepEqual(p.parse(b).expected, ["0xff"]);
});
});

0 comments on commit 6d7be00

Please sign in to comment.