Better collections; package types
This commit is contained in:
parent
55db55b42b
commit
b0ed7e914b
|
@ -9,6 +9,7 @@
|
||||||
},
|
},
|
||||||
"repository": "gitlab:preserves/preserves",
|
"repository": "gitlab:preserves/preserves",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
|
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.19",
|
"@types/jest": "^26.0.19",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
underlying,
|
underlying,
|
||||||
Annotated,
|
Annotated,
|
||||||
Dictionary, Set, Bytes, Record, Single, Double,
|
Dictionary, Set, Bytes, Record, Single, Double,
|
||||||
isSet, isDictionary,
|
|
||||||
BytesLike,
|
BytesLike,
|
||||||
Value,
|
Value,
|
||||||
} from './values';
|
} from './values';
|
||||||
|
@ -117,8 +116,8 @@ export class Decoder {
|
||||||
return this.includeAnnotations ? new Annotated(v) : v;
|
return this.includeAnnotations ? new Annotated(v) : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dictionaryFromArray(vs: Value[]): Dictionary {
|
static dictionaryFromArray(vs: Value[]): Dictionary<Value> {
|
||||||
const d = new Dictionary();
|
const d = new Dictionary<Value>();
|
||||||
if (vs.length % 2) throw new DecodeError("Missing dictionary value");
|
if (vs.length % 2) throw new DecodeError("Missing dictionary value");
|
||||||
for (let i = 0; i < vs.length; i += 2) {
|
for (let i = 0; i < vs.length; i += 2) {
|
||||||
d.set(vs[i], vs[i+1]);
|
d.set(vs[i], vs[i+1]);
|
||||||
|
@ -294,7 +293,7 @@ export class Encoder {
|
||||||
this.emitbyte(Tag.End);
|
this.emitbyte(Tag.End);
|
||||||
}
|
}
|
||||||
|
|
||||||
push(v: Value) {
|
push(v: any) {
|
||||||
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
|
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
|
||||||
v[PreserveOn](this);
|
v[PreserveOn](this);
|
||||||
}
|
}
|
||||||
|
@ -327,30 +326,6 @@ export class Encoder {
|
||||||
else if (Array.isArray(v)) {
|
else if (Array.isArray(v)) {
|
||||||
this.encodevalues(Tag.Sequence, v);
|
this.encodevalues(Tag.Sequence, v);
|
||||||
}
|
}
|
||||||
else if (isSet(v)) {
|
|
||||||
if (this.canonical) {
|
|
||||||
const pieces = v._map((_v, k) => encode(k, { canonical: true }));
|
|
||||||
pieces.sort(Bytes.compare);
|
|
||||||
this.encoderawvalues(Tag.Set, pieces);
|
|
||||||
} else {
|
|
||||||
this.encodevalues(Tag.Set, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (isDictionary(v)) {
|
|
||||||
if (this.canonical) {
|
|
||||||
const pieces = v._map((v, k) => Bytes.concat([encode(k, { canonical: true }),
|
|
||||||
encode(v, { canonical: true })]));
|
|
||||||
pieces.sort(Bytes.compare);
|
|
||||||
this.encoderawvalues(Tag.Dictionary, pieces);
|
|
||||||
} else {
|
|
||||||
this.emitbyte(Tag.Dictionary);
|
|
||||||
v._forEach((v, k) => {
|
|
||||||
this.push(k);
|
|
||||||
this.push(v);
|
|
||||||
});
|
|
||||||
this.emitbyte(Tag.End);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') {
|
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') {
|
||||||
this.encodevalues(Tag.Sequence, v as Iterable<Value>);
|
this.encodevalues(Tag.Sequence, v as Iterable<Value>);
|
||||||
}
|
}
|
||||||
|
@ -361,10 +336,10 @@ export class Encoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encode(v: Value, options?: EncoderOptions): Bytes {
|
export function encode(v: any, options?: EncoderOptions): Bytes {
|
||||||
return new Encoder(options).push(v).contents();
|
return new Encoder(options).push(v).contents();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeWithAnnotations(v: Value, options: EncoderOptions = {}): Bytes {
|
export function encodeWithAnnotations(v: any, options: EncoderOptions = {}): Bytes {
|
||||||
return encode(v, { ... options, includeAnnotations: true });
|
return encode(v, { ... options, includeAnnotations: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
// FlexMap, FlexSet: like built-in Map and Set, but with a
|
||||||
|
// canonicalization function which gives us the possibility of a
|
||||||
|
// coarser equivalence than the identity equivalence used in Map and
|
||||||
|
// Set.
|
||||||
|
|
||||||
|
// A Canonicalizer represents the equivalence you have in mind. For
|
||||||
|
//
|
||||||
|
// c: Canonicalizer<V>
|
||||||
|
// eqv: Equivalence<V>
|
||||||
|
// v1: V
|
||||||
|
// v2: V
|
||||||
|
//
|
||||||
|
// where `eqv` is the equivalence you want,
|
||||||
|
//
|
||||||
|
// eqv(v1, v2) ⇔ c(v1) === c(v2)
|
||||||
|
//
|
||||||
|
export type Canonicalizer<V> = (v: V) => string;
|
||||||
|
export type Equivalence<V> = (v1: V, v2: V) => boolean;
|
||||||
|
|
||||||
|
export type IdentityMap<K, V> = Map<K, V>;
|
||||||
|
export type IdentitySet<V> = Set<V>;
|
||||||
|
export const IdentityMap = Map;
|
||||||
|
export const IdentitySet = Set;
|
||||||
|
|
||||||
|
export function _iterMap<S,T>(i: Iterator<S> | undefined, f : (s: S) => T): IterableIterator<T> {
|
||||||
|
if (!i) return void 0;
|
||||||
|
const _f = (r: IteratorResult<S>): IteratorResult<T> => {
|
||||||
|
const { done, value } = r;
|
||||||
|
return { done, value: done ? void 0 : f(value) };
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
next: (v?: any): IteratorResult<T> => _f(i.next(v)),
|
||||||
|
return: (v?: any): IteratorResult<T> => _f(i.return(v)),
|
||||||
|
throw: (e?: any): IteratorResult<T> => _f(i.throw(e)),
|
||||||
|
[Symbol.iterator]() { return this; },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FlexMap<K, V> implements Map<K, V> {
|
||||||
|
readonly items: Map<string, [K, V]>;
|
||||||
|
readonly canonicalizer: Canonicalizer<K>;
|
||||||
|
|
||||||
|
constructor(c: Canonicalizer<K>, items?: Iterable<readonly [K, V]>) {
|
||||||
|
this.canonicalizer = c;
|
||||||
|
this.items = new Map((items === void 0)
|
||||||
|
? void 0
|
||||||
|
: _iterMap(items[Symbol.iterator](), ([k, v]) => [this._key(k), [k, v]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
_key(k: K): string {
|
||||||
|
return this.canonicalizer(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(k: K, defaultValue?: V): V | undefined {
|
||||||
|
const e = this.items.get(this._key(k));
|
||||||
|
return (e === void 0) ? defaultValue : e[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(k: K, v: V): this {
|
||||||
|
this.items.set(this._key(k), [k, v]);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(f: <T extends Map<K, V>> (v: V, k: K, map: T) => void, thisArg?: any) {
|
||||||
|
this.items.forEach(([k, v]) => f.call(thisArg, v, k, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries(): IterableIterator<[K, V]> {
|
||||||
|
return this.items.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
keys(): IterableIterator<K> {
|
||||||
|
return _iterMap(this.items.values(), ([k, _v]) => k);
|
||||||
|
}
|
||||||
|
|
||||||
|
values(): IterableIterator<V> {
|
||||||
|
return _iterMap(this.items.values(), ([_k, v]) => v);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(k: K): boolean {
|
||||||
|
return this.items.delete(this._key(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.items.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
has(k: K): boolean {
|
||||||
|
return this.items.has(this._key(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
get size(): number {
|
||||||
|
return this.items.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator](): IterableIterator<[K, V]> {
|
||||||
|
return this.items.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.toStringTag] = 'FlexMap';
|
||||||
|
|
||||||
|
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 this.items.values()) {
|
||||||
|
if (!other.has(k)) return false;
|
||||||
|
if (!eqv(v, other.get(k))) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(key: K,
|
||||||
|
f: (oldValue?: V) => V | undefined,
|
||||||
|
defaultValue?: V,
|
||||||
|
eqv: Equivalence<V> = (v1, v2) => v1 === v2): number
|
||||||
|
{
|
||||||
|
const ks = this._key(key);
|
||||||
|
if (this.items.has(ks)) {
|
||||||
|
const oldValue = this.items.get(ks)[1];
|
||||||
|
const newValue = f(oldValue);
|
||||||
|
if (newValue === void 0) {
|
||||||
|
this.items.delete(ks);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
if (!eqv(newValue, oldValue)) this.items.set(ks, [key, newValue]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newValue = f(defaultValue);
|
||||||
|
if (newValue === void 0) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
this.items.set(ks, [key, newValue]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalKeys(): IterableIterator<string> {
|
||||||
|
return this.items.keys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FlexSet<V> implements Set<V> {
|
||||||
|
readonly items: Map<string, V>;
|
||||||
|
readonly canonicalizer: Canonicalizer<V>;
|
||||||
|
|
||||||
|
constructor(c: Canonicalizer<V>, items?: Iterable<V>) {
|
||||||
|
this.canonicalizer = c;
|
||||||
|
this.items = new Map((items === void 0)
|
||||||
|
? void 0
|
||||||
|
: _iterMap(items[Symbol.iterator](), (v) => [this._key(v), v]));
|
||||||
|
}
|
||||||
|
|
||||||
|
_key(v: V): string {
|
||||||
|
return this.canonicalizer(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
has(v: V): boolean {
|
||||||
|
return this.items.has(this._key(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
get(v: V): {item: V} | null {
|
||||||
|
const vs = this._key(v);
|
||||||
|
if (this.items.has(vs)) {
|
||||||
|
return {item: this.items[vs]};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(v: V): this {
|
||||||
|
this.items[this._key(v)] = v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(f: <T extends Set<V>>(v: V, v2: V, set: T) => void, thisArg?: any) {
|
||||||
|
this.items.forEach((v) => f.call(thisArg, v, v, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries(): IterableIterator<[V, V]> {
|
||||||
|
return _iterMap(this.items.values(), (v) => [v, v]);
|
||||||
|
}
|
||||||
|
|
||||||
|
keys(): IterableIterator<V> {
|
||||||
|
return this.items.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
values(): IterableIterator<V> {
|
||||||
|
return this.items.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(v: V): boolean {
|
||||||
|
return this.items.delete(this._key(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.items.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
get size(): number {
|
||||||
|
return this.items.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator](): IterableIterator<V> {
|
||||||
|
return this.items.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.toStringTag] = 'FlexSet';
|
||||||
|
|
||||||
|
equals(other: any): boolean {
|
||||||
|
if (!('size' in other && 'has' in other)) return false;
|
||||||
|
if (this.size !== other.size) return false;
|
||||||
|
for (let v of this.items.values()) {
|
||||||
|
if (!other.has(v)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalValues(): IterableIterator<string> {
|
||||||
|
return this.items.keys();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './flex';
|
||||||
export * from './symbols';
|
export * from './symbols';
|
||||||
export * from './codec';
|
export * from './codec';
|
||||||
export * from './values';
|
export * from './values';
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import { Value } from './values';
|
import { Value } from './values';
|
||||||
|
|
||||||
export function stringify(x: Value): string {
|
export function stringify(x: any): string {
|
||||||
if (typeof x === 'object' && x !== null && 'asPreservesText' in x) {
|
if (typeof x === 'object' && x !== null && 'asPreservesText' in x) {
|
||||||
return x.asPreservesText();
|
return x.asPreservesText();
|
||||||
} else {
|
} else {
|
||||||
return JSON.stringify(x);
|
try {
|
||||||
|
return JSON.stringify(x);
|
||||||
|
} catch (_e) {
|
||||||
|
return ('' + x).asPreservesText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
import { PreserveOn, AsPreserve } from './symbols';
|
import { PreserveOn, AsPreserve } from './symbols';
|
||||||
import { Tag } from './constants';
|
import { Tag } from './constants';
|
||||||
import { Encoder, encode } from './codec';
|
import { Encoder, encode } from './codec';
|
||||||
|
import { stringify } from './text';
|
||||||
|
import { _iterMap, FlexMap, FlexSet, IdentityMap, IdentitySet } from './flex';
|
||||||
|
|
||||||
const textEncoder = new TextEncoder();
|
const textEncoder = new TextEncoder();
|
||||||
const textDecoder = new TextDecoder();
|
const textDecoder = new TextDecoder();
|
||||||
|
|
||||||
export type Value = Atom | Compound | Annotated;
|
export type Value = Atom | Compound | Annotated;
|
||||||
export type Atom = boolean | Single | Double | number | string | Bytes | symbol;
|
export type Atom = boolean | Single | Double | number | string | Bytes | symbol;
|
||||||
export type Compound = Record | Array<Value> | Set | Dictionary;
|
export type Compound = Record | Array<Value> | Set | Dictionary<Value>;
|
||||||
|
|
||||||
export function isRecord(x: any): x is Record {
|
export function isRecord(x: any): x is Record {
|
||||||
return Array.isArray(x) && 'label' in x;
|
return Array.isArray(x) && 'label' in x;
|
||||||
|
@ -517,12 +519,14 @@ export interface RecordConstructorInfo {
|
||||||
arity: number;
|
arity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function is(a: Value, b: Value): boolean {
|
export function is(a: any, b: any): boolean {
|
||||||
|
if (isAnnotated(a)) a = a.item;
|
||||||
|
if (isAnnotated(b)) b = b.item;
|
||||||
if (Object.is(a, b)) return true;
|
if (Object.is(a, b)) return true;
|
||||||
if (typeof a !== typeof b) return false;
|
if (typeof a !== typeof b) return false;
|
||||||
if (typeof a === 'object') {
|
if (typeof a === 'object') {
|
||||||
if (a === null || b === null) return false;
|
if (a === null || b === null) return false;
|
||||||
if ('equals' in a) return a.equals(b);
|
if ('equals' in a) return a.equals(b, is);
|
||||||
if (Array.isArray(a) && Array.isArray(b)) {
|
if (Array.isArray(a) && Array.isArray(b)) {
|
||||||
if (a.length !== b.length) return false;
|
if (a.length !== b.length) return false;
|
||||||
for (let i = 0; i < a.length; i++) if (!is(a[i], b[i])) return false;
|
for (let i = 0; i < a.length; i++) if (!is(a[i], b[i])) return false;
|
||||||
|
@ -542,176 +546,119 @@ export function isClassOf(ci: RecordConstructorInfo, v: any): v is Record {
|
||||||
|
|
||||||
export type DictionaryType = 'Dictionary' | 'Set';
|
export type DictionaryType = 'Dictionary' | 'Set';
|
||||||
|
|
||||||
export function is_Dictionary(x: any, t: DictionaryType): x is _Dictionary {
|
export function is_Dictionary(x: any, t: DictionaryType): boolean {
|
||||||
return typeof x === 'object' && x !== null &&
|
return typeof x === 'object' && x !== null && x[Symbol.toStringTag] === t;
|
||||||
'_items' in x &&
|
|
||||||
'_dictionaryType' in x &&
|
|
||||||
x._dictionaryType() === t;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isDictionary = (x: any): x is Dictionary => is_Dictionary(x, 'Dictionary');
|
export const isDictionary = <T> (x: any): x is Dictionary<T> => is_Dictionary(x, 'Dictionary');
|
||||||
export const isSet = (x: any): x is Set => is_Dictionary(x, 'Set');
|
export const isSet = (x: any): x is Set => is_Dictionary(x, 'Set');
|
||||||
|
|
||||||
export type DictionaryEntry = [Value, Value];
|
export function _canonicalString(item: Value): string {
|
||||||
|
const bs = encode(item, { canonical: true })._view;
|
||||||
|
const s = String.fromCharCode.apply(null, bs);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class _Dictionary {
|
export class Dictionary<T> extends FlexMap<Value, T> {
|
||||||
_items: { [key: string]: DictionaryEntry } = {};
|
static fromJS(x: object): Dictionary<Value> {
|
||||||
|
if (isDictionary(x)) return x as Dictionary<Value>;
|
||||||
_key(key: Value): string {
|
const d = new Dictionary<Value>();
|
||||||
const bs = encode(key, { canonical: true })._view;
|
for (let key in x) {
|
||||||
const s = String.fromCharCode.apply(null, bs);
|
const value = x[key];
|
||||||
return s;
|
d.set(key, fromJS(value));
|
||||||
}
|
|
||||||
|
|
||||||
_lookup(key: Value): DictionaryEntry | null {
|
|
||||||
const k = this._key(key);
|
|
||||||
return k in this._items ? this._items[k] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_set(key: Value, value: Value) {
|
|
||||||
this._items[this._key(key)] = [key, value];
|
|
||||||
}
|
|
||||||
|
|
||||||
_get(key: Value, defaultValue?: Value): Value {
|
|
||||||
const k = this._key(key);
|
|
||||||
return k in this._items ? this._items[k][1] : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_delete(key: Value) {
|
|
||||||
delete this._items[this._key(key)];
|
|
||||||
}
|
|
||||||
|
|
||||||
_forEach(f: (value: Value, key: Value) => void) {
|
|
||||||
for (let ks in this._items) {
|
|
||||||
const [k, v] = this._items[ks];
|
|
||||||
f(v, k);
|
|
||||||
}
|
}
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
_map<T>(f: (value: Value, key: Value) => T): Array<T> {
|
constructor(items?: Iterable<readonly [any, T]>) {
|
||||||
const result = [];
|
super(_canonicalString, _iterMap(items?.[Symbol.iterator](), ([k,v]) => [fromJS(k), v]));
|
||||||
for (let ks in this._items) {
|
}
|
||||||
const [k, v] = this._items[ks];
|
|
||||||
result.push(f(v, k));
|
mapEntries<R>(f: (entry: [Value, T]) => [Value, R]): Dictionary<R> {
|
||||||
|
const result = new Dictionary<R>();
|
||||||
|
for (let oldEntry of this.entries()) {
|
||||||
|
const newEntry = f(oldEntry);
|
||||||
|
result.set(newEntry[0], newEntry[1])
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: any): boolean {
|
asPreservesText(): string {
|
||||||
if (!Object.is(other.constructor, this.constructor)) return false;
|
return '{' +
|
||||||
const es1 = Object.entries(this._items);
|
Array.from(_iterMap(this.entries(), ([k, v]) =>
|
||||||
if (es1.length !== Object.entries(other._items).length) return false;
|
k.asPreservesText() + ': ' + stringify(v))).join(', ') +
|
||||||
for (let [ks1, e1] of es1) {
|
'}';
|
||||||
const e2 = other._items[ks1];
|
}
|
||||||
if (!is(e1[1], e2[1])) return false;
|
|
||||||
}
|
clone(): Dictionary<T> {
|
||||||
return true;
|
return new Dictionary(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return this.asPreservesText();
|
return this.asPreservesText();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract asPreservesText(): string;
|
[Symbol.toStringTag] = 'Dictionary';
|
||||||
abstract _dictionaryType(): DictionaryType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Dictionary extends _Dictionary {
|
[PreserveOn](encoder: Encoder) {
|
||||||
static fromJS(x: object): Dictionary {
|
if (encoder.canonical) {
|
||||||
if (isDictionary(x)) return x;
|
const pieces = Array.from(this).map(([k, v]) =>
|
||||||
const d = new Dictionary();
|
Bytes.concat([encode(k, { canonical: true }),
|
||||||
for (let key in x) {
|
encode(v, { canonical: true })]));
|
||||||
const value = x[key];
|
pieces.sort(Bytes.compare);
|
||||||
d._set(key, fromJS(value));
|
encoder.encoderawvalues(Tag.Dictionary, pieces);
|
||||||
|
} else {
|
||||||
|
encoder.emitbyte(Tag.Dictionary);
|
||||||
|
this.forEach((v, k) => {
|
||||||
|
encoder.push(k);
|
||||||
|
encoder.push(v);
|
||||||
|
});
|
||||||
|
encoder.emitbyte(Tag.End);
|
||||||
}
|
}
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
_dictionaryType(): DictionaryType {
|
|
||||||
return 'Dictionary';
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: Value, value: Value) {
|
|
||||||
this._set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key: Value, defaultValue?: Value): Value {
|
|
||||||
return this._get(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(key: Value) {
|
|
||||||
this._delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
mapEntries(f: (entry: DictionaryEntry) => DictionaryEntry): Dictionary {
|
|
||||||
const result = new Dictionary();
|
|
||||||
for (let ks in this._items) {
|
|
||||||
const oldEntry = this._items[ks];
|
|
||||||
const newEntry = f(oldEntry);
|
|
||||||
result._set(newEntry[0], newEntry[1])
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(f: (value: Value, key: Value) => void) {
|
|
||||||
this._forEach(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
asPreservesText(): string {
|
|
||||||
return '{' +
|
|
||||||
this._map((v, k) => k.asPreservesText() + ': ' + v.asPreservesText()).join(', ') +
|
|
||||||
'}';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Set extends _Dictionary implements Iterable<Value> {
|
export class Set extends FlexSet<Value> {
|
||||||
constructor(items: Iterable<any> = []) {
|
constructor(items?: Iterable<any>) {
|
||||||
super();
|
super(_canonicalString, _iterMap(items?.[Symbol.iterator](), fromJS));
|
||||||
for (let item of items) this.add(fromJS(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
_dictionaryType(): DictionaryType {
|
|
||||||
return 'Set';
|
|
||||||
}
|
|
||||||
|
|
||||||
add(v: Value) {
|
|
||||||
this._set(v, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(v: Value) {
|
|
||||||
this._delete(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
includes(key: Value) {
|
|
||||||
return this._lookup(key) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(f: (value: Value) => void) {
|
|
||||||
this._forEach((_v, k) => f(k));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
map(f: (value: Value) => Value): Set {
|
map(f: (value: Value) => Value): Set {
|
||||||
const result = new Set();
|
return new Set(_iterMap(this[Symbol.iterator](), f));
|
||||||
for (let ks in this._items) {
|
|
||||||
const k = this._items[ks][0];
|
|
||||||
result._set(f(k), true);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator](): Iterator<Value> {
|
filter(f: (value: Value) => boolean): Set {
|
||||||
return this._map((_v, k) => k)[Symbol.iterator]();
|
const result = new Set();
|
||||||
|
for (let k of this) if (f(k)) result.add(k);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
asPreservesText(): string {
|
asPreservesText(): string {
|
||||||
return '#{' +
|
return '#{' +
|
||||||
this._map((_v, k) => k.asPreservesText()).join(', ') +
|
Array.from(_iterMap(this.values(), v => v.asPreservesText())).join(', ') +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone(): Set {
|
||||||
|
return new Set(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.toStringTag] = 'Set';
|
||||||
|
|
||||||
|
[PreserveOn](encoder: Encoder) {
|
||||||
|
if (encoder.canonical) {
|
||||||
|
const pieces = Array.from(this).map(k => encode(k, { canonical: true }));
|
||||||
|
pieces.sort(Bytes.compare);
|
||||||
|
encoder.encoderawvalues(Tag.Set, pieces);
|
||||||
|
} else {
|
||||||
|
encoder.encodevalues(Tag.Set, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAnnotated(x: any): x is Annotated {
|
export function isAnnotated(x: any): x is Annotated {
|
||||||
return typeof x === 'object' && x !== null &&
|
return typeof x === 'object' && x !== null &&
|
||||||
|
x.constructor.name === 'Annotated' &&
|
||||||
'annotations' in x &&
|
'annotations' in x &&
|
||||||
'item' in x;
|
'item' in x;
|
||||||
}
|
}
|
||||||
|
@ -740,7 +687,7 @@ export class Annotated {
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: any): boolean {
|
equals(other: any): boolean {
|
||||||
return isAnnotated(other) && is(this.item, other.item);
|
return is(this.item, isAnnotated(other) ? other.item : other);
|
||||||
}
|
}
|
||||||
|
|
||||||
hashCode(): number {
|
hashCode(): number {
|
||||||
|
|
|
@ -132,7 +132,7 @@ describe('common test suite', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const tests = peel(TestCases._.cases(peel(samples))) as Dictionary;
|
const tests = peel(TestCases._.cases(peel(samples))) as Dictionary<Value>;
|
||||||
tests.forEach((t0: Value, tName0: Value) => {
|
tests.forEach((t0: Value, tName0: Value) => {
|
||||||
const tName = Symbol.keyFor(strip(tName0) as symbol);
|
const tName = Symbol.keyFor(strip(tName0) as symbol);
|
||||||
const t = peel(t0) as Record;
|
const t = peel(t0) as Record;
|
||||||
|
|
Loading…
Reference in New Issue