preserves/implementations/javascript/src/dictionary.ts

126 lines
4.1 KiB
TypeScript

import { canonicalEncode, canonicalString, Encoder } from "./codec";
import { Tag } from "./constants";
import { FlexMap, FlexSet, _iterMap } from "./flex";
import { PreserveOn } from "./symbols";
import { stringify } from "./text";
import { DefaultPointer, fromJS, Value } from "./values";
import { Bytes } from './bytes';
export type DictionaryType = 'Dictionary' | 'Set';
export const DictionaryType = Symbol.for('DictionaryType');
export class Dictionary<V, T extends object = DefaultPointer> extends FlexMap<Value<T>, V> {
get [DictionaryType](): DictionaryType {
return 'Dictionary';
}
static isDictionary<V, T extends object = DefaultPointer>(x: any): x is Dictionary<V, T> {
return x?.[DictionaryType] === 'Dictionary';
}
static fromJS<V extends object = DefaultPointer, T extends object = DefaultPointer>(x: object): Dictionary<Value<V>, T> {
if (Dictionary.isDictionary<V, T>(x)) return x as Dictionary<Value<V>, T>;
const d = new Dictionary<Value<V>, T>();
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
return d;
}
constructor(items?: Iterable<readonly [any, V]>) {
const iter = items?.[Symbol.iterator]();
super(canonicalString, iter === void 0 ? void 0 : _iterMap(iter, ([k,v]) => [fromJS(k), v]));
}
mapEntries<W, R extends object = DefaultPointer>(f: (entry: [Value<T>, V]) => [Value<R>, W]): Dictionary<W, R> {
const result = new Dictionary<W, R>();
for (let oldEntry of this.entries()) {
const newEntry = f(oldEntry);
result.set(newEntry[0], newEntry[1])
}
return result;
}
asPreservesText(): string {
return '{' +
Array.from(_iterMap(this.entries(), ([k, v]) =>
k.asPreservesText() + ': ' + stringify(v))).join(', ') +
'}';
}
clone(): Dictionary<V, T> {
return new Dictionary(this);
}
toString(): string {
return this.asPreservesText();
}
get [Symbol.toStringTag]() { return 'Dictionary'; }
[PreserveOn](encoder: Encoder<T>) {
if (encoder.canonical) {
const pieces = Array.from(this).map(([k, v]) =>
Bytes.concat([canonicalEncode(k), canonicalEncode(v)]));
pieces.sort(Bytes.compare);
encoder.encoderawvalues(Tag.Dictionary, pieces);
} else {
encoder.emitbyte(Tag.Dictionary);
this.forEach((v, k) => {
encoder.push(k);
encoder.push(v as unknown as Value<T>); // Suuuuuuuper unsound
});
encoder.emitbyte(Tag.End);
}
}
}
export class Set<T extends object = DefaultPointer> extends FlexSet<Value<T>> {
get [DictionaryType](): DictionaryType {
return 'Set';
}
static isSet<T extends object = DefaultPointer>(x: any): x is Set<T> {
return x?.[DictionaryType] === 'Set';
}
constructor(items?: Iterable<any>) {
const iter = items?.[Symbol.iterator]();
super(canonicalString, iter === void 0 ? void 0 : _iterMap<any, Value<T>>(iter, fromJS));
}
map<R extends object = DefaultPointer>(f: (value: Value<T>) => Value<R>): Set<R> {
return new Set(_iterMap(this[Symbol.iterator](), f));
}
filter(f: (value: Value<T>) => boolean): Set<T> {
const result = new Set<T>();
for (let k of this) if (f(k)) result.add(k);
return result;
}
toString(): string {
return this.asPreservesText();
}
asPreservesText(): string {
return '#{' +
Array.from(_iterMap(this.values(), v => v.asPreservesText())).join(', ') +
'}';
}
clone(): Set<T> {
return new Set(this);
}
get [Symbol.toStringTag]() { return 'Set'; }
[PreserveOn](encoder: Encoder<T>) {
if (encoder.canonical) {
const pieces = Array.from(this).map(k => canonicalEncode(k));
pieces.sort(Bytes.compare);
encoder.encoderawvalues(Tag.Set, pieces);
} else {
encoder.encodevalues(Tag.Set, this);
}
}
}