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; module)) return;
const Values = require('./values.js'); const Values = require('./values.js');
const Annotations = require('./annotations.js');
const { List, Map, Set, Bytes, Record, Single, Double } = Values; const { List, Map, Set, Bytes, Record, Single, Double } = Values;
const { PreserveOn } = require('./symbols.js'); const { PreserveOn } = require('./symbols.js');
@ -27,7 +28,7 @@ class Decoder {
? (packet._view || packet) // strip off Bytes wrapper, if any ? (packet._view || packet) // strip off Bytes wrapper, if any
: new Uint8Array(0); : new Uint8Array(0);
this.index = 0; this.index = 0;
this.shortForms = options.shortForms || {}; this.includeAnnotations = options.includeAnnotations || false;
} }
write(data) { write(data) {
@ -75,16 +76,15 @@ class Decoder {
return [major, minor, arg]; return [major, minor, arg];
} }
peekend(arg) { peekend() {
const [a,i,r] = this.nextop(); const result = this.nextop() === 4;
const result = (a === 0) && (i === 3) && (r === arg);
if (!result) this.index--; if (!result) this.index--;
return result; return result;
} }
binarystream(arg, minor) { binarystream(minor) {
const result = []; const result = [];
while (!this.peekend(arg)) { while (!this.peekend()) {
const chunk = this.next(); const chunk = this.next();
if (ArrayBuffer.isView(chunk)) { if (ArrayBuffer.isView(chunk)) {
result.push(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)); result.push(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
@ -99,10 +99,10 @@ class Decoder {
return this.decodebinary(minor, Bytes.concat(result)); return this.decodebinary(minor, Bytes.concat(result));
} }
valuestream(arg, minor, decoder) { valuestream(minor) {
const result = []; const result = [];
while (!this.peekend(arg)) result.push(this.next()); while (!this.peekend()) result.push(this.next());
return decoder(minor, result); return this.decodecompound(minor, result);
} }
decodeint(bs) { decodeint(bs) {
@ -123,23 +123,15 @@ class Decoder {
} }
} }
decoderecord(minor, vs) { decodecompound(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) { switch (minor) {
case 0: return List(vs); case 0: {
case 1: return Set(vs); if (vs.length === 0) throw new DecodeError("Too few elements in encoded record");
case 2: return this.mapFromArray(vs); return new Record(vs[0], vs.slice(1));
case 3: throw new DecodeError("Invalid collection type"); }
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 1: return true;
case 2: return Single(this.nextbytes(4).getFloat32(0, false)); case 2: return Single(this.nextbytes(4).getFloat32(0, false));
case 3: return Double(this.nextbytes(8).getFloat64(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: case 1:
return (arg > 12) ? arg - 16 : arg; ...placeholder...;
case 2: { case 2: {
const t = arg >> 2; const t = arg >> 2;
const n = arg & 3; const n = arg & 3;
switch (t) { switch (t) {
case 0: throw new DecodeError("Invalid format C start byte"); case 0: throw new DecodeError("Invalid format C start byte (0)");
case 1: return this.binarystream(arg, n); case 1: return this.binarystream(n);
case 2: return this.valuestream(arg, n, this.decoderecord.bind(this)); case 2: return this.valuestream(n);
case 3: return this.valuestream(arg, n, this.decodecollection.bind(this)); case 3: throw new DecodeError("Invalid format C start byte (3)");
} }
} }
case 3: case 3:
throw new DecodeError("Invalid format C end byte"); return (arg > 12) ? arg - 16 : arg;
} }
case 1: case 1:
return this.decodebinary(minor, Bytes.from(this.nextbytes(this.wirelength(arg)))); return this.decodebinary(minor, Bytes.from(this.nextbytes(this.wirelength(arg))));
case 2: case 2:
return this.decoderecord(minor, this.nextvalues(this.wirelength(arg))); return this.decodecompound(minor, this.nextvalues(this.wirelength(arg)));
case 3: 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.chunks = [];
this.view = new DataView(new ArrayBuffer(256)); this.view = new DataView(new ArrayBuffer(256));
this.index = 0; this.index = 0;
this.shortForms = options.shortForms || {};
} }
contents() { contents() {
@ -275,7 +269,7 @@ class Encoder {
} }
encodecollection(minor, items) { encodecollection(minor, items) {
this.header(3, minor, items.size); this.header(2, minor, items.size);
for (const item of items) { this.push(item); } for (const item of items) { this.push(item); }
} }
@ -283,10 +277,11 @@ class Encoder {
const tn = ((t & 3) << 2) | (n & 3); const tn = ((t & 3) << 2) | (n & 3);
this.header(0, 2, tn); this.header(0, 2, tn);
for (const item of items) { this.push(item); } for (const item of items) { this.push(item); }
this.header(0, 3, tn); this.header(0, 0, 4);
} }
push(v) { push(v) {
...placeholder...;
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') { if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
v[PreserveOn](this); v[PreserveOn](this);
} }
@ -295,7 +290,7 @@ class Encoder {
} }
else if (typeof v === 'number') { else if (typeof v === 'number') {
if (v >= -3 && v <= 12) { if (v >= -3 && v <= 12) {
this.leadbyte(0, 1, v >= 0 ? v : v + 16); this.leadbyte(0, 3, v >= 0 ? v : v + 16);
} else { } else {
this.encodeint(v); this.encodeint(v);
} }
@ -323,18 +318,18 @@ class Encoder {
} }
} }
else if (List.isList(v)) { else if (List.isList(v)) {
this.encodecollection(0, v);
}
else if (Set.isSet(v)) {
this.encodecollection(1, v); this.encodecollection(1, v);
} }
else if (Set.isSet(v)) {
this.encodecollection(2, v);
}
else if (Map.isMap(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); }); v.forEach((val, key) => { l.push(key).push(val); });
})); }));
} }
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') { else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') {
this.encodestream(3, 0, v); this.encodestream(2, 0, v);
} }
else { else {
throw new EncodeError("Cannot encode", v); 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('./symbols.js'));
Object.assign(module.exports, require('./codec.js')); Object.assign(module.exports, require('./codec.js'));
Object.assign(module.exports, require('./values.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 util = require('util');
const Immutable = require('immutable'); const Immutable = require('immutable');
const { List, isList, Map, Set, is } = Immutable; const { List, Map, Set, is } = Immutable;
const { PreserveOn, AsPreserve } = require('./symbols.js'); const { PreserveOn, AsPreserve } = require('./symbols.js');
@ -341,16 +341,8 @@ Record.prototype.set = function (index, newValue) {
}; };
Record.prototype[PreserveOn] = function (encoder) { Record.prototype[PreserveOn] = function (encoder) {
if (is(encoder.shortForms[0], this.label)) { encoder.header(2, 0, this.fields.size + 1);
encoder.header(2, 0, this.fields.size); encoder.push(this.label);
} 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);
}
for (const field of this.fields) { encoder.push(field); } for (const field of this.fields) { encoder.push(field); }
}; };