This commit is contained in:
Tony Garnock-Jones 2019-08-24 19:08:07 +01:00
parent 2b74100b2a
commit 738a47ce90
4 changed files with 95 additions and 53 deletions

View File

@ -0,0 +1,54 @@
"use strict";
// Preserves Annotations.
if (require('./singletonmodule.js')('leastfixedpoint.com/preserves',
require('../package.json').version,
'annotations.js',
module)) return;
const { Record, List, Map, Set } = require('./values.js');
const { PreserveOn, AsPreserve } = require('./symbols.js');
function Annotated(item) {
this.annotations = [];
this.item = item;
}
Annotated.prototype[AsPreserve] = function () {
return this;
};
Annotated.prototype[PreserveOn] = function (encoder) {
for (const a of this.annotations) {
encoder.header(0, 0, 5);
encoder.push(a);
}
encoder.push(this.item);
};
function stripAnnotations(v, depth) {
function step(v, depth) {
if (depth === 0) return v;
if (!(v instanceof Annotated)) return v;
const nextDepth = depth - 1;
function walk(v) { return step(v, nextDepth); }
if (v.item instanceof Record) {
return Record(walk(v.item.label), v.item.fields.map(walk));
} else if (List.isList(v.item)) {
return v.item.map(walk);
} else if (Set.isSet(v.item)) {
return v.item.map(walk);
} else if (Map.isMap(v.item)) {
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
} else if (v.item instanceof Annotated) {
const e = new Error("Improper annotation structure");
e.irritant = v;
throw e;
} else {
return v.item;
}
}
step(v, (depth === void 0) ? Infinity : depth);
}

View File

@ -7,6 +7,7 @@ if (require('./singletonmodule.js')('leastfixedpoint.com/preserves',
module)) return;
const Values = require('./values.js');
const Annotations = require('./annotations.js');
const { List, Map, Set, Bytes, Record, Single, Double } = Values;
const { PreserveOn } = require('./symbols.js');
@ -27,7 +28,7 @@ class Decoder {
? (packet._view || packet) // strip off Bytes wrapper, if any
: new Uint8Array(0);
this.index = 0;
this.shortForms = options.shortForms || {};
this.includeAnnotations = options.includeAnnotations || false;
}
write(data) {
@ -75,16 +76,15 @@ class Decoder {
return [major, minor, arg];
}
peekend(arg) {
const [a,i,r] = this.nextop();
const result = (a === 0) && (i === 3) && (r === arg);
peekend() {
const result = this.nextop() === 4;
if (!result) this.index--;
return result;
}
binarystream(arg, minor) {
binarystream(minor) {
const result = [];
while (!this.peekend(arg)) {
while (!this.peekend()) {
const chunk = this.next();
if (ArrayBuffer.isView(chunk)) {
result.push(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
@ -99,10 +99,10 @@ class Decoder {
return this.decodebinary(minor, Bytes.concat(result));
}
valuestream(arg, minor, decoder) {
valuestream(minor) {
const result = [];
while (!this.peekend(arg)) result.push(this.next());
return decoder(minor, result);
while (!this.peekend()) result.push(this.next());
return this.decodecompound(minor, result);
}
decodeint(bs) {
@ -123,23 +123,15 @@ class Decoder {
}
}
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) {
decodecompound(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");
case 0: {
if (vs.length === 0) throw new DecodeError("Too few elements in encoded record");
return new Record(vs[0], vs.slice(1));
}
case 1: return List(vs);
case 2: return Set(vs);
case 3: return this.mapFromArray(vs);
}
}
@ -162,28 +154,31 @@ class Decoder {
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 4: throw new DecodeError("Unexpected end-of-stream marker");
case 5: ...annotation...;
default: throw new DecodeError("Illegal format A lead byte");
}
case 1:
return (arg > 12) ? arg - 16 : arg;
...placeholder...;
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 0: throw new DecodeError("Invalid format C start byte (0)");
case 1: return this.binarystream(n);
case 2: return this.valuestream(n);
case 3: throw new DecodeError("Invalid format C start byte (3)");
}
}
case 3:
throw new DecodeError("Invalid format C end byte");
return (arg > 12) ? arg - 16 : arg;
}
case 1:
return this.decodebinary(minor, Bytes.from(this.nextbytes(this.wirelength(arg))));
case 2:
return this.decoderecord(minor, this.nextvalues(this.wirelength(arg)));
return this.decodecompound(minor, this.nextvalues(this.wirelength(arg)));
case 3:
return this.decodecollection(minor, this.nextvalues(this.wirelength(arg)));
throw new DecodeError("Invalid lead byte (major 3)");
}
}
@ -207,7 +202,6 @@ class Encoder {
this.chunks = [];
this.view = new DataView(new ArrayBuffer(256));
this.index = 0;
this.shortForms = options.shortForms || {};
}
contents() {
@ -275,7 +269,7 @@ class Encoder {
}
encodecollection(minor, items) {
this.header(3, minor, items.size);
this.header(2, minor, items.size);
for (const item of items) { this.push(item); }
}
@ -283,10 +277,11 @@ class Encoder {
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);
this.header(0, 0, 4);
}
push(v) {
...placeholder...;
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
v[PreserveOn](this);
}
@ -295,7 +290,7 @@ class Encoder {
}
else if (typeof v === 'number') {
if (v >= -3 && v <= 12) {
this.leadbyte(0, 1, v >= 0 ? v : v + 16);
this.leadbyte(0, 3, v >= 0 ? v : v + 16);
} else {
this.encodeint(v);
}
@ -323,18 +318,18 @@ class Encoder {
}
}
else if (List.isList(v)) {
this.encodecollection(0, v);
}
else if (Set.isSet(v)) {
this.encodecollection(1, v);
}
else if (Set.isSet(v)) {
this.encodecollection(2, v);
}
else if (Map.isMap(v)) {
this.encodecollection(2, List().withMutations((l) => {
this.encodecollection(3, 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);
this.encodestream(2, 0, v);
}
else {
throw new EncodeError("Cannot encode", v);

View File

@ -8,3 +8,4 @@ if (require('./singletonmodule.js')('leastfixedpoint.com/preserves',
Object.assign(module.exports, require('./symbols.js'));
Object.assign(module.exports, require('./codec.js'));
Object.assign(module.exports, require('./values.js'));
Object.assign(module.exports, require('./annotations.js'));

View File

@ -10,7 +10,7 @@ if (require('./singletonmodule.js')('leastfixedpoint.com/preserves',
const util = require('util');
const Immutable = require('immutable');
const { List, isList, Map, Set, is } = Immutable;
const { List, Map, Set, is } = Immutable;
const { PreserveOn, AsPreserve } = require('./symbols.js');
@ -341,16 +341,8 @@ Record.prototype.set = function (index, newValue) {
};
Record.prototype[PreserveOn] = function (encoder) {
if (is(encoder.shortForms[0], this.label)) {
encoder.header(2, 0, this.fields.size);
} else if (is(encoder.shortForms[1], this.label)) {
encoder.header(2, 1, this.fields.size);
} else if (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);
}
encoder.header(2, 0, this.fields.size + 1);
encoder.push(this.label);
for (const field of this.fields) { encoder.push(field); }
};