From 738a47ce90815482b452e0089a327ae857acd914 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 24 Aug 2019 19:08:07 +0100 Subject: [PATCH] WIP --- implementations/javascript/src/annotations.js | 54 +++++++++++++ implementations/javascript/src/codec.js | 79 +++++++++---------- implementations/javascript/src/index.js | 1 + implementations/javascript/src/values.js | 14 +--- 4 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 implementations/javascript/src/annotations.js diff --git a/implementations/javascript/src/annotations.js b/implementations/javascript/src/annotations.js new file mode 100644 index 0000000..4c79f15 --- /dev/null +++ b/implementations/javascript/src/annotations.js @@ -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); +} diff --git a/implementations/javascript/src/codec.js b/implementations/javascript/src/codec.js index 4c5b054..5112f16 100644 --- a/implementations/javascript/src/codec.js +++ b/implementations/javascript/src/codec.js @@ -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); diff --git a/implementations/javascript/src/index.js b/implementations/javascript/src/index.js index e95d2ca..d1ac9f8 100644 --- a/implementations/javascript/src/index.js +++ b/implementations/javascript/src/index.js @@ -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')); diff --git a/implementations/javascript/src/values.js b/implementations/javascript/src/values.js index b1db489..2b28a29 100644 --- a/implementations/javascript/src/values.js +++ b/implementations/javascript/src/values.js @@ -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); } };