import { Encoder, canonicalEncode, canonicalString } from "./encoder"; import { Tag } from "./constants"; import { FlexMap, FlexSet, _iterMap } from "./flex"; import { PreserveOn } from "./symbols"; import { stringify } from "./text"; import { DefaultPointer, Value } from "./values"; import { Bytes } from './bytes'; import { fromJS } from "./fromjs"; export type DictionaryType = 'Dictionary' | 'Set'; export const DictionaryType = Symbol.for('DictionaryType'); export class KeyedDictionary, V, T extends object = DefaultPointer> extends FlexMap { get [DictionaryType](): DictionaryType { return 'Dictionary'; } static isKeyedDictionary, V, T extends object = DefaultPointer>(x: any): x is KeyedDictionary { return x?.[DictionaryType] === 'Dictionary'; } constructor(items?: readonly [K, V][]); constructor(items?: Iterable); constructor(items?: Iterable) { super(canonicalString, items); } mapEntries, R extends object = DefaultPointer>(f: (entry: [K, V]) => [S, W]): KeyedDictionary { const result = new KeyedDictionary(); 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(): KeyedDictionary { return new KeyedDictionary(this); } toString(): string { return this.asPreservesText(); } get [Symbol.toStringTag]() { return 'Dictionary'; } [PreserveOn](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.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.emitbyte(Tag.End); } else { encoder.emitbyte(Tag.Dictionary); this.forEach((v, k) => { encoder.push(k); encoder.push(v as unknown as Value); // Suuuuuuuper unsound }); encoder.emitbyte(Tag.End); } } } export class Dictionary extends KeyedDictionary, V, T> { static isDictionary(x: any): x is Dictionary { return x?.[DictionaryType] === 'Dictionary'; } static fromJS(x: object): Dictionary, T> { if (Dictionary.isDictionary, T>(x)) return x as Dictionary, T>; const d = new Dictionary, T>(); Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value))); return d; } } export class KeyedSet, T extends object = DefaultPointer> extends FlexSet { get [DictionaryType](): DictionaryType { return 'Set'; } static isKeyedSet, T extends object = DefaultPointer>(x: any): x is KeyedSet { return x?.[DictionaryType] === 'Set'; } constructor(items?: Iterable) { super(canonicalString, items); } map, R extends object = DefaultPointer>(f: (value: K) => S): KeyedSet { return new KeyedSet(_iterMap(this[Symbol.iterator](), f)); } filter(f: (value: K) => boolean): KeyedSet { const result = new KeyedSet(); 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 { return new Set(this); } get [Symbol.toStringTag]() { return 'Set'; } [PreserveOn](encoder: Encoder) { 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); } } } export class Set extends KeyedSet, T> { static isSet(x: any): x is Set { return x?.[DictionaryType] === 'Set'; } }