From 9420cc723659b8427c145eb9a03c5db1f9e41b12 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 16 Dec 2023 22:13:17 +1300 Subject: [PATCH] New EncodableDictionary/EncodableSet as intermediate steps between Flex- and Keyed- --- .../packages/core/src/dictionary.ts | 207 ++++++++++++------ .../javascript/packages/core/src/encoder.ts | 8 +- 2 files changed, 148 insertions(+), 67 deletions(-) diff --git a/implementations/javascript/packages/core/src/dictionary.ts b/implementations/javascript/packages/core/src/dictionary.ts index 570614b..358cc8b 100644 --- a/implementations/javascript/packages/core/src/dictionary.ts +++ b/implementations/javascript/packages/core/src/dictionary.ts @@ -1,31 +1,61 @@ -import { Encodable, Encoder, canonicalEncode, canonicalString } from "./encoder"; +import { Encoder, canonicalString } from "./encoder"; import { Tag } from "./constants"; -import { FlexMap, FlexSet, _iterMap } from "./flex"; +import { FlexMap, FlexSet, _iterMap, IdentitySet } from "./flex"; import { Value } from "./values"; import { Bytes } from './bytes'; import { GenericEmbedded } from "./embedded"; import type { Preservable } from "./encoder"; -import type { Writable, Writer, PreserveWritable } from "./writer"; +import type { Writer, PreserveWritable } from "./writer"; import { annotations, Annotated } from "./annotated"; export type DictionaryType = 'Dictionary' | 'Set'; export const DictionaryType = Symbol.for('DictionaryType'); -export class KeyedDictionary & Writable), V, T = GenericEmbedded> extends FlexMap +export type CompoundKey = Value | (Preservable & PreserveWritable); + +export class EncodableDictionary extends FlexMap implements Preservable, PreserveWritable +{ + constructor( + public readonly encodeK: (k: K) => CompoundKey, + public readonly encodeV: (v: V) => CompoundKey, + items?: readonly [K, V][] | Iterable + ) { + super((k: K) => canonicalString(encodeK(k)), items); + } + + __preserve_on__(encoder: Encoder) { + encodeDictionaryOn(this, + encoder, + (k, e) => e.push(this.encodeK(k)), + (v, e) => e.push(this.encodeV(v))); + } + + __preserve_text_on__(w: Writer) { + writeDictionaryOn(this, + w, + (k, w) => w.push(this.encodeK(k)), + (v, w) => w.push(this.encodeV(v))); + } +} + +export class KeyedDictionary, V, T = GenericEmbedded> + extends EncodableDictionary { get [DictionaryType](): DictionaryType { return 'Dictionary'; } - static isKeyedDictionary, V, T = GenericEmbedded>(x: any): x is KeyedDictionary { + static isKeyedDictionary, V, T = GenericEmbedded>( + x: any, + ): x is KeyedDictionary { return x?.[DictionaryType] === 'Dictionary'; } - constructor(items?: readonly [K, V][]); - constructor(items?: Iterable); - constructor(items?: Iterable) { - super(canonicalString, items); + constructor(items?: readonly [K, V][] | Iterable) { + // The cast in encodeV is suuuuuuuper unsound, since V may not in fact be Encodable and Writable. + // Don't try to encode/write dictionaries holding non-encodable/non-writable values. + super(k => k, v => v as CompoundKey, items); } mapEntries, R = GenericEmbedded>(f: (entry: [K, V]) => [S, W]): KeyedDictionary { @@ -42,44 +72,6 @@ export class KeyedDictionary & Writable), V, T = Gene } get [Symbol.toStringTag]() { return 'Dictionary'; } - - __preserve_on__(encoder: Encoder) { - if (encoder.canonical) { - const entries = Array.from(this); - const pieces = entries.map<[Bytes, number]>(([k, _v], i) => [canonicalEncode(k), i]); - pieces.sort((a, b) => Bytes.compare(a[0], b[0])); - encoder.state.emitbyte(Tag.Dictionary); - pieces.forEach(([_encodedKey, i]) => { - const [k, v] = entries[i]; - encoder.push(k); - encoder.push(v as unknown as Value); // Suuuuuuuper unsound - }); - encoder.state.emitbyte(Tag.End); - } else { - encoder.state.emitbyte(Tag.Dictionary); - this.forEach((v, k) => { - encoder.push(k); - encoder.push(v as unknown as Value); // Suuuuuuuper unsound - }); - encoder.state.emitbyte(Tag.End); - } - } - - __preserve_text_on__(w: Writer) { - w.state.writeSeq('{', '}', this.entries(), ([k, v]) => { - w.push(k); - if (Annotated.isAnnotated(v) && (annotations(v).length > 1) && w.state.isIndenting) { - w.state.pieces.push(':'); - w.state.indentCount++; - w.state.writeIndent(); - w.push(v); - w.state.indentCount--; - } else { - w.state.pieces.push(': '); - w.push(v as unknown as Value); // Suuuuuuuper unsound - } - }); - } } export class Dictionary> extends KeyedDictionary, V, T> { @@ -92,19 +84,91 @@ export class Dictionary> extends KeyedDictiona } } -export class KeyedSet, T = GenericEmbedded> extends FlexSet +export function encodeDictionaryOn( + dict: Map, + encoder: Encoder, + encodeK: (k: K, encoder: Encoder) => void, + encodeV: (v: V, encoder: Encoder) => void, +) { + if (encoder.canonical) { + const entries = Array.from(dict); + const canonicalEncoder = new Encoder({ + canonical: true, + embeddedEncode: encoder.embeddedEncode, + }); + const pieces = entries.map<[Bytes, number]>(([k, _v], i) => { + encodeK(k, canonicalEncoder); + return [canonicalEncoder.contents(), i]; + }); + pieces.sort((a, b) => Bytes.compare(a[0], b[0])); + encoder.grouped(Tag.Dictionary, () => pieces.forEach(([_encodedKey, i]) => { + const [k, v] = entries[i]; + encodeK(k, encoder); + encodeV(v, encoder); + })); + } else { + encoder.grouped(Tag.Dictionary, () => dict.forEach((v, k) => { + encodeK(k, encoder); + encodeV(v, encoder); + })); + } +} + +export function writeDictionaryOn( + dict: Map, + w: Writer, + writeK: (k: K, w: Writer) => void, + writeV: (v: V, w: Writer) => void, +) { + w.state.writeSeq('{', '}', dict.entries(), ([k, v]) => { + writeK(k, w); + if (Annotated.isAnnotated(v) && (annotations(v).length > 1) && w.state.isIndenting) { + w.state.pieces.push(':'); + w.state.indentCount++; + w.state.writeIndent(); + writeV(v, w); + w.state.indentCount--; + } else { + w.state.pieces.push(': '); + writeV(v, w); + } + }); +} + +export class EncodableSet extends FlexSet implements Preservable, PreserveWritable +{ + constructor( + public readonly encodeV: (v: V) => CompoundKey, + items?: Iterable, + ) { + super((v: V) => canonicalString(encodeV(v)), items); + } + + __preserve_on__(encoder: Encoder) { + encodeSetOn(this, encoder, (v, e) => e.push(this.encodeV(v))); + } + + __preserve_text_on__(w: Writer) { + writeSetOn(this, w, (v, w) => w.push(this.encodeV(v))); + } +} + +export class KeyedSet, T = GenericEmbedded> + extends EncodableSet { get [DictionaryType](): DictionaryType { return 'Set'; } - static isKeyedSet, T = GenericEmbedded>(x: any): x is KeyedSet { + static isKeyedSet, T = GenericEmbedded>( + x: any, + ): x is KeyedSet { return x?.[DictionaryType] === 'Set'; } constructor(items?: Iterable) { - super(canonicalString, items); + super(k => k, items); } map, R = GenericEmbedded>(f: (value: K) => S): KeyedSet { @@ -122,20 +186,6 @@ export class KeyedSet, T = GenericEmbedded> extends FlexSet) { - if (encoder.canonical) { - const pieces = Array.from(this).map<[Bytes, K]>(k => [canonicalEncode(k), k]); - pieces.sort((a, b) => Bytes.compare(a[0], b[0])); - encoder.encodevalues(Tag.Set, pieces.map(e => e[1])); - } else { - encoder.encodevalues(Tag.Set, this); - } - } - - __preserve_text_on__(w: Writer) { - w.state.writeSeq('#{', '}', this, vv => w.push(vv)); - } } export class Set extends KeyedSet, T> { @@ -147,3 +197,32 @@ export class Set extends KeyedSet, T> { return Set.isSet(v) ? v : void 0; } } + +export function encodeSetOn( + s: IdentitySet, + encoder: Encoder, + encodeV: (v: V, encoder: Encoder) => void, +) { + if (encoder.canonical) { + const canonicalEncoder = new Encoder({ + canonical: true, + embeddedEncode: encoder.embeddedEncode, + }); + const pieces = Array.from(s).map<[Bytes, V]>(v => { + encodeV(v, canonicalEncoder); + return [canonicalEncoder.contents(), v]; + }); + pieces.sort((a, b) => Bytes.compare(a[0], b[0])); + encoder.grouped(Tag.Set, () => pieces.forEach(([_e, v]) => encodeV(v, encoder))); + } else { + encoder.grouped(Tag.Set, () => s.forEach(v => encodeV(v, encoder))); + } +} + +export function writeSetOn( + s: IdentitySet, + w: Writer, + writeV: (v: V, w: Writer) => void, +) { + w.state.writeSeq('#{', '}', s, vv => writeV(vv, w)); +} diff --git a/implementations/javascript/packages/core/src/encoder.ts b/implementations/javascript/packages/core/src/encoder.ts index a283c69..d8b9cd7 100644 --- a/implementations/javascript/packages/core/src/encoder.ts +++ b/implementations/javascript/packages/core/src/encoder.ts @@ -242,9 +242,9 @@ export class Encoder { return this.state.contentsString(); } - encodevalues(tag: Tag, items: Iterable>) { + grouped(tag: Tag, f: () => void) { this.state.emitbyte(tag); - for (let i of items) { this.push(i); } + f(); this.state.emitbyte(Tag.End); } @@ -284,7 +284,9 @@ export class Encoder { this.state.emitbyte(Tag.End); } else if (isIterable>(v)) { - this.encodevalues(Tag.Sequence, v); + this.grouped(Tag.Sequence, () => { + for (let i of v) this.push(i); + }); } else { ((v: Embedded) => {