405 lines
13 KiB
TypeScript
405 lines
13 KiB
TypeScript
import { Encoder, canonicalString } from "./encoder";
|
|
import { Tag } from "./constants";
|
|
import { FlexMap, FlexSet, _iterMap, IdentitySet, Equivalence, IsMap } from "./flex";
|
|
import { Value } from "./values";
|
|
import { Bytes } from './bytes';
|
|
import { Embeddable, GenericEmbedded, isEmbedded } from "./embedded";
|
|
import type { Preservable } from "./encoder";
|
|
import type { Writer, PreserveWritable } from "./writer";
|
|
import { annotations, Annotated } from "./annotated";
|
|
import { Float } from "./float";
|
|
import { JsDictionary } from "./jsdictionary";
|
|
import { unannotate } from "./strip";
|
|
|
|
export type DictionaryType = 'Dictionary' | 'Set';
|
|
export const DictionaryType = Symbol.for('DictionaryType');
|
|
|
|
export type CompoundKey<T extends Embeddable> = Value<T> | (Preservable<T> & PreserveWritable<T>);
|
|
|
|
export class EncodableDictionary<T extends Embeddable, K, V> extends FlexMap<K, V>
|
|
implements Preservable<T>, PreserveWritable<T>
|
|
{
|
|
constructor(
|
|
public readonly encodeK: (k: K) => CompoundKey<T>,
|
|
public readonly encodeV: (v: V) => CompoundKey<T>,
|
|
items?: readonly [K, V][] | Iterable<readonly [K, V]>
|
|
) {
|
|
super((k: K) => canonicalString(encodeK(k)), items);
|
|
}
|
|
|
|
__preserve_on__(encoder: Encoder<T>) {
|
|
encodeDictionaryOn(this,
|
|
encoder,
|
|
(k, e) => e.push(this.encodeK(k)),
|
|
(v, e) => e.push(this.encodeV(v)));
|
|
}
|
|
|
|
__preserve_text_on__(w: Writer<T>) {
|
|
writeDictionaryOn(this,
|
|
w,
|
|
(k, w) => w.push(this.encodeK(k)),
|
|
(v, w) => w.push(this.encodeV(v)));
|
|
}
|
|
}
|
|
|
|
export class KeyedDictionary<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>, V = Value<T>>
|
|
extends EncodableDictionary<T, K, V>
|
|
{
|
|
get [DictionaryType](): DictionaryType {
|
|
return 'Dictionary';
|
|
}
|
|
|
|
static isKeyedDictionary<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>, V = Value<T>>(
|
|
x: any,
|
|
): x is KeyedDictionary<T, K, V> {
|
|
return x?.[DictionaryType] === 'Dictionary';
|
|
}
|
|
|
|
constructor(items?: readonly [K, V][] | Iterable<readonly [K, V]>) {
|
|
// 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<T>, items);
|
|
}
|
|
|
|
clone(): KeyedDictionary<T, K, V> {
|
|
return new KeyedDictionary(this);
|
|
}
|
|
|
|
get [Symbol.toStringTag]() { return 'Dictionary'; }
|
|
|
|
equals(otherAny: any, eqv: Equivalence<V> = (v1, v2) => v1 === v2): boolean {
|
|
const otherMap = Dictionary.asMap(otherAny);
|
|
if (!otherMap) return false;
|
|
return super.equals(otherMap, eqv);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static from<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
|
entries: [Value<T>, V][] | Iterable<[Value<T>, V]>,
|
|
): DictionaryMap<T, V> {
|
|
const r = new DictionaryMap<T, V>();
|
|
for (const [key, value] of entries) r.set(key, value);
|
|
return r;
|
|
}
|
|
|
|
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';
|
|
}
|
|
|
|
clone(): DictionaryMap<T, V> {
|
|
return new DictionaryMap<T, V>(this.j ? JsDictionary.clone(this.j) : this.k!.clone());
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
simplifiedValue(): Dictionary<T, V> {
|
|
this.simplify();
|
|
return this.value;
|
|
}
|
|
|
|
asJsDictionary(): JsDictionary<V> {
|
|
this.simplify();
|
|
if (!this.j) throw new Error("Cannot represent general dictionary as JsDictionary");
|
|
return this.j;
|
|
}
|
|
|
|
asKeyedDictionary(): KeyedDictionary<T, Value<T>, V> {
|
|
return this.k ?? new KeyedDictionary<T, Value<T>, V>(JsDictionary.entries(this.j!));
|
|
}
|
|
}
|
|
|
|
export namespace Dictionary {
|
|
export function isDictionary<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
|
x: any
|
|
): x is Dictionary<T, V> {
|
|
if (typeof x !== 'object' || x === null) return false;
|
|
switch (x[DictionaryType]) {
|
|
case 'Dictionary': return true;
|
|
case void 0: return JsDictionary.isJsDictionary(x);
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
|
x: Dictionary<T, V>
|
|
): DictionaryMap<T, V>;
|
|
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
|
x: any
|
|
): DictionaryMap<T, V> | undefined;
|
|
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
|
x: any
|
|
): DictionaryMap<T, V> | undefined {
|
|
return isDictionary<T, V>(x) ? new DictionaryMap(x) : void 0;
|
|
}
|
|
|
|
export function from<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
|
entries: [Value<T>, V][] | Iterable<[Value<T>, V]>,
|
|
): Dictionary<T, V> {
|
|
return DictionaryMap.from(entries).simplifiedValue();
|
|
}
|
|
|
|
export function __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Dictionary<T> {
|
|
return Dictionary.isDictionary<T>(v) ? v : void 0;
|
|
}
|
|
}
|
|
|
|
export function encodeDictionaryOn<T extends Embeddable, K, V>(
|
|
dict: Map<K, V>,
|
|
encoder: Encoder<T>,
|
|
encodeK: (k: K, encoder: Encoder<T>) => void,
|
|
encodeV: (v: V, encoder: Encoder<T>) => void,
|
|
) {
|
|
if (encoder.canonical) {
|
|
const entries = Array.from(dict);
|
|
const canonicalEncoder = new Encoder<T>({
|
|
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<T extends Embeddable, K, V>(
|
|
dict: Map<K, V>,
|
|
w: Writer<T>,
|
|
writeK: (k: K, w: Writer<T>) => void,
|
|
writeV: (v: V, w: Writer<T>) => void,
|
|
) {
|
|
w.state.writeSeq('{', '}', dict.entries(), ([k, v]) => {
|
|
writeK(k, w);
|
|
if (Annotated.isAnnotated<T>(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<T extends Embeddable, V> extends FlexSet<V>
|
|
implements Preservable<T>, PreserveWritable<T>
|
|
{
|
|
constructor(
|
|
public readonly encodeV: (v: V) => CompoundKey<T>,
|
|
items?: Iterable<V>,
|
|
) {
|
|
super((v: V) => canonicalString(encodeV(v)), items);
|
|
}
|
|
|
|
__preserve_on__(encoder: Encoder<T>) {
|
|
encodeSetOn(this, encoder, (v, e) => e.push(this.encodeV(v)));
|
|
}
|
|
|
|
__preserve_text_on__(w: Writer<T>) {
|
|
writeSetOn(this, w, (v, w) => w.push(this.encodeV(v)));
|
|
}
|
|
}
|
|
|
|
export class KeyedSet<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>>
|
|
extends EncodableSet<T, K>
|
|
{
|
|
get [DictionaryType](): DictionaryType {
|
|
return 'Set';
|
|
}
|
|
|
|
static isKeyedSet<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>>(
|
|
x: any,
|
|
): x is KeyedSet<T, K> {
|
|
return x?.[DictionaryType] === 'Set';
|
|
}
|
|
|
|
constructor(items?: Iterable<K>) {
|
|
super(k => k, items);
|
|
}
|
|
|
|
map<R extends Embeddable = GenericEmbedded, S extends Value<R> = Value<R>>(
|
|
f: (value: K) => S,
|
|
): KeyedSet<R, S> {
|
|
return new KeyedSet(_iterMap(this[Symbol.iterator](), f));
|
|
}
|
|
|
|
filter(f: (value: K) => boolean): KeyedSet<T, K> {
|
|
const result = new KeyedSet<T, K>();
|
|
for (let k of this) if (f(k)) result.add(k);
|
|
return result;
|
|
}
|
|
|
|
clone(): KeyedSet<T, K> {
|
|
return new KeyedSet(this);
|
|
}
|
|
|
|
get [Symbol.toStringTag]() { return 'Set'; }
|
|
}
|
|
|
|
export class Set<T extends Embeddable = GenericEmbedded> extends KeyedSet<T> {
|
|
static isSet<T extends Embeddable = GenericEmbedded>(x: any): x is Set<T> {
|
|
return x?.[DictionaryType] === 'Set';
|
|
}
|
|
|
|
static __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Set<T> {
|
|
return Set.isSet<T>(v) ? v : void 0;
|
|
}
|
|
}
|
|
|
|
export function encodeSetOn<T extends Embeddable, V>(
|
|
s: IdentitySet<V>,
|
|
encoder: Encoder<T>,
|
|
encodeV: (v: V, encoder: Encoder<T>) => void,
|
|
) {
|
|
if (encoder.canonical) {
|
|
const canonicalEncoder = new Encoder<T>({
|
|
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<T extends Embeddable, V>(
|
|
s: IdentitySet<V>,
|
|
w: Writer<T>,
|
|
writeV: (v: V, w: Writer<T>) => void,
|
|
) {
|
|
w.state.writeSeq('#{', '}', s, vv => writeV(vv, w));
|
|
}
|