Mark Embeddable objects specially, so that plain JS objects can be used as symbol-to-value maps.
This commit is contained in:
parent
055a7f90e9
commit
4f4ff6e108
|
@ -219,13 +219,17 @@ export class Decoder<T extends Embeddable = never> implements TypedDecoder<T> {
|
|||
}
|
||||
|
||||
static dictionaryFromArray<T extends Embeddable>(vs: Value<T>[]): Dictionary<T> {
|
||||
const d = new Dictionary<T>();
|
||||
const entries: [Value<T>, Value<T>][] = [];
|
||||
if (vs.length % 2) throw new DecodeError("Missing dictionary value");
|
||||
for (let i = 0; i < vs.length; i += 2) {
|
||||
if (d.has(vs[i])) throw new DecodeError(`Duplicate key: ${stringify(vs[i])}`);
|
||||
d.set(vs[i], vs[i+1]);
|
||||
entries.push([vs[i], vs[i+1]]);
|
||||
}
|
||||
const r = Dictionary.from<T, typeof entries[0]>(entries, true);
|
||||
if ('duplicate' in r) {
|
||||
throw new DecodeError("Duplicate key");
|
||||
} else {
|
||||
return r.ok;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
next(): Value<T> {
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import { Encoder, canonicalString } from "./encoder";
|
||||
import { Tag } from "./constants";
|
||||
import { FlexMap, FlexSet, _iterMap, IdentitySet } from "./flex";
|
||||
import { FlexMap, FlexSet, _iterMap, IdentitySet, Equivalence } from "./flex";
|
||||
import { Value } from "./values";
|
||||
import { Bytes } from './bytes';
|
||||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
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, JsDictionaryMap } from "./jsdictionary";
|
||||
|
||||
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<K, V, T extends Embeddable = GenericEmbedded> extends FlexMap<K, V>
|
||||
export class EncodableDictionary<T extends Embeddable, K, V> extends FlexMap<K, V>
|
||||
implements Preservable<T>, PreserveWritable<T>
|
||||
{
|
||||
constructor(
|
||||
|
@ -39,16 +41,16 @@ export class EncodableDictionary<K, V, T extends Embeddable = GenericEmbedded> e
|
|||
}
|
||||
}
|
||||
|
||||
export class KeyedDictionary<K extends CompoundKey<T>, V, T extends Embeddable = GenericEmbedded>
|
||||
extends EncodableDictionary<K, V, T>
|
||||
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<K extends CompoundKey<T>, V, T extends Embeddable = GenericEmbedded>(
|
||||
static isKeyedDictionary<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>, V = Value<T>>(
|
||||
x: any,
|
||||
): x is KeyedDictionary<K, V, T> {
|
||||
): x is KeyedDictionary<T, K, V> {
|
||||
return x?.[DictionaryType] === 'Dictionary';
|
||||
}
|
||||
|
||||
|
@ -58,35 +60,110 @@ export class KeyedDictionary<K extends CompoundKey<T>, V, T extends Embeddable =
|
|||
super(k => k, v => v as CompoundKey<T>, items);
|
||||
}
|
||||
|
||||
mapEntries<W, S extends Value<R>, R extends Embeddable = GenericEmbedded>(
|
||||
f: (entry: [K, V]) => [S, W],
|
||||
): KeyedDictionary<S, W, R> {
|
||||
const result = new KeyedDictionary<S, W, R>();
|
||||
for (let oldEntry of this.entries()) {
|
||||
const newEntry = f(oldEntry);
|
||||
result.set(newEntry[0], newEntry[1])
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
clone(): KeyedDictionary<K, V, T> {
|
||||
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 class Dictionary<T extends Embeddable = GenericEmbedded, V = Value<T>> extends KeyedDictionary<Value<T>, V, T> {
|
||||
static isDictionary<T extends Embeddable = GenericEmbedded, V = Value<T>>(x: any): x is Dictionary<T, V> {
|
||||
return x?.[DictionaryType] === 'Dictionary';
|
||||
export type Dictionary<T extends Embeddable = GenericEmbedded, V = Value<T>> =
|
||||
JsDictionary<V> | KeyedDictionary<T, Value<T>, V>;
|
||||
|
||||
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:
|
||||
if (Array.isArray(x)) return false;
|
||||
if (isEmbedded(x)) return false;
|
||||
if (Float.isFloat(x)) return false;
|
||||
if (Bytes.isBytes(x)) return false;
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
static __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Dictionary<T> {
|
||||
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>;
|
||||
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
||||
x: any
|
||||
): Map<Value<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[]]>(
|
||||
entries: Iterable<E>, failOnDuplicateKeys: true): {ok: Dictionary<T>} | {duplicate: E};
|
||||
export function from<T extends Embeddable, E extends [Value<T>, Value<T>, ... any[]]>(
|
||||
entries: Iterable<E>, failOnDuplicateKeys: false): Dictionary<T>;
|
||||
export function from<T extends Embeddable, E extends [Value<T>, Value<T>, ... any[]]>(
|
||||
entries: Iterable<E>): Dictionary<T>;
|
||||
export function from<T extends Embeddable, E extends [Value<T>, Value<T>, ... any[]]>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export function __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Dictionary<T> {
|
||||
return Dictionary.isDictionary<T>(v) ? v : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeDictionaryOn<K, V, T extends Embeddable>(
|
||||
export function encodeDictionaryOn<T extends Embeddable, K, V>(
|
||||
dict: Map<K, V>,
|
||||
encoder: Encoder<T>,
|
||||
encodeK: (k: K, encoder: Encoder<T>) => void,
|
||||
|
@ -116,7 +193,7 @@ export function encodeDictionaryOn<K, V, T extends Embeddable>(
|
|||
}
|
||||
}
|
||||
|
||||
export function writeDictionaryOn<K, V, T extends Embeddable>(
|
||||
export function writeDictionaryOn<T extends Embeddable, K, V>(
|
||||
dict: Map<K, V>,
|
||||
w: Writer<T>,
|
||||
writeK: (k: K, w: Writer<T>) => void,
|
||||
|
@ -137,7 +214,7 @@ export function writeDictionaryOn<K, V, T extends Embeddable>(
|
|||
});
|
||||
}
|
||||
|
||||
export class EncodableSet<V, T extends Embeddable = GenericEmbedded> extends FlexSet<V>
|
||||
export class EncodableSet<T extends Embeddable, V> extends FlexSet<V>
|
||||
implements Preservable<T>, PreserveWritable<T>
|
||||
{
|
||||
constructor(
|
||||
|
@ -156,16 +233,16 @@ export class EncodableSet<V, T extends Embeddable = GenericEmbedded> extends Fle
|
|||
}
|
||||
}
|
||||
|
||||
export class KeyedSet<K extends CompoundKey<T>, T extends Embeddable = GenericEmbedded>
|
||||
extends EncodableSet<K, T>
|
||||
export class KeyedSet<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>>
|
||||
extends EncodableSet<T, K>
|
||||
{
|
||||
get [DictionaryType](): DictionaryType {
|
||||
return 'Set';
|
||||
}
|
||||
|
||||
static isKeyedSet<K extends CompoundKey<T>, T extends Embeddable = GenericEmbedded>(
|
||||
static isKeyedSet<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>>(
|
||||
x: any,
|
||||
): x is KeyedSet<K, T> {
|
||||
): x is KeyedSet<T, K> {
|
||||
return x?.[DictionaryType] === 'Set';
|
||||
}
|
||||
|
||||
|
@ -173,26 +250,26 @@ export class KeyedSet<K extends CompoundKey<T>, T extends Embeddable = GenericEm
|
|||
super(k => k, items);
|
||||
}
|
||||
|
||||
map<S extends Value<R>, R extends Embeddable = GenericEmbedded>(
|
||||
map<R extends Embeddable = GenericEmbedded, S extends Value<R> = Value<R>>(
|
||||
f: (value: K) => S,
|
||||
): KeyedSet<S, R> {
|
||||
): KeyedSet<R, S> {
|
||||
return new KeyedSet(_iterMap(this[Symbol.iterator](), f));
|
||||
}
|
||||
|
||||
filter(f: (value: K) => boolean): KeyedSet<K, T> {
|
||||
const result = new KeyedSet<K, T>();
|
||||
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<K, T> {
|
||||
clone(): KeyedSet<T, K> {
|
||||
return new KeyedSet(this);
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() { return 'Set'; }
|
||||
}
|
||||
|
||||
export class Set<T extends Embeddable = GenericEmbedded> extends KeyedSet<Value<T>, T> {
|
||||
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';
|
||||
}
|
||||
|
@ -202,7 +279,7 @@ export class Set<T extends Embeddable = GenericEmbedded> extends KeyedSet<Value<
|
|||
}
|
||||
}
|
||||
|
||||
export function encodeSetOn<V, T extends Embeddable>(
|
||||
export function encodeSetOn<T extends Embeddable, V>(
|
||||
s: IdentitySet<V>,
|
||||
encoder: Encoder<T>,
|
||||
encodeV: (v: V, encoder: Encoder<T>) => void,
|
||||
|
@ -223,7 +300,7 @@ export function encodeSetOn<V, T extends Embeddable>(
|
|||
}
|
||||
}
|
||||
|
||||
export function writeSetOn<V, T extends Embeddable>(
|
||||
export function writeSetOn<T extends Embeddable, V>(
|
||||
s: IdentitySet<V>,
|
||||
w: Writer<T>,
|
||||
writeV: (v: V, w: Writer<T>) => void,
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import type { DecoderState } from "./decoder";
|
||||
import type { EncoderState } from "./encoder";
|
||||
import type { Value } from "./values";
|
||||
import { Annotated } from "./annotated";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Float } from "./float";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { ReaderStateOptions } from "./reader";
|
||||
|
||||
export type Embeddable = object;
|
||||
export const IsEmbedded = Symbol.for('IsEmbedded');
|
||||
|
||||
export interface Embeddable {
|
||||
readonly [IsEmbedded]: true;
|
||||
}
|
||||
|
||||
export function isEmbedded<T extends Embeddable>(v: any): v is T {
|
||||
return !!v?.[IsEmbedded];
|
||||
}
|
||||
|
||||
export type EmbeddedTypeEncode<T extends Embeddable> = {
|
||||
encode(s: EncoderState, v: T): void;
|
||||
|
@ -20,28 +24,20 @@ export type EmbeddedTypeDecode<T> = {
|
|||
|
||||
export type EmbeddedType<T extends Embeddable> = EmbeddedTypeEncode<T> & EmbeddedTypeDecode<T>;
|
||||
|
||||
export function isEmbedded<T extends Embeddable>(v: Value<T>): v is T {
|
||||
return (typeof(v) === 'object')
|
||||
&& (v !== null)
|
||||
&& !Array.isArray(v)
|
||||
&& !Bytes.isBytes(v)
|
||||
&& !Float.isFloat(v)
|
||||
&& !Set.isSet<T>(v)
|
||||
&& !Dictionary.isDictionary<T>(v)
|
||||
&& !Annotated.isAnnotated<T>(v)
|
||||
&& ((_v: T) => true)(v);
|
||||
}
|
||||
|
||||
export class Embedded<T> {
|
||||
get [IsEmbedded](): true { return true; }
|
||||
|
||||
constructor(public readonly value: T) {}
|
||||
|
||||
equals(other: any): boolean {
|
||||
return typeof other === 'object' && 'value' in other && Object.is(this.value, other.value);
|
||||
}
|
||||
}
|
||||
|
||||
export class GenericEmbedded {
|
||||
generic: Value;
|
||||
get [IsEmbedded](): true { return true; }
|
||||
|
||||
constructor(generic: Value) {
|
||||
this.generic = generic;
|
||||
}
|
||||
constructor(public readonly generic: Value) {}
|
||||
|
||||
equals(other: any, is: (a: any, b: any) => boolean) {
|
||||
return typeof other === 'object' && 'generic' in other && is(this.generic, other.generic);
|
||||
|
|
|
@ -3,8 +3,10 @@ import { Bytes, unhexDigit } from "./bytes";
|
|||
import { Value } from "./values";
|
||||
import { EncodeError } from "./codec";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { EmbeddedTypeEncode } from "./embedded";
|
||||
import { EmbeddedTypeEncode, isEmbedded } from "./embedded";
|
||||
import type { Embeddable } from "./embedded";
|
||||
import { encodeDictionaryOn } from "./dictionary";
|
||||
import { JsDictionaryMap } from "./jsdictionary";
|
||||
|
||||
export type Encodable<T extends Embeddable> =
|
||||
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
|
||||
|
@ -288,11 +290,16 @@ export class Encoder<T extends Embeddable> {
|
|||
for (let i of v) this.push(i);
|
||||
});
|
||||
}
|
||||
else if (isEmbedded<T>(v)) {
|
||||
this.state.emitbyte(Tag.Embedded);
|
||||
this.embeddedEncode.encode(this.state, v);
|
||||
}
|
||||
else {
|
||||
((v: T) => {
|
||||
this.state.emitbyte(Tag.Embedded);
|
||||
this.embeddedEncode.encode(this.state, v);
|
||||
})(v);
|
||||
encodeDictionaryOn(
|
||||
new JsDictionaryMap(v),
|
||||
this,
|
||||
(k, e) => e.push(k),
|
||||
(v, e) => e.push(v));
|
||||
}
|
||||
return this; // for chaining
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Record, Tuple } from "./record";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Value } from "./values";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { Set, KeyedDictionary, Dictionary } from "./dictionary";
|
||||
import { JsDictionary, JsDictionaryMap } from "./jsdictionary";
|
||||
import { annotate, Annotated } from "./annotated";
|
||||
import { Double, Float } from "./float";
|
||||
import { Embeddable } from "./embedded";
|
||||
import { Embeddable, isEmbedded } from "./embedded";
|
||||
|
||||
export enum ValueClass {
|
||||
Boolean,
|
||||
|
@ -34,7 +35,8 @@ 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;
|
||||
dictionary(d: Dictionary<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;
|
||||
|
||||
annotated(a: Annotated<T>, k: Fold<T, R>): R;
|
||||
|
||||
|
@ -54,7 +56,10 @@ 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); }
|
||||
dictionary(d: Dictionary<T>, k: Fold<T, void>): void {
|
||||
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 {
|
||||
d.forEach((value, key) => { k(key); k(value); });
|
||||
}
|
||||
annotated(a: Annotated<T>, k: Fold<T, void>): void { k(a.item); a.annotations.forEach(k); }
|
||||
|
@ -94,8 +99,15 @@ 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);
|
||||
}
|
||||
dictionary(d: Dictionary<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||
return d.mapEntries(([key, value]) => [k(key), k(value)]);
|
||||
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;
|
||||
}
|
||||
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||
return annotate(k(a.item), ...a.annotations.map(k));
|
||||
|
@ -188,16 +200,18 @@ export function fold<T extends Embeddable, R>(v: Value<T>, o: FoldMethods<T, R>)
|
|||
return o.array(v, walk);
|
||||
} else if (Set.isSet<T>(v)) {
|
||||
return o.set(v, walk);
|
||||
} else if (Dictionary.isDictionary<T>(v)) {
|
||||
return o.dictionary(v, walk);
|
||||
} else if (isEmbedded(v)) {
|
||||
return o.embedded(v, walk);
|
||||
} else if (Annotated.isAnnotated<T>(v)) {
|
||||
return o.annotated(v, walk);
|
||||
} else if (Bytes.isBytes(v)) {
|
||||
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 ((_v: T) => o.embedded(v, walk))(v);
|
||||
return o.jsDictionary(v, walk);
|
||||
}
|
||||
default:
|
||||
((_v: never): never => { throw new Error("Internal error"); })(v);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Embeddable, GenericEmbedded } from "./embedded";
|
||||
import { Embeddable, GenericEmbedded, isEmbedded } from "./embedded";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { Value } from "./values";
|
||||
import { Dictionary, Set } from "./dictionary";
|
||||
import { Dictionary, KeyedDictionary, Set } from "./dictionary";
|
||||
import { JsDictionary } from "./jsdictionary";
|
||||
|
||||
export function fromJS<T extends Embeddable = GenericEmbedded>(x: any): Value<T> {
|
||||
switch (typeof x) {
|
||||
|
@ -39,7 +40,7 @@ export function fromJS<T extends Embeddable = GenericEmbedded>(x: any): Value<T>
|
|||
return Bytes.from(x);
|
||||
}
|
||||
if (Map.isMap(x)) {
|
||||
const d = new Dictionary<T>();
|
||||
const d = new KeyedDictionary<T>();
|
||||
x.forEach((v, k) => d.set(fromJS(k), fromJS(v)));
|
||||
return d;
|
||||
}
|
||||
|
@ -48,8 +49,15 @@ export function fromJS<T extends Embeddable = GenericEmbedded>(x: any): Value<T>
|
|||
x.forEach(v => s.add(fromJS(v)));
|
||||
return s;
|
||||
}
|
||||
// Just... assume it's a T.
|
||||
return x as T;
|
||||
if (isEmbedded<T>(x)) {
|
||||
return x;
|
||||
}
|
||||
// Handle plain JS objects to build a JsDictionary
|
||||
{
|
||||
const r: JsDictionary<Value<T>> = {};
|
||||
Object.entries(x).forEach(([k, v]) => r[k] = fromJS(v));
|
||||
return r;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -60,17 +68,16 @@ export function fromJS<T extends Embeddable = GenericEmbedded>(x: any): Value<T>
|
|||
|
||||
declare module "./dictionary" {
|
||||
namespace Dictionary {
|
||||
export function fromJS<T extends Embeddable = GenericEmbedded, V extends Embeddable = GenericEmbedded>(
|
||||
export function stringMap<T extends Embeddable = GenericEmbedded>(
|
||||
x: object
|
||||
): Dictionary<T, Value<V>>;
|
||||
): KeyedDictionary<T, string, Value<T>>;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary.fromJS = function <T extends Embeddable = GenericEmbedded, V extends Embeddable = GenericEmbedded>(
|
||||
Dictionary.stringMap = function <T extends Embeddable = GenericEmbedded>(
|
||||
x: object
|
||||
): Dictionary<T, Value<V>> {
|
||||
if (Dictionary.isDictionary<T, Value<V>>(x)) return x;
|
||||
const d = new Dictionary<T, Value<V>>();
|
||||
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
|
||||
return d;
|
||||
): KeyedDictionary<T, string, Value<T>> {
|
||||
const r = new KeyedDictionary<T, string, Value<T>>();
|
||||
Object.entries(x).forEach(([key, value]) => r.set(key, fromJS(value)));
|
||||
return r;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
import type { Annotated } from "./annotated";
|
||||
import { Dictionary } from "./dictionary";
|
||||
|
||||
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
||||
|
||||
|
@ -30,6 +31,17 @@ export function is(a: any, b: any): boolean {
|
|||
for (let i = 0; i < a.length; i++) if (!is(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
{
|
||||
const aMap = Dictionary.asMap(a);
|
||||
const bMap = Dictionary.asMap(b);
|
||||
if (!aMap || !bMap) return false;
|
||||
if (aMap.size !== bMap.size) return false;
|
||||
for (const k of aMap.keys()) {
|
||||
if (!bMap.has(k)) return false;
|
||||
if (!is(aMap.get(k), bMap.get(k))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import { Equivalence, IsMap, _iterMap } from './flex';
|
||||
import { Dictionary, DictionaryType } from './dictionary';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -3,12 +3,13 @@ import { Bytes } from "./bytes";
|
|||
import { fold } from "./fold";
|
||||
import { is } from "./is";
|
||||
import { Value } from "./values";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { Set, Dictionary, KeyedDictionary } 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";
|
||||
|
||||
export function merge<T extends Embeddable>(
|
||||
mergeEmbeddeds: (a: T, b: T) => T | undefined,
|
||||
|
@ -48,17 +49,15 @@ export function merge<T extends Embeddable>(
|
|||
return walkMany(a, b);
|
||||
},
|
||||
set(_s: Set<T>) { die(); },
|
||||
dictionary(d: Dictionary<T>) {
|
||||
if (!Dictionary.isDictionary<T>(b)) die();
|
||||
const r = new Dictionary<T>();
|
||||
d.forEach((av,ak) => {
|
||||
const bv = b.get(ak);
|
||||
r.set(ak, bv === void 0 ? av : walk(av, bv));
|
||||
});
|
||||
b.forEach((bv, bk) => {
|
||||
if (!d.has(bk)) r.set(bk, bv);
|
||||
});
|
||||
return r;
|
||||
jsDictionary(a: JsDictionary<Value<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);
|
||||
},
|
||||
|
||||
annotated(a: Annotated<T>) {
|
||||
|
@ -82,5 +81,17 @@ 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);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { Annotated } from './annotated';
|
||||
import { Bytes } from './bytes';
|
||||
import { Set, Dictionary } from './dictionary';
|
||||
import { Set, EncodableDictionary } from './dictionary';
|
||||
import { stringify } from './text';
|
||||
|
||||
import * as util from 'util';
|
||||
|
||||
[Bytes, Annotated, Set, Dictionary].forEach((C) => {
|
||||
[Bytes, Annotated, Set, EncodableDictionary].forEach((C) => {
|
||||
(C as any).prototype[util.inspect.custom] =
|
||||
function (_depth: any, _options: any) {
|
||||
return stringify(this, { indent: 2 });
|
||||
|
|
|
@ -89,8 +89,8 @@ export function compare<T extends Embeddable>(
|
|||
return cmp(va, vb);
|
||||
}
|
||||
case 10: {
|
||||
const va = Array.from(a as Dictionary<T>).sort(cmp);
|
||||
const vb = Array.from(b as Dictionary<T>).sort(cmp);
|
||||
const va = Array.from(Dictionary.asMap<T>(a)!.entries()).sort(cmp);
|
||||
const vb = Array.from(Dictionary.asMap<T>(b)!.entries()).sort(cmp);
|
||||
return cmp(va, vb);
|
||||
}
|
||||
case 11:
|
||||
|
|
|
@ -398,22 +398,24 @@ export class Reader<T extends Embeddable> {
|
|||
}
|
||||
|
||||
readDictionary(): Dictionary<T> {
|
||||
return this.seq(true,
|
||||
new Dictionary<T>(),
|
||||
(k, acc) => {
|
||||
this.state.skipws();
|
||||
switch (this.state.peek()) {
|
||||
case ':':
|
||||
if (acc.has(k)) this.state.error(
|
||||
`Duplicate key: ${stringify(k)}`, this.state.pos);
|
||||
this.state.advance();
|
||||
acc.set(k, this.next());
|
||||
break;
|
||||
default:
|
||||
this.state.error('Missing key/value separator', this.state.pos);
|
||||
}
|
||||
},
|
||||
'}');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
readSet(): Set<T> {
|
||||
|
|
|
@ -13,6 +13,7 @@ export * from './float';
|
|||
export * from './fold';
|
||||
export * from './fromjs';
|
||||
export * from './is';
|
||||
export * from './jsdictionary';
|
||||
export * from './merge';
|
||||
export * from './order';
|
||||
export * from './reader';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Value } from "./values";
|
||||
import { Annotated } from "./annotated";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { Set, Dictionary, KeyedDictionary } from "./dictionary";
|
||||
import { JsDictionary, JsDictionaryMap } from "./jsdictionary";
|
||||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
|
||||
export function unannotate<T extends Embeddable = GenericEmbedded>(v: Value<T>): Value<T> {
|
||||
|
@ -33,8 +34,12 @@ 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 (Dictionary.isDictionary<T>(v.item)) {
|
||||
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
|
||||
} 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 {
|
||||
return v.item;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
import type { Bytes } from './bytes';
|
||||
import type { DoubleFloat } from './float';
|
||||
import type { Annotated } from './annotated';
|
||||
import type { Set, Dictionary } from './dictionary';
|
||||
import type { JsDictionary } from './jsdictionary';
|
||||
import { Set, KeyedDictionary } from './dictionary';
|
||||
import type { Embeddable, GenericEmbedded } from './embedded';
|
||||
|
||||
export type Value<T extends Embeddable = GenericEmbedded> =
|
||||
|
@ -27,4 +28,7 @@ export type Compound<T extends Embeddable = GenericEmbedded> =
|
|||
// Value<T> to any.
|
||||
| Array<Value<T>>
|
||||
| Set<T>
|
||||
| Dictionary<T>;
|
||||
// v Expanded from definition of Dictionary<> in dictionary.ts,
|
||||
// because of circular-use-of-Value<T> issues.
|
||||
| JsDictionary<Value<T>>
|
||||
| KeyedDictionary<T>;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { isAnnotated } from './is';
|
||||
import { Record, Tuple } from "./record";
|
||||
import type { Embeddable, GenericEmbedded, EmbeddedTypeEncode } from "./embedded";
|
||||
import { Embeddable, GenericEmbedded, EmbeddedTypeEncode, isEmbedded } from "./embedded";
|
||||
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';
|
||||
|
||||
export type Writable<T extends Embeddable> =
|
||||
Value<T> | PreserveWritable<T> | Iterable<Value<T>> | ArrayBufferView;
|
||||
|
@ -298,16 +300,19 @@ export class Writer<T extends Embeddable> {
|
|||
else if (isIterable(v)) {
|
||||
this.state.writeSeq('[', ']', v, vv => this.push(vv));
|
||||
}
|
||||
else {
|
||||
((v: T) => {
|
||||
this.state.pieces.push('#:');
|
||||
if ('write' in this.embeddedWrite) {
|
||||
this.embeddedWrite.write(this.state, v);
|
||||
} else {
|
||||
new Writer(this.state, genericEmbeddedTypeEncode)
|
||||
.push(this.embeddedWrite.toValue(v));
|
||||
}
|
||||
})(v);
|
||||
else if (isEmbedded(v)) {
|
||||
this.state.pieces.push('#:');
|
||||
if ('write' in this.embeddedWrite) {
|
||||
this.embeddedWrite.write(this.state, v);
|
||||
} else {
|
||||
new Writer(this.state, genericEmbeddedTypeEncode)
|
||||
.push(this.embeddedWrite.toValue(v));
|
||||
}
|
||||
} else {
|
||||
writeDictionaryOn(new JsDictionaryMap(v),
|
||||
this,
|
||||
(k, w) => w.push(k),
|
||||
(v, w) => w.push(v));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
genericEmbeddedTypeDecode,
|
||||
genericEmbeddedTypeEncode,
|
||||
parse,
|
||||
Embedded,
|
||||
KeyedDictionary,
|
||||
} from '../src/index';
|
||||
const { Tag } = Constants;
|
||||
import './test-utils';
|
||||
|
@ -75,7 +77,7 @@ describe('parsing from subarray', () => {
|
|||
|
||||
describe('reusing buffer space', () => {
|
||||
it('should be done safely, even with nested dictionaries', () => {
|
||||
expect(canonicalEncode(fromJS(['aaa', Dictionary.fromJS({a: 1}), 'zzz'])).toHex()).is(
|
||||
expect(canonicalEncode(fromJS(['aaa', Dictionary.stringMap({a: 1}), 'zzz'])).toHex()).is(
|
||||
`b5
|
||||
b103616161
|
||||
b7
|
||||
|
@ -87,33 +89,29 @@ describe('reusing buffer space', () => {
|
|||
});
|
||||
|
||||
describe('encoding and decoding embeddeds', () => {
|
||||
class LookasideEmbeddedType implements EmbeddedType<object> {
|
||||
readonly objects: object[];
|
||||
class LookasideEmbeddedType implements EmbeddedType<Embedded<object>> {
|
||||
readonly objects: Embedded<object>[];
|
||||
|
||||
constructor(objects: object[]) {
|
||||
constructor(objects: Embedded<object>[]) {
|
||||
this.objects = objects;
|
||||
}
|
||||
|
||||
decode(d: DecoderState): object {
|
||||
decode(d: DecoderState): Embedded<object> {
|
||||
return this.fromValue(new Decoder<GenericEmbedded>(d).next());
|
||||
}
|
||||
|
||||
encode(e: EncoderState, v: object): void {
|
||||
encode(e: EncoderState, v: Embedded<object>): void {
|
||||
new Encoder(e).push(this.toValue(v));
|
||||
}
|
||||
|
||||
equals(a: object, b: object): boolean {
|
||||
return Object.is(a, b);
|
||||
}
|
||||
|
||||
fromValue(v: Value<GenericEmbedded>): object {
|
||||
fromValue(v: Value<GenericEmbedded>): Embedded<object> {
|
||||
if (typeof v !== 'number' || v < 0 || v >= this.objects.length) {
|
||||
throw new Error("Unknown embedded target");
|
||||
throw new Error(`Unknown embedded target: ${stringify(v)}`);
|
||||
}
|
||||
return this.objects[v];
|
||||
}
|
||||
|
||||
toValue(v: object): number {
|
||||
toValue(v: Embedded<object>): number {
|
||||
let i = this.objects.indexOf(v);
|
||||
if (i !== -1) return i;
|
||||
this.objects.push(v);
|
||||
|
@ -122,8 +120,8 @@ describe('encoding and decoding embeddeds', () => {
|
|||
}
|
||||
|
||||
it('should encode using embeddedId when no function has been supplied', () => {
|
||||
const A1 = {a: 1};
|
||||
const A2 = {a: 1};
|
||||
const A1 = new Embedded({a: 1});
|
||||
const A2 = new Embedded({a: 1});
|
||||
const bs1 = canonicalEncode(A1);
|
||||
const bs2 = canonicalEncode(A2);
|
||||
const bs3 = canonicalEncode(A1);
|
||||
|
@ -140,10 +138,10 @@ describe('encoding and decoding embeddeds', () => {
|
|||
.toThrow("Embeddeds not permitted at this point in Preserves document");
|
||||
});
|
||||
it('should encode properly', () => {
|
||||
const objects: object[] = [];
|
||||
const objects: Embedded<object>[] = [];
|
||||
const pt = new LookasideEmbeddedType(objects);
|
||||
const A = {a: 1};
|
||||
const B = {b: 2};
|
||||
const A = new Embedded({a: 1});
|
||||
const B = new Embedded({b: 2});
|
||||
expect(encode([A, B], { embeddedEncode: pt })).is(
|
||||
Bytes.from([Tag.Sequence,
|
||||
Tag.Embedded, Tag.SignedInteger, 0,
|
||||
|
@ -152,10 +150,10 @@ describe('encoding and decoding embeddeds', () => {
|
|||
expect(objects).toEqual([A, B]);
|
||||
});
|
||||
it('should decode properly', () => {
|
||||
const objects: object[] = [];
|
||||
const objects: Embedded<object>[] = [];
|
||||
const pt = new LookasideEmbeddedType(objects);
|
||||
const X = {x: 123};
|
||||
const Y = {y: 456};
|
||||
const X = new Embedded({x: 123});
|
||||
const Y = new Embedded({y: 456});
|
||||
objects.push(X);
|
||||
objects.push(Y);
|
||||
expect(decode(Bytes.from([
|
||||
|
@ -166,17 +164,17 @@ describe('encoding and decoding embeddeds', () => {
|
|||
]), { embeddedDecode: pt })).is([X, Y]);
|
||||
});
|
||||
it('should store embeddeds embedded in map keys correctly', () => {
|
||||
const A1a = {a: 1};
|
||||
const A1a = new Embedded({a: 1});
|
||||
const A1 = A1a;
|
||||
const A2 = {a: 1};
|
||||
const m = new Dictionary<object, number>();
|
||||
const A2 = new Embedded({a: 1});
|
||||
const m = new KeyedDictionary<Embedded<object>, Value<Embedded<object>>, number>();
|
||||
m.set([A1], 1);
|
||||
m.set([A2], 2);
|
||||
expect(m.get(A1)).toBeUndefined();
|
||||
expect(m.get([A1])).toBe(1);
|
||||
expect(m.get([A2])).toBe(2);
|
||||
expect(m.get([{a: 1}])).toBeUndefined();
|
||||
A1a.a = 3;
|
||||
A1a.value.a = 3;
|
||||
expect(m.get([A1])).toBe(1);
|
||||
});
|
||||
});
|
||||
|
@ -306,7 +304,7 @@ describe('common test suite', () => {
|
|||
|
||||
const tests = (peel(TestCases._.cases(peel(samples) as TestCases)) as
|
||||
Dictionary<GenericEmbedded>);
|
||||
tests.forEach((t0: Value<GenericEmbedded>, tName0: Value<GenericEmbedded>) => {
|
||||
Dictionary.asMap(tests).forEach((t0, tName0) => {
|
||||
const tName = Symbol.keyFor(strip(tName0) as symbol)!;
|
||||
const t = peel(t0) as Record<symbol, any, GenericEmbedded>;
|
||||
switch (t.label) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Bytes, Decoder, genericEmbeddedType, encode, Reader, Double } from '../src/index';
|
||||
import { Bytes, Decoder, genericEmbeddedType, encode, Reader, Double, KeyedDictionary, Value } from '../src/index';
|
||||
import './test-utils';
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
@ -36,4 +36,23 @@ describe('reading common test suite', () => {
|
|||
expect(new Reader('123.0').next()).toEqual(Double(123.0));
|
||||
expect(new Reader('123.00').next()).toEqual(Double(123.0));
|
||||
});
|
||||
|
||||
it('should produce a sensible JS object for symbol-keyed dictionaries', () => {
|
||||
expect(new Reader('{a: 1, b: 2}').next()).toEqual({a: 1, b: 2});
|
||||
});
|
||||
|
||||
it('should produce a sensible dictionary for mixed-keyed dictionaries', () => {
|
||||
expect(new Reader('{a: 1, "b": 2}').next()).is(
|
||||
new KeyedDictionary([[Symbol.for('a'), 1], ["b", 2]] as [Value, Value][]));
|
||||
});
|
||||
|
||||
it('should produce a sensible dictionary for string-keyed dictionaries', () => {
|
||||
expect(new Reader('{"a": 1, "b": 2}').next()).is(
|
||||
new KeyedDictionary([["a", 1], ["b", 2]] as [Value, Value][]));
|
||||
});
|
||||
|
||||
it('should produce a sensible dictionary for integer-keyed dictionaries', () => {
|
||||
expect(new Reader('{9: 1, 8: 2}').next()).is(
|
||||
new KeyedDictionary([[9, 1], [8, 2]] as [Value, Value][]));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Double, fromJS, Dictionary, IDENTITY_FOLD, fold, mapEmbeddeds, Value, preserves } from '../src/index';
|
||||
import { Double, fromJS, IDENTITY_FOLD, fold, mapEmbeddeds, Value, preserves, KeyedDictionary, Embeddable, Embedded } from '../src/index';
|
||||
import './test-utils';
|
||||
|
||||
describe('Double', () => {
|
||||
|
@ -8,13 +8,13 @@ describe('Double', () => {
|
|||
});
|
||||
|
||||
describe('fold', () => {
|
||||
function mkv<T extends object>(t: T): Value<T> {
|
||||
function mkv<T extends Embeddable>(t: T): Value<T> {
|
||||
return fromJS<T>([
|
||||
1,
|
||||
2,
|
||||
new Dictionary([[[3, 4], fromJS([5, 6])],
|
||||
['a', 1],
|
||||
['b', true]]),
|
||||
new KeyedDictionary<T>([[[3, 4], fromJS([5, 6])],
|
||||
['a', 1],
|
||||
['b', true]]),
|
||||
Double(3.4),
|
||||
t,
|
||||
]);
|
||||
|
@ -22,12 +22,12 @@ describe('fold', () => {
|
|||
|
||||
it('should support identity', () => {
|
||||
const w = new Date();
|
||||
const v = mkv(w);
|
||||
const v = mkv(new Embedded(w));
|
||||
expect(fold(v, IDENTITY_FOLD)).is(v);
|
||||
const w1 = new Date();
|
||||
const v1 = mkv(w1);
|
||||
const v1 = mkv(new Embedded(w1));
|
||||
expect(fold(v, IDENTITY_FOLD)).not.is(v1);
|
||||
expect(mapEmbeddeds(v, _t => w1)).is(v1);
|
||||
expect(mapEmbeddeds(v, _t => new Embedded(w1))).is(v1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -69,6 +69,12 @@ describe('is()', () => {
|
|||
expect(c).not.toBe(3);
|
||||
expect(c).not.is(3);
|
||||
});
|
||||
it('should compare equivalent JsDictionary and KeyedDictionary values sensibly', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = new KeyedDictionary(
|
||||
[[Symbol.for('a'), 1], [Symbol.for('b'), 2]] as [Value, Value][]);
|
||||
expect(a).is(b);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`preserves` formatter', () => {
|
||||
|
@ -82,4 +88,18 @@ describe('`preserves` formatter', () => {
|
|||
expect(preserves`>${BigInt("12345678123456781234567812345678")}<`)
|
||||
.toBe('>12345678123456781234567812345678<');
|
||||
});
|
||||
it('should format regular JS objects', () => {
|
||||
expect(preserves`>${({a: 1, b: 2})}<`)
|
||||
.toBe('>{a: 1 b: 2}<');
|
||||
});
|
||||
it('should format dictionaries with string keys', () => {
|
||||
const v = new KeyedDictionary([["a", 1], ["b", 2]]);
|
||||
expect(preserves`>${v}<`)
|
||||
.toBe('>{"a": 1 "b": 2}<');
|
||||
});
|
||||
it('should format dictionaries with symbol keys', () => {
|
||||
const v = new KeyedDictionary([[Symbol.for("a"), 1], [Symbol.for("b"), 2]]);
|
||||
expect(preserves`>${v}<`)
|
||||
.toBe('>{a: 1 b: 2}<');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue