forked from syndicate-lang/preserves
Refactor to use immutable Bytes wrapper around a Uint8Array
This commit is contained in:
parent
9b4b548896
commit
a8519f6ae3
|
@ -0,0 +1,340 @@
|
||||||
|
"use strict";
|
||||||
|
// Preserves Binary codec.
|
||||||
|
|
||||||
|
const Values = require('./values.js');
|
||||||
|
const { List, Map, Set, Bytes, Record, Single, Double } = Values;
|
||||||
|
|
||||||
|
const { PreserveOn } = require('./symbols.js');
|
||||||
|
|
||||||
|
class DecodeError extends Error {}
|
||||||
|
class EncodeError extends Error {
|
||||||
|
constructor(message, irritant) {
|
||||||
|
super(message);
|
||||||
|
this.irritant = irritant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Decoder {
|
||||||
|
constructor(packet, options) {
|
||||||
|
options = options || {};
|
||||||
|
this.packet = packet._view || packet; // strip off Bytes wrapper, if any
|
||||||
|
this.index = 0;
|
||||||
|
this.shortForms = options.shortForms || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
peekbyte() {
|
||||||
|
if (this.index >= this.packet.length) throw new DecodeError("Short packet");
|
||||||
|
// ^ NOTE: greater-than-or-equal-to, not greater-than.
|
||||||
|
return this.packet[this.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(count) {
|
||||||
|
const start = this.index;
|
||||||
|
this.index += (count === void 0 ? 1 : count);
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextbyte() {
|
||||||
|
const val = this.peekbyte();
|
||||||
|
this.advance();
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
wirelength(arg) {
|
||||||
|
if (arg < 15) return arg;
|
||||||
|
return this.varint();
|
||||||
|
}
|
||||||
|
|
||||||
|
varint() {
|
||||||
|
// TODO: Bignums :-/
|
||||||
|
const v = this.nextbyte();
|
||||||
|
if (v < 128) return v;
|
||||||
|
return (this.varint() << 7) + (v - 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextbytes(n) {
|
||||||
|
const start = this.advance(n);
|
||||||
|
if (this.index > this.packet.length) throw new DecodeError("Short packet");
|
||||||
|
// ^ NOTE: greater-than, not greater-than-or-equal-to.
|
||||||
|
return new DataView(this.packet.buffer, start, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextvalues(n) {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < n; i++) result.push(this.next());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
peekop() {
|
||||||
|
const b = this.peekbyte();
|
||||||
|
const major = b >> 6;
|
||||||
|
const minor = (b >> 4) & 3;
|
||||||
|
const arg = b & 15;
|
||||||
|
return [major, minor, arg];
|
||||||
|
}
|
||||||
|
|
||||||
|
nextop() {
|
||||||
|
const op = this.peekop();
|
||||||
|
this.advance();
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
peekend(arg) {
|
||||||
|
const [a,i,r] = this.peekop();
|
||||||
|
return (a === 0) && (i === 3) && (r === arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
binarystream(arg, minor) {
|
||||||
|
const result = [];
|
||||||
|
while (!this.peekend(arg)) {
|
||||||
|
const chunk = this.next();
|
||||||
|
if (ArrayBuffer.isView(chunk)) {
|
||||||
|
result.push(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
|
||||||
|
} else if (chunk instanceof Bytes) {
|
||||||
|
result.push(chunk._view);
|
||||||
|
} else {
|
||||||
|
const e = new DecodeError("Unexpected non-binary chunk");
|
||||||
|
e.irritant = chunk;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.decodebinary(minor, Bytes.concat(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
valuestream(arg, minor, decoder) {
|
||||||
|
const result = [];
|
||||||
|
while (!this.peekend(arg)) result.push(this.next());
|
||||||
|
return decoder(minor, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeint(bs) {
|
||||||
|
// TODO: Bignums :-/
|
||||||
|
if (bs.length === 0) return 0;
|
||||||
|
let acc = bs[0];
|
||||||
|
if (acc & 0x80) acc -= 256;
|
||||||
|
for (let i = 1; i < bs.length; i++) acc = (acc << 8) | bs[i];
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodebinary(minor, bs) {
|
||||||
|
switch (minor) {
|
||||||
|
case 0: return this.decodeint(bs._view);
|
||||||
|
case 1: return bs.toString();
|
||||||
|
case 2: return bs;
|
||||||
|
case 3: return Symbol.for(bs.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decoderecord(minor, vs) {
|
||||||
|
if (minor === 3) {
|
||||||
|
if (vs.length === 0) throw new DecodeError("Too few elements in encoded record");
|
||||||
|
return Record(vs[0], vs.slice(1));
|
||||||
|
} else {
|
||||||
|
const label = this.shortForms[minor];
|
||||||
|
if (label === void 0) throw new DecodeError("Use of unconfigured short form " + minor);
|
||||||
|
return Record(label, vs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decodecollection(minor, vs) {
|
||||||
|
switch (minor) {
|
||||||
|
case 0: return List(vs);
|
||||||
|
case 1: return Set(vs);
|
||||||
|
case 2: return this.mapFromArray(vs);
|
||||||
|
case 3: throw new DecodeError("Invalid collection type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapFromArray(vs) {
|
||||||
|
return Map().withMutations((m) => {
|
||||||
|
for (let i = 0; i < vs.length; i += 2) {
|
||||||
|
m.set(vs[i], vs[i+1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
const [major, minor, arg] = this.nextop();
|
||||||
|
switch (major) {
|
||||||
|
case 0:
|
||||||
|
switch (minor) {
|
||||||
|
case 0:
|
||||||
|
switch (arg) {
|
||||||
|
case 0: return false;
|
||||||
|
case 1: return true;
|
||||||
|
case 2: return Single(this.nextbytes(4).getFloat32(0, false));
|
||||||
|
case 3: return Double(this.nextbytes(8).getFloat64(0, false));
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
return (arg > 12) ? arg - 16 : arg;
|
||||||
|
case 2: {
|
||||||
|
const t = arg >> 2;
|
||||||
|
const n = arg & 3;
|
||||||
|
switch (t) {
|
||||||
|
case 0: throw new DecodeError("Invalid format C start byte");
|
||||||
|
case 1: return this.binarystream(arg, n);
|
||||||
|
case 2: return this.valuestream(arg, n, this.decoderecord.bind(this));
|
||||||
|
case 3: return this.valuestream(arg, n, this.decodecollection.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
throw new DecodeError("Invalid format C end byte");
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
return this.decodebinary(minor, Bytes.from(this.nextbytes(this.wirelength(arg))));
|
||||||
|
case 2:
|
||||||
|
return this.decoderecord(minor, this.nextvalues(this.wirelength(arg)));
|
||||||
|
case 3:
|
||||||
|
return this.decodecollection(minor, this.nextvalues(this.wirelength(arg)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Encoder {
|
||||||
|
constructor(options) {
|
||||||
|
options = options || {}
|
||||||
|
this.chunks = [];
|
||||||
|
this.view = new DataView(new ArrayBuffer(256));
|
||||||
|
this.index = 0;
|
||||||
|
this.shortForms = options.shortForms || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
contents() {
|
||||||
|
this.rotatebuffer(4096);
|
||||||
|
return Bytes.concat(this.chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
rotatebuffer(size) {
|
||||||
|
this.chunks.push(new Uint8Array(this.view.buffer, 0, this.index));
|
||||||
|
this.view = new DataView(new ArrayBuffer(size));
|
||||||
|
this.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeroom(amount) {
|
||||||
|
if (this.index + amount > this.view.byteLength) {
|
||||||
|
this.rotatebuffer(amount + 4096);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitbyte(b) {
|
||||||
|
this.makeroom(1);
|
||||||
|
this.view.setUint8(this.index++, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitbytes(bs) {
|
||||||
|
this.makeroom(bs.length);
|
||||||
|
(new Uint8Array(this.view.buffer)).set(bs, this.index);
|
||||||
|
this.index += bs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
varint(v) {
|
||||||
|
while (v >= 128) {
|
||||||
|
this.emitbyte((v % 128) + 128);
|
||||||
|
v = Math.floor(v / 128);
|
||||||
|
}
|
||||||
|
this.emitbyte(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
leadbyte(major, minor, arg) {
|
||||||
|
this.emitbyte(((major & 3) << 6) | ((minor & 3) << 4) | (arg & 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
header(major, minor, wirelength) {
|
||||||
|
if (wirelength < 15) {
|
||||||
|
this.leadbyte(major, minor, wirelength);
|
||||||
|
} else {
|
||||||
|
this.leadbyte(major, minor, 15);
|
||||||
|
this.varint(wirelength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeint(v) {
|
||||||
|
// TODO: Bignums :-/
|
||||||
|
const plain_bitcount = Math.floor(Math.log2(v > 0 ? v : ~v)) + 1;
|
||||||
|
const signed_bitcount = plain_bitcount + 1;
|
||||||
|
const bytecount = (signed_bitcount + 7) >> 3;
|
||||||
|
this.header(1, 0, bytecount);
|
||||||
|
const enc = (n, x) => {
|
||||||
|
if (n > 0) {
|
||||||
|
enc(n - 1, x >> 8);
|
||||||
|
this.emitbyte(x & 255);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
enc(bytecount, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
encodecollection(minor, items) {
|
||||||
|
this.header(3, minor, items.size);
|
||||||
|
for (const item of items) { this.push(item); }
|
||||||
|
}
|
||||||
|
|
||||||
|
encodestream(t, n, items) {
|
||||||
|
const tn = ((t & 3) << 2) | (n & 3);
|
||||||
|
this.header(0, 2, tn);
|
||||||
|
for (const item of items) { this.push(item); }
|
||||||
|
this.header(0, 3, tn);
|
||||||
|
}
|
||||||
|
|
||||||
|
push(v) {
|
||||||
|
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
|
||||||
|
v[PreserveOn](this);
|
||||||
|
}
|
||||||
|
else if (typeof v === 'boolean') {
|
||||||
|
this.leadbyte(0, 0, v ? 1 : 0);
|
||||||
|
}
|
||||||
|
else if (typeof v === 'number') {
|
||||||
|
if (v >= -3 && v <= 12) {
|
||||||
|
this.leadbyte(0, 1, v >= 0 ? v : v + 16);
|
||||||
|
} else {
|
||||||
|
this.encodeint(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (typeof v === 'string') {
|
||||||
|
const bs = Bytes(v)._view;
|
||||||
|
this.header(1, 1, bs.length);
|
||||||
|
this.emitbytes(bs);
|
||||||
|
}
|
||||||
|
else if (typeof v === 'symbol') {
|
||||||
|
const key = Symbol.keyFor(v);
|
||||||
|
if (key === void 0) throw new EncodeError("Cannot preserve non-global Symbol", v);
|
||||||
|
const bs = Bytes(key)._view;
|
||||||
|
this.header(1, 3, bs.length);
|
||||||
|
this.emitbytes(bs);
|
||||||
|
}
|
||||||
|
else if (ArrayBuffer.isView(v)) {
|
||||||
|
if (v instanceof Uint8Array) {
|
||||||
|
this.header(1, 2, v.length);
|
||||||
|
this.emitbytes(v);
|
||||||
|
} else {
|
||||||
|
const bs = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
|
||||||
|
this.header(1, 2, bs.length);
|
||||||
|
this.emitbytes(bs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (List.isList(v)) {
|
||||||
|
this.encodecollection(0, v);
|
||||||
|
}
|
||||||
|
else if (Set.isSet(v)) {
|
||||||
|
this.encodecollection(1, v);
|
||||||
|
}
|
||||||
|
else if (Map.isMap(v)) {
|
||||||
|
this.encodecollection(2, List().withMutations((l) => {
|
||||||
|
v.forEach((val, key) => { l.push(key).push(val); });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') {
|
||||||
|
this.encodestream(3, 0, v);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new EncodeError("Cannot encode", v);
|
||||||
|
}
|
||||||
|
return this; // for chaining
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(module.exports, {
|
||||||
|
DecodeError,
|
||||||
|
EncodeError,
|
||||||
|
Decoder,
|
||||||
|
Encoder,
|
||||||
|
});
|
|
@ -1,419 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Immutable = require('immutable');
|
Object.assign(module.exports, require('./symbols.js'));
|
||||||
const { List, Map, Set, fromJS } = Immutable;
|
Object.assign(module.exports, require('./codec.js'));
|
||||||
|
Object.assign(module.exports, require('./values.js'));
|
||||||
const PreserveOn = Symbol('PreserveOn');
|
|
||||||
|
|
||||||
class DecodeError extends Error {}
|
|
||||||
class EncodeError extends Error {
|
|
||||||
constructor(message, irritant) {
|
|
||||||
super(message);
|
|
||||||
this.irritant = irritant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Decoder {
|
|
||||||
constructor(packet, options) {
|
|
||||||
options = options || {};
|
|
||||||
this.packet = packet;
|
|
||||||
this.index = 0;
|
|
||||||
this.shortForms = options.shortForms || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
peekbyte() {
|
|
||||||
if (this.index >= this.packet.length) throw new DecodeError("Short packet");
|
|
||||||
// ^ NOTE: greater-than-or-equal-to, not greater-than.
|
|
||||||
return this.packet[this.index];
|
|
||||||
}
|
|
||||||
|
|
||||||
advance(count) {
|
|
||||||
const start = this.index;
|
|
||||||
this.index += (count === void 0 ? 1 : count);
|
|
||||||
return start;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextbyte() {
|
|
||||||
const val = this.peekbyte();
|
|
||||||
this.advance();
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
wirelength(arg) {
|
|
||||||
if (arg < 15) return arg;
|
|
||||||
return this.varint();
|
|
||||||
}
|
|
||||||
|
|
||||||
varint() {
|
|
||||||
// TODO: Bignums :-/
|
|
||||||
const v = this.nextbyte();
|
|
||||||
if (v < 128) return v;
|
|
||||||
return (this.varint() << 7) + (v - 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
nextbytes(n) {
|
|
||||||
const start = this.advance(n);
|
|
||||||
if (this.index > this.packet.length) throw new DecodeError("Short packet");
|
|
||||||
// ^ NOTE: greater-than, not greater-than-or-equal-to.
|
|
||||||
return this.packet.slice(start, this.index); // pretend it is immutable, please
|
|
||||||
}
|
|
||||||
|
|
||||||
nextvalues(n) {
|
|
||||||
const result = [];
|
|
||||||
for (let i = 0; i < n; i++) result.push(this.next());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
peekop() {
|
|
||||||
const b = this.peekbyte();
|
|
||||||
const major = b >> 6;
|
|
||||||
const minor = (b >> 4) & 3;
|
|
||||||
const arg = b & 15;
|
|
||||||
return [major, minor, arg];
|
|
||||||
}
|
|
||||||
|
|
||||||
nextop() {
|
|
||||||
const op = this.peekop();
|
|
||||||
this.advance();
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
|
|
||||||
peekend(arg) {
|
|
||||||
const [a,i,r] = this.peekop();
|
|
||||||
return (a === 0) && (i === 3) && (r === arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
binarystream(arg, minor) {
|
|
||||||
const result = [];
|
|
||||||
while (!this.peekend(arg)) {
|
|
||||||
const chunk = this.next();
|
|
||||||
if (!Buffer.isBuffer(chunk)) throw new DecodeError("Unexpected non-binary chunk");
|
|
||||||
result.push(chunk);
|
|
||||||
}
|
|
||||||
return Buffer.concat(result); // pretend it is immutable, please
|
|
||||||
}
|
|
||||||
|
|
||||||
valuestream(arg, minor, decoder) {
|
|
||||||
const result = [];
|
|
||||||
while (!this.peekend(arg)) result.push(this.next());
|
|
||||||
return decoder(minor, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeint(bs) {
|
|
||||||
// TODO: Bignums :-/
|
|
||||||
if (bs.length === 0) return 0;
|
|
||||||
let acc = bs[0];
|
|
||||||
if (acc & 0x80) acc -= 256;
|
|
||||||
for (let i = 1; i < bs.length; i++) acc = (acc << 8) | bs[i];
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
decodebinary(minor, bs) {
|
|
||||||
switch (minor) {
|
|
||||||
case 0: return this.decodeint(bs);
|
|
||||||
case 1: return bs.toString('utf-8');
|
|
||||||
case 2: return bs; // again, pretend it is immutable, please
|
|
||||||
case 3: return Symbol.for(bs.toString('utf-8'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
decoderecord(minor, vs) {
|
|
||||||
if (minor === 3) {
|
|
||||||
if (vs.length === 0) throw new DecodeError("Too few elements in encoded record");
|
|
||||||
return new Record(vs[0], vs.slice(1));
|
|
||||||
} else {
|
|
||||||
const label = this.shortForms[minor];
|
|
||||||
if (label === void 0) throw new DecodeError("Use of unconfigured short form " + minor);
|
|
||||||
return new Record(label, vs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
decodecollection(minor, vs) {
|
|
||||||
switch (minor) {
|
|
||||||
case 0: return List(vs);
|
|
||||||
case 1: return Set(vs);
|
|
||||||
case 2: return this.mapFromArray(vs);
|
|
||||||
case 3: throw new DecodeError("Invalid collection type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mapFromArray(vs) {
|
|
||||||
return Map().withMutations((m) => {
|
|
||||||
for (let i = 0; i < vs.length; i += 2) {
|
|
||||||
m.set(vs[i], vs[i+1]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
next() {
|
|
||||||
const [major, minor, arg] = this.nextop();
|
|
||||||
switch (major) {
|
|
||||||
case 0:
|
|
||||||
switch (minor) {
|
|
||||||
case 0:
|
|
||||||
switch (arg) {
|
|
||||||
case 0: return false;
|
|
||||||
case 1: return true;
|
|
||||||
case 2: return new Single(this.nextbytes(4).readFloatBE());
|
|
||||||
case 3: return new Double(this.nextbytes(8).readDoubleBE());
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
return (arg > 12) ? arg - 16 : arg;
|
|
||||||
case 2: {
|
|
||||||
const t = arg >> 2;
|
|
||||||
const n = arg & 3;
|
|
||||||
switch (t) {
|
|
||||||
case 0: throw new DecodeError("Invalid format C start byte");
|
|
||||||
case 1: return this.binarystream(arg, n);
|
|
||||||
case 2: return this.valuestream(arg, n, this.decoderecord.bind(this));
|
|
||||||
case 3: return this.valuestream(arg, n, this.decodecollection.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
throw new DecodeError("Invalid format C end byte");
|
|
||||||
}
|
|
||||||
case 1: return this.decodebinary(minor, this.nextbytes(this.wirelength(arg)));
|
|
||||||
case 2: return this.decoderecord(minor, this.nextvalues(this.wirelength(arg)));
|
|
||||||
case 3: return this.decodecollection(minor, this.nextvalues(this.wirelength(arg)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Encoder {
|
|
||||||
constructor(options) {
|
|
||||||
options = options || {}
|
|
||||||
this.chunks = [];
|
|
||||||
this.buffer = Buffer.alloc(256);
|
|
||||||
this.index = 0;
|
|
||||||
this.shortForms = options.shortForms || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
contents() {
|
|
||||||
this.rotatebuffer(4096);
|
|
||||||
return Buffer.concat(this.chunks);
|
|
||||||
}
|
|
||||||
|
|
||||||
rotatebuffer(size) {
|
|
||||||
this.chunks.push(this.buffer.slice(0, this.index));
|
|
||||||
this.buffer = Buffer.alloc(size);
|
|
||||||
this.index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
makeroom(amount) {
|
|
||||||
if (this.index + amount > this.buffer.length) {
|
|
||||||
this.rotatebuffer(amount + 4096);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emitbyte(b) {
|
|
||||||
this.makeroom(1);
|
|
||||||
this.buffer[this.index++] = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitbytes(bs) {
|
|
||||||
this.makeroom(bs.length);
|
|
||||||
this.buffer.fill(bs, this.index, this.index + bs.length);
|
|
||||||
this.index += bs.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
varint(v) {
|
|
||||||
while (v >= 128) {
|
|
||||||
this.emitbyte((v % 128) + 128);
|
|
||||||
v = Math.floor(v / 128);
|
|
||||||
}
|
|
||||||
this.emitbyte(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
leadbyte(major, minor, arg) {
|
|
||||||
this.emitbyte(((major & 3) << 6) | ((minor & 3) << 4) | (arg & 15));
|
|
||||||
}
|
|
||||||
|
|
||||||
header(major, minor, wirelength) {
|
|
||||||
if (wirelength < 15) {
|
|
||||||
this.leadbyte(major, minor, wirelength);
|
|
||||||
} else {
|
|
||||||
this.leadbyte(major, minor, 15);
|
|
||||||
this.varint(wirelength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
encodeint(v) {
|
|
||||||
// TODO: Bignums :-/
|
|
||||||
const plain_bitcount = Math.floor(Math.log2(v > 0 ? v : ~v)) + 1;
|
|
||||||
const signed_bitcount = plain_bitcount + 1;
|
|
||||||
const bytecount = (signed_bitcount + 7) >> 3;
|
|
||||||
this.header(1, 0, bytecount);
|
|
||||||
const enc = (n, x) => {
|
|
||||||
if (n > 0) {
|
|
||||||
enc(n - 1, x >> 8);
|
|
||||||
this.emitbyte(x & 255);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
enc(bytecount, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
encodecollection(minor, items) {
|
|
||||||
this.header(3, minor, items.size);
|
|
||||||
for (const item of items) { this.push(item); }
|
|
||||||
}
|
|
||||||
|
|
||||||
encodestream(t, n, items) {
|
|
||||||
const tn = ((t & 3) << 2) | (n & 3);
|
|
||||||
this.header(0, 2, tn);
|
|
||||||
for (const item of items) { this.push(item); }
|
|
||||||
this.header(0, 3, tn);
|
|
||||||
}
|
|
||||||
|
|
||||||
push(v) {
|
|
||||||
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
|
|
||||||
v[PreserveOn](this);
|
|
||||||
}
|
|
||||||
else if (typeof v === 'boolean') {
|
|
||||||
this.leadbyte(0, 0, v ? 1 : 0);
|
|
||||||
}
|
|
||||||
else if (typeof v === 'number') {
|
|
||||||
if (v >= -3 && v <= 12) {
|
|
||||||
this.leadbyte(0, 1, v >= 0 ? v : v + 16);
|
|
||||||
} else {
|
|
||||||
this.encodeint(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (typeof v === 'string') {
|
|
||||||
const bs = Buffer.from(v);
|
|
||||||
this.header(1, 1, bs.length);
|
|
||||||
this.emitbytes(bs);
|
|
||||||
}
|
|
||||||
else if (typeof v === 'symbol') {
|
|
||||||
const key = Symbol.keyFor(v);
|
|
||||||
if (key === void 0) throw new EncodeError("Cannot preserve non-global Symbol", v);
|
|
||||||
const bs = Buffer.from(key);
|
|
||||||
this.header(1, 3, bs.length);
|
|
||||||
this.emitbytes(bs);
|
|
||||||
}
|
|
||||||
else if (Buffer.isBuffer(v)) {
|
|
||||||
this.header(1, 2, v.length);
|
|
||||||
this.emitbytes(v);
|
|
||||||
}
|
|
||||||
else if (List.isList(v)) {
|
|
||||||
this.encodecollection(0, v);
|
|
||||||
}
|
|
||||||
else if (Set.isSet(v)) {
|
|
||||||
this.encodecollection(1, v);
|
|
||||||
}
|
|
||||||
else if (Map.isMap(v)) {
|
|
||||||
this.encodecollection(2, List().withMutations((l) => {
|
|
||||||
v.forEach((val, key) => { l.push(key).push(val); });
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') {
|
|
||||||
this.encodestream(3, 0, v);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new EncodeError("Cannot encode", v);
|
|
||||||
}
|
|
||||||
return this; // for chaining
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Record {
|
|
||||||
constructor(label, fields) {
|
|
||||||
this.label = fromJS(label);
|
|
||||||
this.fields = fromJS(fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(other) {
|
|
||||||
return (other instanceof Record) &&
|
|
||||||
Immutable.is(this.label, other.label) &&
|
|
||||||
Immutable.is(this.fields, other.fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
hashCode() {
|
|
||||||
return Immutable.List([this.label, this.fields]).hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
get(index, defaultValue) {
|
|
||||||
return this.fields.get(index, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(index, newValue) {
|
|
||||||
return new Record(this.label, this.fields.set(index, newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
[PreserveOn](encoder) {
|
|
||||||
if (Immutable.is(encoder.shortForms[0], this.label)) {
|
|
||||||
encoder.header(2, 0, this.fields.size);
|
|
||||||
} else if (Immutable.is(encoder.shortForms[1], this.label)) {
|
|
||||||
encoder.header(2, 1, this.fields.size);
|
|
||||||
} else if (Immutable.is(encoder.shortForms[2], this.label)) {
|
|
||||||
encoder.header(2, 2, this.fields.size);
|
|
||||||
} else {
|
|
||||||
encoder.header(2, 3, this.fields.size + 1);
|
|
||||||
encoder.push(this.label);
|
|
||||||
}
|
|
||||||
for (const field of this.fields) { encoder.push(field); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Record.makeConstructor = function (labelSymbolText, fieldNames) {
|
|
||||||
return Record.makeBasicConstructor(Symbol.for(labelSymbolText), fieldNames);
|
|
||||||
};
|
|
||||||
|
|
||||||
Record.makeBasicConstructor = function (label, fieldNames) {
|
|
||||||
const arity = fieldNames.length;
|
|
||||||
const ctor = (...fields) => {
|
|
||||||
if (fields.length !== arity) {
|
|
||||||
throw new Error("Record: cannot instantiate " + (label && label.toString()) +
|
|
||||||
" expecting " + arity + " fields with " + fields.length + " fields");
|
|
||||||
}
|
|
||||||
return new Record(label, fields);
|
|
||||||
};
|
|
||||||
ctor.meta = label;
|
|
||||||
ctor.isClassOf = (v) => ((v instanceof Record) && Immutable.is(label, v.label));
|
|
||||||
return ctor;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Float {
|
|
||||||
constructor(value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashCode() {
|
|
||||||
return this.value | 0; // TODO: something better?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Single extends Float {
|
|
||||||
equals(other) {
|
|
||||||
return (other instanceof Single) && (other.value === this.value);
|
|
||||||
}
|
|
||||||
[PreserveOn](encoder) {
|
|
||||||
encoder.leadbyte(0, 0, 2);
|
|
||||||
encoder.makeroom(4);
|
|
||||||
encoder.buffer.writeFloatBE(this.value, encoder.index);
|
|
||||||
encoder.index += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Double extends Float {
|
|
||||||
equals(other) {
|
|
||||||
return (other instanceof Double) && (other.value === this.value);
|
|
||||||
}
|
|
||||||
[PreserveOn](encoder) {
|
|
||||||
encoder.leadbyte(0, 0, 3);
|
|
||||||
encoder.makeroom(8);
|
|
||||||
encoder.buffer.writeDoubleBE(this.value, encoder.index);
|
|
||||||
encoder.index += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(module.exports, {
|
|
||||||
PreserveOn,
|
|
||||||
DecodeError,
|
|
||||||
EncodeError,
|
|
||||||
Decoder,
|
|
||||||
Encoder,
|
|
||||||
Record,
|
|
||||||
Float,
|
|
||||||
Single,
|
|
||||||
Double,
|
|
||||||
});
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
"use strict";
|
||||||
|
// Symbols for various Preserves protocols.
|
||||||
|
|
||||||
|
const PreserveOn = Symbol('PreserveOn');
|
||||||
|
const AsPreserve = Symbol('AsPreserve');
|
||||||
|
|
||||||
|
Object.assign(module.exports, {
|
||||||
|
PreserveOn,
|
||||||
|
AsPreserve,
|
||||||
|
});
|
|
@ -0,0 +1,284 @@
|
||||||
|
"use strict";
|
||||||
|
// Preserves Values.
|
||||||
|
// Uses Immutable.js for many things; adds immutable values of its own for the rest.
|
||||||
|
|
||||||
|
const Immutable = require('immutable');
|
||||||
|
const { List, isList, Map, Set } = Immutable;
|
||||||
|
|
||||||
|
const { PreserveOn, AsPreserve } = require('./symbols.js');
|
||||||
|
|
||||||
|
function _decode(bs) {
|
||||||
|
return Buffer.from(bs.buffer, bs.byteOffset, bs.byteLength).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const encoder = (typeof TextEncoder === 'undefined') ? { encode: Buffer.from } : new TextEncoder();
|
||||||
|
const decoder = (typeof TextDecoder === 'undefined') ? { decode: _decode } : new TextDecoder();
|
||||||
|
|
||||||
|
function fromJS(x) {
|
||||||
|
switch (typeof x) {
|
||||||
|
case 'number':
|
||||||
|
if (!Number.isInteger(x)) {
|
||||||
|
// We require that clients be explicit about integer vs. non-integer types.
|
||||||
|
throw new TypeError("Refusing to autoconvert non-integer number to Single or Double");
|
||||||
|
}
|
||||||
|
// FALL THROUGH
|
||||||
|
case 'string':
|
||||||
|
case 'symbol':
|
||||||
|
case 'boolean':
|
||||||
|
return x;
|
||||||
|
case 'undefined':
|
||||||
|
throw new TypeError("Cannot represent JavaScript undefined value as Preserves");
|
||||||
|
case 'function':
|
||||||
|
// We are relaxed about these, for now, even though Preserves can't serialize them.
|
||||||
|
return x;
|
||||||
|
case 'object':
|
||||||
|
if (x === null) {
|
||||||
|
// We are relaxed about these, for now, even though null is
|
||||||
|
// strictly speaking not a Preserves Value.
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
if (typeof x[AsPreserve] === 'function') {
|
||||||
|
return x[AsPreserve]();
|
||||||
|
}
|
||||||
|
if (Array.isArray(x)) {
|
||||||
|
return List().withMutations((l) => {
|
||||||
|
for (let i = 0; i < x.length; i++) {
|
||||||
|
l.push(fromJS(x[i]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
||||||
|
return Bytes(x);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _Float(value) {
|
||||||
|
Object.defineProperty(this, 'value', {
|
||||||
|
value: value,
|
||||||
|
writable: false,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function Float(value) {
|
||||||
|
if (!(this instanceof Float)) return new Float(value);
|
||||||
|
_Float.call(this, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Float.prototype.hashCode = function () {
|
||||||
|
return this.value | 0; // TODO: something better?
|
||||||
|
};
|
||||||
|
|
||||||
|
function Single(value) {
|
||||||
|
if (!(this instanceof Single)) return new Single(value);
|
||||||
|
_Float.call(this, value);
|
||||||
|
}
|
||||||
|
Single.prototype = Float(NaN);
|
||||||
|
|
||||||
|
Single.prototype.equals = function (other) {
|
||||||
|
return (other instanceof Single) && (other.value === this.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
Single.prototype[PreserveOn] = function (encoder) {
|
||||||
|
encoder.leadbyte(0, 0, 2);
|
||||||
|
encoder.makeroom(4);
|
||||||
|
encoder.view.setFloat32(encoder.index, this.value, false);
|
||||||
|
encoder.index += 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Double(value) {
|
||||||
|
if (!(this instanceof Double)) return new Double(value);
|
||||||
|
_Float.call(this, value);
|
||||||
|
}
|
||||||
|
Double.prototype = Float(NaN);
|
||||||
|
|
||||||
|
Double.prototype.equals = function (other) {
|
||||||
|
return (other instanceof Double) && (other.value === this.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
Double.prototype[PreserveOn] = function (encoder) {
|
||||||
|
encoder.leadbyte(0, 0, 3);
|
||||||
|
encoder.makeroom(8);
|
||||||
|
encoder.view.setFloat64(encoder.index, this.value, false);
|
||||||
|
encoder.index += 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Bytes(maybeByteIterable) {
|
||||||
|
if (!(this instanceof Bytes)) return new Bytes(maybeByteIterable);
|
||||||
|
if (maybeByteIterable === void 0) {
|
||||||
|
_installView.call(this, new Uint8Array());
|
||||||
|
} else if (ArrayBuffer.isView(maybeByteIterable)) {
|
||||||
|
_installView.call(this, new Uint8Array(maybeByteIterable.buffer,
|
||||||
|
maybeByteIterable.byteOffset,
|
||||||
|
maybeByteIterable.byteLength));
|
||||||
|
} else if (maybeByteIterable instanceof ArrayBuffer) {
|
||||||
|
_installView.call(this, new Uint8Array(maybeByteIterable.slice()));
|
||||||
|
} else if (typeof maybeByteIterable === 'string') {
|
||||||
|
_installView.call(this, encoder.encode(maybeByteIterable));
|
||||||
|
} else if (typeof maybeByteIterable === 'number') {
|
||||||
|
_installView.call(this, new Uint8Array(maybeByteIterable));
|
||||||
|
} else if (typeof maybeByteIterable.length === 'number') {
|
||||||
|
_installView.call(this, Uint8Array.from(maybeByteIterable));
|
||||||
|
} else if (typeof maybeByteIterable.size === 'number') {
|
||||||
|
_installView.call(this, new ArrayBuffer(maybeByteIterable.size));
|
||||||
|
for (let i = 0; i < this.size; i++) { this._view[i] = maybeByteIterable.get(i); }
|
||||||
|
} else {
|
||||||
|
const e = new TypeError("Attempt to initialize Bytes from unsupported value");
|
||||||
|
e.irritant = maybeByteIterable;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bytes.from = Bytes;
|
||||||
|
|
||||||
|
function unhexDigit(asciiCode) {
|
||||||
|
if (asciiCode >= 48 && asciiCode <= 57) return asciiCode - 48;
|
||||||
|
if (asciiCode >= 97 && asciiCode <= 102) return asciiCode - 97 + 10;
|
||||||
|
if (asciiCode >= 65 && asciiCode <= 70) return asciiCode - 65 + 10;
|
||||||
|
throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
Bytes.fromHex = function (s) {
|
||||||
|
if (s.length & 1) throw new Error("Cannot decode odd-length hexadecimal string");
|
||||||
|
const len = s.length >> 1;
|
||||||
|
const result = Bytes(len);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
result._view[i] =
|
||||||
|
(unhexDigit(s.charCodeAt(i << 1)) << 4) | unhexDigit(s.charCodeAt((i << 1) + 1));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function underlying(b) {
|
||||||
|
return b._view || b;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bytes.concat = function (bss) {
|
||||||
|
let len = 0;
|
||||||
|
for (let i = 0; i < bss.length; i++) { len += underlying(bss[i]).length; }
|
||||||
|
|
||||||
|
const result = Bytes(len);
|
||||||
|
let index = 0;
|
||||||
|
for (let i = 0; i < bss.length; i++) {
|
||||||
|
const bs = underlying(bss[i]);
|
||||||
|
result._view.set(bs, index);
|
||||||
|
index += bs.length;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bytes.prototype.equals = function (other) {
|
||||||
|
if (!(other instanceof Bytes)) return false;
|
||||||
|
if (other.size !== this.size) return false;
|
||||||
|
const va = this._view;
|
||||||
|
const vb = other._view;
|
||||||
|
for (let i = 0; i < va.length; i++) {
|
||||||
|
if (va[i] !== vb[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bytes.prototype.hashCode = function () {
|
||||||
|
// Immutable.js uses this function for strings.
|
||||||
|
const v = this._view;
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < v.length; i++) {
|
||||||
|
hash = ((31 * hash) + v[i]) | 0;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bytes.decodeUtf8 = function (bs) {
|
||||||
|
return decoder.decode(underlying(bs));
|
||||||
|
};
|
||||||
|
|
||||||
|
Bytes.prototype.toString = function () {
|
||||||
|
return decoder.decode(this._view);
|
||||||
|
};
|
||||||
|
|
||||||
|
function hexDigit(n) { return '0123456789abcdef'[n]; }
|
||||||
|
|
||||||
|
Bytes.prototype.toHex = function () {
|
||||||
|
var nibbles = [];
|
||||||
|
for (let i = 0; i < this.size; i++) {
|
||||||
|
nibbles.push(hexDigit(this._view[i] >> 4));
|
||||||
|
nibbles.push(hexDigit(this._view[i] & 15));
|
||||||
|
}
|
||||||
|
return nibbles.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
Bytes.prototype[PreserveOn] = function (encoder) {
|
||||||
|
encoder.header(1, 2, this.size);
|
||||||
|
encoder.emitbytes(this._view);
|
||||||
|
};
|
||||||
|
|
||||||
|
function _installView(view) {
|
||||||
|
Object.defineProperty(this, '_view', { value: view, writable: false });
|
||||||
|
Object.defineProperty(this, 'size', { value: view.length, writable: false, enumerable: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function Record(label, fields) {
|
||||||
|
if (!(this instanceof Record)) return new Record(label, fields);
|
||||||
|
Object.defineProperty(this, 'label', { value: fromJS(label), writable: false, enumerable: true });
|
||||||
|
Object.defineProperty(this, 'fields', { value: fromJS(fields), writable: false, enumerable: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
Record.prototype.equals = function (other) {
|
||||||
|
return (other instanceof Record) &&
|
||||||
|
Immutable.is(this.label, other.label) &&
|
||||||
|
Immutable.is(this.fields, other.fields);
|
||||||
|
};
|
||||||
|
|
||||||
|
Record.prototype.hashCode = function () {
|
||||||
|
return Immutable.List([this.label, this.fields]).hashCode();
|
||||||
|
};
|
||||||
|
|
||||||
|
Record.prototype.get = function (index, defaultValue) {
|
||||||
|
return this.fields.get(index, defaultValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
Record.prototype.set = function (index, newValue) {
|
||||||
|
return new Record(this.label, this.fields.set(index, newValue));
|
||||||
|
};
|
||||||
|
|
||||||
|
Record.prototype[PreserveOn] = function (encoder) {
|
||||||
|
if (Immutable.is(encoder.shortForms[0], this.label)) {
|
||||||
|
encoder.header(2, 0, this.fields.size);
|
||||||
|
} else if (Immutable.is(encoder.shortForms[1], this.label)) {
|
||||||
|
encoder.header(2, 1, this.fields.size);
|
||||||
|
} else if (Immutable.is(encoder.shortForms[2], this.label)) {
|
||||||
|
encoder.header(2, 2, this.fields.size);
|
||||||
|
} else {
|
||||||
|
encoder.header(2, 3, this.fields.size + 1);
|
||||||
|
encoder.push(this.label);
|
||||||
|
}
|
||||||
|
for (const field of this.fields) { encoder.push(field); }
|
||||||
|
};
|
||||||
|
|
||||||
|
Record.makeConstructor = function (labelSymbolText, fieldNames) {
|
||||||
|
return Record.makeBasicConstructor(Symbol.for(labelSymbolText), fieldNames);
|
||||||
|
};
|
||||||
|
|
||||||
|
Record.makeBasicConstructor = function (label, fieldNames) {
|
||||||
|
const arity = fieldNames.length;
|
||||||
|
const ctor = (...fields) => {
|
||||||
|
if (fields.length !== arity) {
|
||||||
|
throw new Error("Record: cannot instantiate " + (label && label.toString()) +
|
||||||
|
" expecting " + arity + " fields with " + fields.length + " fields");
|
||||||
|
}
|
||||||
|
return new Record(label, fields);
|
||||||
|
};
|
||||||
|
ctor.meta = label;
|
||||||
|
ctor.isClassOf = (v) => ((v instanceof Record) && Immutable.is(label, v.label));
|
||||||
|
return ctor;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(module.exports, {
|
||||||
|
fromJS,
|
||||||
|
List, Map, Set,
|
||||||
|
Float, Single, Double,
|
||||||
|
Bytes,
|
||||||
|
Record,
|
||||||
|
});
|
|
@ -5,10 +5,9 @@ const expect = chai.expect;
|
||||||
chai.use(require('chai-immutable'));
|
chai.use(require('chai-immutable'));
|
||||||
|
|
||||||
const Immutable = require('immutable');
|
const Immutable = require('immutable');
|
||||||
const { List, Set, Map, fromJS } = Immutable;
|
|
||||||
|
|
||||||
const Preserves = require('../src/index.js');
|
const Preserves = require('../src/index.js');
|
||||||
const { Decoder, Encoder, Record, Single, Double } = Preserves;
|
const { List, Set, Map, Decoder, Encoder, Bytes, Record, Single, Double } = Preserves;
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
@ -25,8 +24,8 @@ const Observe = Record.makeConstructor('observe', ['pattern']);
|
||||||
|
|
||||||
describe('hex samples', () => {
|
describe('hex samples', () => {
|
||||||
const samples = fs.readFileSync(__dirname + '/samples.txt').toString().split(/\n/)
|
const samples = fs.readFileSync(__dirname + '/samples.txt').toString().split(/\n/)
|
||||||
.filter((h) => h)
|
.filter((h) => h) // filters out empty lines
|
||||||
.map((h) => Buffer.from(h, 'hex'));
|
.map(Bytes.fromHex);
|
||||||
|
|
||||||
function manyFalses(n) {
|
function manyFalses(n) {
|
||||||
return List().withMutations((l) => {
|
return List().withMutations((l) => {
|
||||||
|
@ -45,12 +44,13 @@ describe('hex samples', () => {
|
||||||
{ expected: -3, },
|
{ expected: -3, },
|
||||||
{ expected: -2, },
|
{ expected: -2, },
|
||||||
{ expected: -1, },
|
{ expected: -1, },
|
||||||
{ expected: Buffer.from("hello"), encodesTo: '6568656c6c6f', },
|
{ expected: "hello", encodesTo: '5568656c6c6f', },
|
||||||
{ expected: Buffer.from("hello"), encodesTo: '6568656c6c6f', },
|
{ expected: "hello", encodesTo: '5568656c6c6f', },
|
||||||
{ expected: Buffer.from("hello"), encodesTo: '6568656c6c6f', },
|
{ expected: Bytes.from("hello"), encodesTo: '6568656c6c6f', },
|
||||||
{ expected: Buffer.from("hello"), encodesTo: '6568656c6c6f', },
|
{ expected: Symbol.for("hello"), encodesTo: '7568656c6c6f', },
|
||||||
{ expected: Immutable.Seq([1, 2, 3, 4]), },
|
{ expected: Immutable.Seq([1, 2, 3, 4]), },
|
||||||
{ expected: fromJS([["a", 1], ["b", 2], ["c", 3]]), encodesTo: 'c3c2516111c2516212c2516313', },
|
{ expected: Preserves.fromJS([["a", 1], ["b", 2], ["c", 3]]),
|
||||||
|
encodesTo: 'c3c2516111c2516212c2516313', },
|
||||||
{ expected: 13, },
|
{ expected: 13, },
|
||||||
{ expected: 127, },
|
{ expected: 127, },
|
||||||
{ expected: -128, },
|
{ expected: -128, },
|
||||||
|
@ -70,7 +70,7 @@ describe('hex samples', () => {
|
||||||
{ expected: 65536, },
|
{ expected: 65536, },
|
||||||
{ expected: 131072, },
|
{ expected: 131072, },
|
||||||
{ expected: "hello", },
|
{ expected: "hello", },
|
||||||
{ expected: Buffer.from("hello"), },
|
{ expected: Bytes.from("hello"), },
|
||||||
{ expected: Symbol.for("hello"), },
|
{ expected: Symbol.for("hello"), },
|
||||||
{ expected: Capture(Discard()), },
|
{ expected: Capture(Discard()), },
|
||||||
{ expected: Observe(new Record(Symbol.for('speak'), [Discard(), Capture(Discard())])), },
|
{ expected: Observe(new Record(Symbol.for('speak'), [Discard(), Capture(Discard())])), },
|
||||||
|
@ -79,8 +79,13 @@ describe('hex samples', () => {
|
||||||
[101, "Blackwell", new Record(Symbol.for('date'), [1821, 2, 3]), "Dr"]), },
|
[101, "Blackwell", new Record(Symbol.for('date'), [1821, 2, 3]), "Dr"]), },
|
||||||
{ expected: List([1, 2, 3, 4]), },
|
{ expected: List([1, 2, 3, 4]), },
|
||||||
{ expected: List([-2, -1, 0, 1]), },
|
{ expected: List([-2, -1, 0, 1]), },
|
||||||
{ expected:
|
{ expected: Preserves.fromJS(["hello",
|
||||||
fromJS(["hello", Symbol.for('there'), Buffer.from('world'), [], Set(), true, false]), },
|
Symbol.for('there'),
|
||||||
|
Bytes.from('world'),
|
||||||
|
[],
|
||||||
|
Set(),
|
||||||
|
true,
|
||||||
|
false]), },
|
||||||
{ expected: manyFalses(14), },
|
{ expected: manyFalses(14), },
|
||||||
{ expected: manyFalses(15), },
|
{ expected: manyFalses(15), },
|
||||||
{ expected: manyFalses(100), },
|
{ expected: manyFalses(100), },
|
||||||
|
@ -89,13 +94,13 @@ describe('hex samples', () => {
|
||||||
Map()
|
Map()
|
||||||
.set(Symbol.for('a'), 1)
|
.set(Symbol.for('a'), 1)
|
||||||
.set('b', true)
|
.set('b', true)
|
||||||
.set(fromJS([1, 2, 3]), Buffer.from('c'))
|
.set(Preserves.fromJS([1, 2, 3]), Bytes.from('c'))
|
||||||
.set(Map().set(Symbol.for('first-name'), 'Elizabeth'),
|
.set(Map().set(Symbol.for('first-name'), 'Elizabeth'),
|
||||||
Map().set(Symbol.for('surname'), 'Blackwell')), },
|
Map().set(Symbol.for('surname'), 'Blackwell')), },
|
||||||
];
|
];
|
||||||
|
|
||||||
samples.forEach((s, sampleIndex) => {
|
samples.forEach((s, sampleIndex) => {
|
||||||
it('[' + sampleIndex + '] ' + s.toString('hex') + ' should decode OK', () => {
|
it('[' + sampleIndex + '] ' + s.toHex() + ' should decode OK', () => {
|
||||||
const actual = new Decoder(s, { shortForms }).next();
|
const actual = new Decoder(s, { shortForms }).next();
|
||||||
const expected = samplesExpected[sampleIndex].expected;
|
const expected = samplesExpected[sampleIndex].expected;
|
||||||
expect(Immutable.is(actual, expected),
|
expect(Immutable.is(actual, expected),
|
||||||
|
@ -106,11 +111,11 @@ describe('hex samples', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
samples.forEach((s, sampleIndex) => {
|
samples.forEach((s, sampleIndex) => {
|
||||||
it('[' + sampleIndex + '] ' + s.toString('hex') + ' should encode OK', () => {
|
it('[' + sampleIndex + '] ' + s.toHex() + ' should encode OK', () => {
|
||||||
const entry = samplesExpected[sampleIndex];
|
const entry = samplesExpected[sampleIndex];
|
||||||
const actual = entry.encodesTo || s;
|
const actualHex = entry.encodesTo || s.toHex();
|
||||||
const expected = new Encoder({ shortForms }).push(entry.expected).contents();
|
const expected = new Encoder({ shortForms }).push(entry.expected).contents();
|
||||||
expect(actual.toString('hex')).to.equal(expected.toString('hex'));
|
expect(actualHex).to.equal(expected.toHex());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue