Great simplification by introducing DictionaryMap

This commit is contained in:
Tony Garnock-Jones 2024-03-27 10:44:54 +01:00
parent eb4f456550
commit cbbc6c50c0
9 changed files with 258 additions and 228 deletions

View File

@ -1,7 +1,7 @@
import { Annotated } from "./annotated";
import { DecodeError, ShortPacket } from "./codec";
import { Tag } from "./constants";
import { Set, Dictionary } from "./dictionary";
import { Set, Dictionary, DictionaryMap } from "./dictionary";
import { DoubleFloat } from "./float";
import { Record } from "./record";
import { Bytes, BytesLike, underlying, hexDigit } from "./bytes";
@ -219,17 +219,14 @@ export class Decoder<T extends Embeddable = never> implements TypedDecoder<T> {
}
static dictionaryFromArray<T extends Embeddable>(vs: Value<T>[]): Dictionary<T> {
const entries: [Value<T>, Value<T>][] = [];
const d = new DictionaryMap<T>();
if (vs.length % 2) throw new DecodeError("Missing dictionary value");
for (let i = 0; i < vs.length; i += 2) {
entries.push([vs[i], vs[i+1]]);
}
const r = Dictionary.from<T>(entries, true);
if ('duplicate' in r) {
throw new DecodeError("Duplicate key");
} else {
return r.ok;
if (d.has(vs[i])) throw new DecodeError(`Duplicate key: ${stringify(vs[i])}`);
d.set(vs[i], vs[i+1]);
}
d.simplify();
return d.value;
}
next(): Value<T> {
@ -260,9 +257,11 @@ export class Decoder<T extends Embeddable = never> implements TypedDecoder<T> {
}
case Tag.Sequence: return this.state.wrap<T>(this.nextvalues());
case Tag.Set: {
const vs = this.nextvalues();
const s = new Set<T>(vs);
if (vs.length !== s.size) throw new DecodeError("Duplicate value in set");
const s = new Set<T>();
for (const v of this.nextvalues()) {
if (s.has(v)) throw new DecodeError(`Duplicate value: ${stringify(v)}`);
s.add(v);
}
return this.state.wrap<T>(s);
}
case Tag.Dictionary: return this.state.wrap<T>(Decoder.dictionaryFromArray(this.nextvalues()));

View File

@ -1,6 +1,6 @@
import { Encoder, canonicalString } from "./encoder";
import { Tag } from "./constants";
import { FlexMap, FlexSet, _iterMap, IdentitySet, Equivalence } from "./flex";
import { FlexMap, FlexSet, _iterMap, IdentitySet, Equivalence, IsMap } from "./flex";
import { Value } from "./values";
import { Bytes } from './bytes';
import { Embeddable, GenericEmbedded, isEmbedded } from "./embedded";
@ -8,7 +8,8 @@ import type { Preservable } from "./encoder";
import type { Writer, PreserveWritable } from "./writer";
import { annotations, Annotated } from "./annotated";
import { Float } from "./float";
import { JsDictionary, JsDictionaryMap } from "./jsdictionary";
import { JsDictionary } from "./jsdictionary";
import { unannotate } from "./strip";
export type DictionaryType = 'Dictionary' | 'Set';
export const DictionaryType = Symbol.for('DictionaryType');
@ -76,6 +77,125 @@ export class KeyedDictionary<T extends Embeddable = GenericEmbedded, K extends C
export type Dictionary<T extends Embeddable = GenericEmbedded, V = Value<T>> =
JsDictionary<V> | KeyedDictionary<T, Value<T>, V>;
export class DictionaryMap<T extends Embeddable = GenericEmbedded, V = Value<T>> implements Map<Value<T>, V> {
get [IsMap](): boolean { return true; }
j: JsDictionary<V> | undefined;
k: KeyedDictionary<T, Value<T>, V> | undefined;
constructor(input?: Dictionary<T, V>) {
if (input === void 0) {
this.j = {};
this.k = void 0;
} else if (DictionaryType in input) {
this.j = void 0;
this.k = input;
} else {
this.j = input;
this.k = void 0;
}
}
clear(): void {
if (this.j) {
JsDictionary.clear(this.j);
} else {
this.k!.clear();
}
}
delete(key: Value<T>): boolean {
if (this.j) {
key = unannotate(key);
if (typeof key !== 'symbol') return false;
return JsDictionary.remove(this.j, key);
} else {
return this.k!.delete(key);
}
}
forEach(callbackfn: (value: V, key: Value<T>, map: Map<Value<T>, V>) => void, thisArg?: any): void {
if (this.j) {
JsDictionary.forEach(this.j, (v, k) => callbackfn.call(thisArg, v, k, this));
} else {
this.k!.forEach(callbackfn, thisArg);
}
}
get(key: Value<T>): V | undefined {
if (this.j) {
key = unannotate(key);
if (typeof key !== 'symbol') return void 0;
return JsDictionary.get(this.j, key);
} else {
return this.k!.get(key);
}
}
has(key: Value<T>): boolean {
if (this.j) {
key = unannotate(key);
if (typeof key !== 'symbol') return false;
return JsDictionary.has(this.j, key);
} else {
return this.k!.has(key);
}
}
set(key: Value<T>, value: V): this {
if (this.j) {
if (typeof key === 'symbol') {
JsDictionary.set(this.j, key, value);
return this;
}
this.k = new KeyedDictionary<T, Value<T>, V>(JsDictionary.entries(this.j));
this.j = void 0;
}
this.k!.set(key, value);
return this;
}
get size(): number {
return this.j ? JsDictionary.size(this.j) : this.k!.size;
}
entries(): IterableIterator<[Value<T>, V]> {
return this.j ? JsDictionary.entries(this.j) : this.k!.entries();
}
keys(): IterableIterator<Value<T>> {
return this.j ? JsDictionary.keys(this.j) : this.k!.keys();
}
values(): IterableIterator<V> {
return this.j ? JsDictionary.values(this.j) : this.k!.values();
}
[Symbol.iterator](): IterableIterator<[Value<T>, V]> {
return this.entries();
}
get [Symbol.toStringTag](): string {
return 'DictionaryMap';
}
get value(): Dictionary<T, V> {
return this.j ?? this.k!;
}
simplify(): void {
if (!this.j) {
const r: JsDictionary<V> = {};
for (const [key, value] of this.k!.entries()) {
if (typeof key !== 'symbol') return;
r[key.description!] = value;
}
this.j = r;
this.k = void 0;
}
}
}
export namespace Dictionary {
export function isDictionary<T extends Embeddable = GenericEmbedded, V = Value<T>>(
x: any
@ -93,69 +213,16 @@ export namespace Dictionary {
}
}
export function mapInto<K, V, S, W, O extends Map<S, W>>(
i: Map<K, V>,
o: O,
f: (entry: [K, V]) => [S, W],
): O {
for (let oldEntry of i.entries()) {
const newEntry = f(oldEntry);
o.set(newEntry[0], newEntry[1]);
}
return o;
}
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
x: Dictionary<T>
): Map<Value<T>, V>;
x: Dictionary<T, V>
): DictionaryMap<T, V>;
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
x: any
): Map<Value<T>, V> | undefined;
): DictionaryMap<T, V> | undefined;
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
x: any
): Map<Value<T>, V> | undefined {
if (!isDictionary<T, V>(x)) return void 0;
if (DictionaryType in x) return x;
return new JsDictionaryMap(x);
}
export function from<T extends Embeddable, E extends [Value<T>, Value<T>, ... any[]] = [Value<T>, Value<T>]>(
entries: Iterable<E>, failOnDuplicateKeys: true): {ok: Dictionary<T>} | {duplicate: E};
export function from<T extends Embeddable, E extends [Value<T>, Value<T>, ... any[]] = [Value<T>, Value<T>]>(
entries: Iterable<E>, failOnDuplicateKeys: false): Dictionary<T>;
export function from<T extends Embeddable, E extends [Value<T>, Value<T>, ... any[]] = [Value<T>, Value<T>]>(
entries: Iterable<E>): Dictionary<T>;
export function from<T extends Embeddable, E extends [Value<T>, Value<T>, ... any[]] = [Value<T>, Value<T>]>(
entries0: Iterable<E>,
failOnDuplicateKeys: boolean = false,
): Dictionary<T> | {ok: Dictionary<T>} | {duplicate: E} {
const typeSet = new IdentitySet();
const entries: E[] = [];
for (const e of entries0) {
typeSet.add(typeof e[0]);
entries.push(e);
}
const types = Array.from(typeSet);
if (types.length === 1 && types[0] === 'symbol') {
const result: JsDictionary<Value<T>> = {};
for (const e of entries) {
const k = (e[0] as symbol).description!;
if (failOnDuplicateKeys && Object.prototype.hasOwnProperty.call(result, k)) {
return {duplicate: e};
}
result[k] = e[1];
}
return failOnDuplicateKeys ? {ok: result} : result;
} else {
const result = new KeyedDictionary<T>();
for (const e of entries) {
if (failOnDuplicateKeys && result.has(e[0])) {
return {duplicate: e};
}
result.set(e[0], e[1]);
}
return failOnDuplicateKeys ? {ok: result} : result;
}
): DictionaryMap<T, V> | undefined {
return isDictionary<T, V>(x) ? new DictionaryMap(x) : void 0;
}
export function __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Dictionary<T> {

View File

@ -5,8 +5,7 @@ import { EncodeError } from "./codec";
import { Record, Tuple } from "./record";
import { EmbeddedTypeEncode, isEmbedded } from "./embedded";
import type { Embeddable } from "./embedded";
import { encodeDictionaryOn } from "./dictionary";
import { JsDictionaryMap } from "./jsdictionary";
import { DictionaryMap, encodeDictionaryOn } from "./dictionary";
export type Encodable<T extends Embeddable> =
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
@ -295,11 +294,10 @@ export class Encoder<T extends Embeddable> {
this.embeddedEncode.encode(this.state, v);
}
else {
encodeDictionaryOn(
new JsDictionaryMap(v),
this,
(k, e) => e.push(k),
(v, e) => e.push(v));
encodeDictionaryOn(new DictionaryMap<T>(v),
this,
(k, e) => e.push(k),
(v, e) => e.push(v));
}
return this; // for chaining
}

View File

@ -1,8 +1,7 @@
import { Record, Tuple } from "./record";
import { Bytes } from "./bytes";
import { Value } from "./values";
import { Set, KeyedDictionary, Dictionary } from "./dictionary";
import { JsDictionary, JsDictionaryMap } from "./jsdictionary";
import { Set, KeyedDictionary, Dictionary, DictionaryMap } from "./dictionary";
import { annotate, Annotated } from "./annotated";
import { Double, Float } from "./float";
import { Embeddable, isEmbedded } from "./embedded";
@ -35,8 +34,7 @@ export interface FoldMethods<T extends Embeddable, R> {
record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, R>): R;
array(a: Array<Value<T>>, k: Fold<T, R>): R;
set(s: Set<T>, k: Fold<T, R>): R;
jsDictionary(d: JsDictionary<Value<T>>, k: Fold<T, R>): R;
keyedDictionary(d: KeyedDictionary<T>, k: Fold<T, R>): R;
dictionary(d: DictionaryMap<T>, k: Fold<T, R>): R;
annotated(a: Annotated<T>, k: Fold<T, R>): R;
@ -56,10 +54,7 @@ export class VoidFold<T extends Embeddable> implements FoldMethods<T, void> {
}
array(a: Value<T>[], k: Fold<T, void>): void { a.forEach(k); }
set(s: Set<T>, k: Fold<T, void>): void { s.forEach(k); }
jsDictionary(d: JsDictionary<Value<T>>, k: Fold<T, void>): void {
new JsDictionaryMap(d).forEach((value, key) => { k(key); k(value); });
}
keyedDictionary(d: KeyedDictionary<T>, k: Fold<T, void>): void {
dictionary(d: DictionaryMap<T>, k: Fold<T, void>): void {
d.forEach((value, key) => { k(key); k(value); });
}
annotated(a: Annotated<T>, k: Fold<T, void>): void { k(a.item); a.annotations.forEach(k); }
@ -99,15 +94,11 @@ export abstract class ValueFold<T extends Embeddable, R extends Embeddable = T>
set(s: Set<T>, k: Fold<T, Value<R>>): Value<R> {
return s.map(k);
}
jsDictionary(d: JsDictionary<Value<T>>, k: Fold<T, Value<R>>): Value<R> {
const results: [Value<R>, Value<R>][] = [];
new JsDictionaryMap(d).forEach((value, key) => results.push([k(key), k(value)]));
return Dictionary.from(results);
}
keyedDictionary(d: KeyedDictionary<T>, k: Fold<T, Value<R>>): Value<R> {
const r = new KeyedDictionary<R>();
Dictionary.mapInto(d, r, ([key, value]) => [k(key), k(value)]);
return r;
dictionary(d: DictionaryMap<T>, k: Fold<T, Value<R>>): Value<R> {
const result = new DictionaryMap<R>();
d.forEach((value, key) => result.set(k(key), k(value)));
result.simplify();
return result.value;
}
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
return annotate(k(a.item), ...a.annotations.map(k));
@ -208,10 +199,8 @@ export function fold<T extends Embeddable, R>(v: Value<T>, o: FoldMethods<T, R>)
return o.bytes(v);
} else if (Float.isDouble(v)) {
return o.double(v.value);
} else if (KeyedDictionary.isKeyedDictionary<T>(v)) {
return o.keyedDictionary(v, walk);
} else {
return o.jsDictionary(v, walk);
} else if (Dictionary.isDictionary<T>(v)) {
return o.dictionary(new DictionaryMap(v), walk);
}
default:
((_v: never): never => { throw new Error("Internal error"); })(v);

View File

@ -5,85 +5,73 @@ export interface JsDictionary<V> {
[key: string]: V;
}
export class JsDictionaryMap<V> implements Map<symbol, V> {
_size: number | undefined = void 0;
constructor(public readonly j: JsDictionary<V>) {}
get [IsMap](): boolean { return true; }
clear(): void {
for (const key in this.j) delete this.j[key];
this._size = void 0;
}
delete(key: symbol): boolean {
const result = this.has(key);
delete this.j[key.description!];
if (result) this._size = void 0;
return result;
}
forEach(
callbackfn: (value: V, key: symbol, map: Map<symbol, V>) => void,
thisArg?: any,
): void {
Object.entries(this.j).forEach(([key, val]) =>
callbackfn.call(thisArg, val, Symbol.for(key), this));
}
get(key: symbol): V | undefined {
return this.j[key.description!];
}
has(key: symbol): boolean {
return Object.hasOwnProperty.call(this.j, key.description!);
}
set(key: symbol, value: V): this {
this.j[key.description!] = value;
this._size = void 0;
return this;
}
get size(): number {
if (this._size === void 0) {
this._size = Object.keys(this.j).length;
}
return this._size;
}
entries(): IterableIterator<[symbol, V]> {
return _iterMap(Object.entries(this.j).values(), ([k, v]) => [Symbol.for(k), v]);
}
keys(): IterableIterator<symbol> {
return _iterMap(Object.keys(this.j).values(), k => Symbol.for(k));
}
values(): IterableIterator<V> {
return Object.values(this.j).values();
}
[Symbol.iterator](): IterableIterator<[symbol, V]> {
return this.entries();
}
get [Symbol.toStringTag]() { return 'JsDictionaryMap'; }
equals(other: any, eqv: Equivalence<V> = (v1, v2) => v1 === v2): boolean {
if (!('size' in other && 'has' in other && 'get' in other)) return false;
if (this.size !== other.size) return false;
for (let [k, v] of Object.entries(this.j)) {
if (!other.has(k)) return false;
if (!eqv(v, other.get(k))) return false;
}
return true;
}
}
export namespace JsDictionary {
export function isJsDictionary<V>(x: any): x is JsDictionary<V> {
return Dictionary.isDictionary(x) && (x as any)[DictionaryType] === void 0;
}
export function clear<V>(j: JsDictionary<V>): void {
for (const key in j) delete j[key];
}
export function remove<V>(j: JsDictionary<V>, key: symbol): boolean {
const result = has(j, key);
delete j[key.description!];
return result;
}
export function forEach<V>(
j: JsDictionary<V>,
callbackfn: (value: V, key: symbol) => void,
): void {
Object.entries(j).forEach(([key, val]) => callbackfn(val, Symbol.for(key)));
}
export function get<V>(j: JsDictionary<V>, key: symbol): V | undefined {
return j[key.description!];
}
export function has<V>(j: JsDictionary<V>, key: symbol): boolean {
return Object.hasOwnProperty.call(j, key.description!);
}
export function set<V>(j: JsDictionary<V>, key: symbol, value: V): JsDictionary<V> {
j[key.description!] = value;
return j;
}
export function size<V>(j: JsDictionary<V>): number {
return Object.keys(j).length;
}
export function entries<V>(j: JsDictionary<V>): IterableIterator<[symbol, V]> {
return _iterMap(Object.entries(j).values(), ([k, v]) => [Symbol.for(k), v]);
}
export function keys<V>(j: JsDictionary<V>): IterableIterator<symbol> {
return _iterMap(Object.keys(j).values(), k => Symbol.for(k));
}
export function values<V>(j: JsDictionary<V>): IterableIterator<V> {
return Object.values(j).values();
}
export function clone<V>(j: JsDictionary<V>): JsDictionary<V> {
const r: JsDictionary<V> = {};
Object.keys(j).forEach(k => r[k] = j[k]);
return r;
}
export function equals<V>(
j1: JsDictionary<V>,
j2: JsDictionary<V>,
eqv: Equivalence<V> = (v1, v2) => v1 === v2,
): boolean {
if (size(j1) !== size(j2)) return false;
for (let [k, v] of entries(j1)) {
if (!has(j2, k)) return false;
if (!eqv(v, get(j2, k)!)) return false;
}
return true;
}
}

View File

@ -3,13 +3,13 @@ import { Bytes } from "./bytes";
import { fold } from "./fold";
import { is } from "./is";
import { Value } from "./values";
import { Set, Dictionary, KeyedDictionary } from "./dictionary";
import { Set, Dictionary, KeyedDictionary, DictionaryMap } from "./dictionary";
import { Annotated } from "./annotated";
import { unannotate } from "./strip";
import { isEmbedded } from "./embedded";
import { isCompound } from "./compound";
import type { Embeddable } from "./embedded";
import { JsDictionary, JsDictionaryMap } from "./jsdictionary";
import { JsDictionary } from "./jsdictionary";
export function merge<T extends Embeddable>(
mergeEmbeddeds: (a: T, b: T) => T | undefined,
@ -44,20 +44,28 @@ export function merge<T extends Embeddable>(
if (!Record.isRecord<Value<T>, Tuple<Value<T>>, T>(b)) die();
return Record(walk(r.label, b.label), walkMany(r, b));
},
array(a: Array<Value<T>>) {
if (!Array.isArray(b) || Record.isRecord(b)) die();
return walkMany(a, b);
},
set(_s: Set<T>) { die(); },
jsDictionary(a: JsDictionary<Value<T>>) {
dictionary(aMap: DictionaryMap<T>) {
const bMap = Dictionary.asMap<T>(b);
if (bMap === void 0) die();
return walkMaps(new JsDictionaryMap(a), bMap);
},
keyedDictionary(a: KeyedDictionary<T>) {
const bMap = Dictionary.asMap<T>(b);
if (bMap === void 0) die();
return walkMaps(a, bMap);
const r = new DictionaryMap<T>();
aMap.forEach((av,ak) => {
const bv = bMap.get(ak);
r.set(ak, bv === void 0 ? av : walk(av, bv));
});
bMap.forEach((bv, bk) => {
if (!aMap.has(bk)) r.set(bk, bv);
});
r.simplify();
return r.value;
},
annotated(a: Annotated<T>) {
@ -81,17 +89,5 @@ export function merge<T extends Embeddable>(
}
}
function walkMaps(a: Map<Value<T>, Value<T>>, b: Map<Value<T>, Value<T>>): Dictionary<T> {
const r = new KeyedDictionary<T>();
a.forEach((av,ak) => {
const bv = b.get(ak);
r.set(ak, bv === void 0 ? av : walk(av, bv));
});
b.forEach((bv, bk) => {
if (!a.has(bk)) r.set(bk, bv);
});
return Dictionary.from(r);
}
return items.reduce(walk, item0);
}

View File

@ -2,7 +2,7 @@
import type { Value } from './values';
import { DecodeError, ShortPacket } from './codec';
import { Dictionary, Set } from './dictionary';
import { Dictionary, DictionaryMap, Set } from './dictionary';
import { strip } from './strip';
import { Bytes, unhexDigit } from './bytes';
import { Decoder, DecoderState, neverEmbeddedTypeDecode } from './decoder';
@ -398,24 +398,20 @@ export class Reader<T extends Embeddable> {
}
readDictionary(): Dictionary<T> {
const r = Dictionary.from<T, [Value<T>, Value<T>, Position]>(
this.seq(true, [] as [Value<T>, Value<T>, Position][], (k, acc) => {
this.state.skipws();
switch (this.state.peek()) {
case ':':
this.state.advance();
acc.push([k, this.next(), this.state.copyPos()]);
break;
default:
this.state.error('Missing key/value separator', this.state.pos);
}
}, '}'),
true as const);
if ('duplicate' in r) {
this.state.error(`Duplicate key: ${stringify(r.duplicate[0])}`, r.duplicate[2]);
} else {
return r.ok;
}
const r = this.seq(true, new DictionaryMap<T>(), (k, acc) => {
this.state.skipws();
switch (this.state.peek()) {
case ':':
this.state.advance();
if (acc.has(k)) this.state.error(`Duplicate key: ${stringify(k)}`, this.state.pos);
acc.set(k, this.next());
break;
default:
this.state.error('Missing key/value separator', this.state.pos);
}
}, '}');
r.simplify();
return r.value;
}
readSet(): Set<T> {

View File

@ -1,8 +1,7 @@
import { Value } from "./values";
import { Annotated } from "./annotated";
import { Record, Tuple } from "./record";
import { Set, Dictionary, KeyedDictionary } from "./dictionary";
import { JsDictionary, JsDictionaryMap } from "./jsdictionary";
import { Set, Dictionary, DictionaryMap } from "./dictionary";
import type { Embeddable, GenericEmbedded } from "./embedded";
export function unannotate<T extends Embeddable = GenericEmbedded>(v: Value<T>): Value<T> {
@ -34,12 +33,11 @@ export function strip<T extends Embeddable = GenericEmbedded>(
return (v.item as Value<T>[]).map(walk);
} else if (Set.isSet<T>(v.item)) {
return v.item.map(walk);
} else if (KeyedDictionary.isKeyedDictionary<T>(v.item)) {
const result = new KeyedDictionary<T>();
return Dictionary.mapInto(v.item, result, (e) => [walk(e[0]), walk(e[1])]);
} else if (JsDictionary.isJsDictionary<T>(v.item)) {
const i = new JsDictionaryMap(v.item);
return Dictionary.mapInto(i, new JsDictionaryMap<T>({}), (e) => [e[0], walk(e[1])]).j;
} else if (Dictionary.isDictionary<T>(v.item)) {
const result = new DictionaryMap<T>();
new DictionaryMap<T>(v.item).forEach((val, key) => result.set(walk(key), walk(val)));
result.simplify();
return result.value;
} else {
return v.item;
}

View File

@ -5,8 +5,7 @@ import { Encoder, EncoderState } from "./encoder";
import type { Value } from "./values";
import { NUMBER_RE } from './reader';
import { encodeBase64 } from './base64';
import { writeDictionaryOn } from './dictionary';
import { JsDictionaryMap } from './jsdictionary';
import { DictionaryMap, writeDictionaryOn } from './dictionary';
export type Writable<T extends Embeddable> =
Value<T> | PreserveWritable<T> | Iterable<Value<T>> | ArrayBufferView;
@ -309,7 +308,7 @@ export class Writer<T extends Embeddable> {
.push(this.embeddedWrite.toValue(v));
}
} else {
writeDictionaryOn(new JsDictionaryMap(v),
writeDictionaryOn(new DictionaryMap<T>(v),
this,
(k, w) => w.push(k),
(v, w) => w.push(v));