Improvements to Typed Records
This commit is contained in:
parent
993689356b
commit
481f866ada
|
@ -2,7 +2,7 @@ import { Encoder } from "./codec";
|
||||||
import { Tag } from "./constants";
|
import { Tag } from "./constants";
|
||||||
import { AsPreserve, PreserveOn } from "./symbols";
|
import { AsPreserve, PreserveOn } from "./symbols";
|
||||||
import { DefaultPointer, is, Value } from "./values";
|
import { DefaultPointer, is, Value } from "./values";
|
||||||
import { Record } from './record';
|
import { Record, Tuple } from './record';
|
||||||
import { Dictionary, Set } from './dictionary';
|
import { Dictionary, Set } from './dictionary';
|
||||||
|
|
||||||
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
||||||
|
@ -72,7 +72,7 @@ export function strip<T extends object = DefaultPointer>(v: Value<T>, depth: num
|
||||||
const nextDepth = depth - 1;
|
const nextDepth = depth - 1;
|
||||||
function walk(v: Value<T>): Value<T> { return step(v, nextDepth); }
|
function walk(v: Value<T>): Value<T> { return step(v, nextDepth); }
|
||||||
|
|
||||||
if (Record.isRecord<Value<T>, T>(v.item)) {
|
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v.item)) {
|
||||||
return Record(step(v.item.label, depth), v.item.map(walk));
|
return Record(step(v.item.label, depth), v.item.map(walk));
|
||||||
} else if (Array.isArray(v.item)) {
|
} else if (Array.isArray(v.item)) {
|
||||||
return (v.item as Value<T>[]).map(walk);
|
return (v.item as Value<T>[]).map(walk);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
Dictionary, Set, Bytes, Record, SingleFloat, DoubleFloat,
|
Dictionary, Set, Bytes, Record, SingleFloat, DoubleFloat,
|
||||||
BytesLike,
|
BytesLike,
|
||||||
Value,
|
Value,
|
||||||
|
Tuple,
|
||||||
} from './values';
|
} from './values';
|
||||||
import { Tag } from './constants';
|
import { Tag } from './constants';
|
||||||
|
|
||||||
|
@ -379,7 +380,7 @@ export class Encoder<T extends object> {
|
||||||
this.encodebytes(Tag.ByteString, bs);
|
this.encodebytes(Tag.ByteString, bs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Record.isRecord<Value<T>, T>(v)) {
|
else if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v)) {
|
||||||
this.emitbyte(Tag.Record);
|
this.emitbyte(Tag.Record);
|
||||||
this.push(v.label);
|
this.push(v.label);
|
||||||
for (let i of v) { this.push(i); }
|
for (let i of v) { this.push(i); }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float } from "./values";
|
import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float, Tuple } from "./values";
|
||||||
|
|
||||||
export type Fold<T extends object, R = Value<T>> = (v: Value<T>) => R;
|
export type Fold<T extends object, R = Value<T>> = (v: Value<T>) => R;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ export interface FoldMethods<T extends object, R> {
|
||||||
bytes(b: Bytes): R;
|
bytes(b: Bytes): R;
|
||||||
symbol(s: symbol): R;
|
symbol(s: symbol): R;
|
||||||
|
|
||||||
record(r: Record<Value<T>, T>, k: Fold<T, R>): 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;
|
array(a: Array<Value<T>>, k: Fold<T, R>): R;
|
||||||
set(s: Set<T>, k: Fold<T, R>): R;
|
set(s: Set<T>, k: Fold<T, R>): R;
|
||||||
dictionary(d: Dictionary<Value<T>, T>, k: Fold<T, R>): R;
|
dictionary(d: Dictionary<Value<T>, T>, k: Fold<T, R>): R;
|
||||||
|
@ -43,7 +43,7 @@ export abstract class ValueFold<T extends object, R extends object = T> implemen
|
||||||
symbol(s: symbol): Value<R> {
|
symbol(s: symbol): Value<R> {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
record(r: Record<Value<T>, T>, k: Fold<T, Value<R>>): Value<R> {
|
record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, Value<R>>): Value<R> {
|
||||||
return Record(k(r.label), r.map(k));
|
return Record(k(r.label), r.map(k));
|
||||||
}
|
}
|
||||||
array(a: Value<T>[], k: Fold<T, Value<R>>): Value<R> {
|
array(a: Value<T>[], k: Fold<T, Value<R>>): Value<R> {
|
||||||
|
@ -99,7 +99,7 @@ export function fold<T extends object, R>(v: Value<T>, o: FoldMethods<T, R>): R
|
||||||
case 'symbol':
|
case 'symbol':
|
||||||
return o.symbol(v);
|
return o.symbol(v);
|
||||||
case 'object':
|
case 'object':
|
||||||
if (Record.isRecord<Value<T>, T>(v)) {
|
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v)) {
|
||||||
return o.record(v, walk);
|
return o.record(v, walk);
|
||||||
} else if (Array.isArray(v)) {
|
} else if (Array.isArray(v)) {
|
||||||
return o.array(v, walk);
|
return o.array(v, walk);
|
||||||
|
|
|
@ -2,21 +2,21 @@ import { DefaultPointer, is, Value } from "./values";
|
||||||
|
|
||||||
export type Tuple<T> = Array<T> | [T];
|
export type Tuple<T> = Array<T> | [T];
|
||||||
|
|
||||||
export type Record<LabelType extends Value<T>, T extends object = DefaultPointer>
|
export type Record<LabelType extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>
|
||||||
= Array<Value<T>> & { label: LabelType };
|
= FieldsType & { label: LabelType };
|
||||||
|
|
||||||
export type RecordGetters<L extends Value<T>, T extends object, Fs> = {
|
export type RecordGetters<Fs, R> = {
|
||||||
[K in string & keyof Fs]: (r: Record<L, T>) => Fs[K];
|
[K in string & keyof Fs]: (r: R) => Fs[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CtorTypes<Fs, Names extends Tuple<keyof Fs>, T extends object> =
|
export type CtorTypes<Fs, Names extends Tuple<keyof Fs>> =
|
||||||
{ [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[];
|
{ [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[];
|
||||||
|
|
||||||
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T extends object = DefaultPointer> {
|
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T extends object = DefaultPointer> {
|
||||||
(...fields: CtorTypes<Fs, Names, T>): Record<L, T>;
|
(...fields: CtorTypes<Fs, Names>): Record<L, CtorTypes<Fs, Names>, T>;
|
||||||
constructorInfo: RecordConstructorInfo<L, T>;
|
constructorInfo: RecordConstructorInfo<L, T>;
|
||||||
isClassOf(v: any): v is Record<L, T>;
|
isClassOf(v: any): v is Record<L, CtorTypes<Fs, Names>, T>;
|
||||||
_: RecordGetters<L, T, Fs>;
|
_: RecordGetters<Fs, Record<L, CtorTypes<Fs, Names>, T>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RecordConstructorInfo<L extends Value<T>, T extends object = DefaultPointer> {
|
export interface RecordConstructorInfo<L extends Value<T>, T extends object = DefaultPointer> {
|
||||||
|
@ -24,15 +24,15 @@ export interface RecordConstructorInfo<L extends Value<T>, T extends object = De
|
||||||
arity: number;
|
arity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Record<L extends Value<T>, T extends object = DefaultPointer>(
|
export function Record<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
|
||||||
label: L, fields: Array<Value<T>>): Record<L, T>
|
label: L, fields: FieldsType): Record<L, FieldsType, T>
|
||||||
{
|
{
|
||||||
(fields as any).label = label;
|
(fields as any).label = label;
|
||||||
return fields as Record<L, T>;
|
return fields as Record<L, FieldsType, T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Record {
|
export namespace Record {
|
||||||
export function isRecord<L extends Value<T>, T extends object = DefaultPointer>(x: any): x is Record<L, T> {
|
export function isRecord<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(x: any): x is Record<L, FieldsType, T> {
|
||||||
return Array.isArray(x) && 'label' in x;
|
return Array.isArray(x) && 'label' in x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,14 +40,14 @@ export namespace Record {
|
||||||
return '<unprintable_preserves_field_value>';
|
return '<unprintable_preserves_field_value>';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function constructorInfo<L extends Value<T>, T extends object = DefaultPointer>(
|
export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
|
||||||
r: Record<L, T>): RecordConstructorInfo<L, T>
|
r: Record<L, FieldsType, T>): RecordConstructorInfo<L, T>
|
||||||
{
|
{
|
||||||
return { label: r.label, arity: r.length };
|
return { label: r.label, arity: r.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isClassOf<L extends Value<T>, T extends object = DefaultPointer>(
|
export function isClassOf<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
|
||||||
ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, T>
|
ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, FieldsType, T>
|
||||||
{
|
{
|
||||||
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
|
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
|
||||||
}
|
}
|
||||||
|
@ -58,13 +58,13 @@ export namespace Record {
|
||||||
{
|
{
|
||||||
return <L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) => {
|
return <L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) => {
|
||||||
const ctor: RecordConstructor<L, Fs, Names, T> =
|
const ctor: RecordConstructor<L, Fs, Names, T> =
|
||||||
((...fields: CtorTypes<Fs, Names, T>) =>
|
((...fields: CtorTypes<Fs, Names>) =>
|
||||||
Record(label, fields)) as RecordConstructor<L, Fs, Names, T>;
|
Record(label, fields)) as RecordConstructor<L, Fs, Names, T>;
|
||||||
const constructorInfo = { label, arity: fieldNames.length };
|
const constructorInfo = { label, arity: fieldNames.length };
|
||||||
ctor.constructorInfo = constructorInfo;
|
ctor.constructorInfo = constructorInfo;
|
||||||
ctor.isClassOf = (v: any): v is Record<L, T> => Record.isClassOf(constructorInfo, v);
|
ctor.isClassOf = (v: any): v is Record<L, CtorTypes<Fs, Names>, T> => Record.isClassOf(constructorInfo, v);
|
||||||
(ctor as any)._ = {};
|
(ctor as any)._ = {};
|
||||||
fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record<L, T>) => r[i]);
|
fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record<L, CtorTypes<Fs, Names>, T>) => r[i]);
|
||||||
return ctor;
|
return ctor;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { AsPreserve } from './symbols';
|
import { AsPreserve } from './symbols';
|
||||||
import { Bytes } from './bytes';
|
import { Bytes } from './bytes';
|
||||||
import { DoubleFloat, SingleFloat } from './float';
|
import { DoubleFloat, SingleFloat } from './float';
|
||||||
import { Record } from './record';
|
import { Record, Tuple } from './record';
|
||||||
import { Annotated } from './annotated';
|
import { Annotated } from './annotated';
|
||||||
import { Set, Dictionary } from './dictionary';
|
import { Set, Dictionary } from './dictionary';
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export type DefaultPointer = object;
|
||||||
|
|
||||||
export type Value<T extends object = DefaultPointer> = Atom | Compound<T> | T | Annotated<T>;
|
export type Value<T extends object = DefaultPointer> = Atom | Compound<T> | T | Annotated<T>;
|
||||||
export type Atom = boolean | SingleFloat | DoubleFloat | number | string | Bytes | symbol;
|
export type Atom = boolean | SingleFloat | DoubleFloat | number | string | Bytes | symbol;
|
||||||
export type Compound<T extends object = DefaultPointer> = Record<any, T> | Array<Value<T>> | Set<T> | Dictionary<Value<T>, T>;
|
export type Compound<T extends object = DefaultPointer> = Record<any, any, T> | Array<Value<T>> | Set<T> | Dictionary<Value<T>, T>;
|
||||||
|
|
||||||
export function fromJS<T extends object = DefaultPointer>(x: any): Value<T> {
|
export function fromJS<T extends object = DefaultPointer>(x: any): Value<T> {
|
||||||
switch (typeof x) {
|
switch (typeof x) {
|
||||||
|
@ -44,7 +44,7 @@ export function fromJS<T extends object = DefaultPointer>(x: any): Value<T> {
|
||||||
if (typeof x[AsPreserve] === 'function') {
|
if (typeof x[AsPreserve] === 'function') {
|
||||||
return x[AsPreserve]();
|
return x[AsPreserve]();
|
||||||
}
|
}
|
||||||
if (Record.isRecord<Value<T>, T>(x)) {
|
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
if (Array.isArray(x)) {
|
if (Array.isArray(x)) {
|
||||||
|
@ -112,7 +112,7 @@ Symbol.prototype.asPreservesText = function (): string {
|
||||||
|
|
||||||
Array.prototype.asPreservesText = function (): string {
|
Array.prototype.asPreservesText = function (): string {
|
||||||
if ('label' in (this as any)) {
|
if ('label' in (this as any)) {
|
||||||
const r = this as Record<any, any>;
|
const r = this as Record<Value, Tuple<Value>, DefaultPointer>;
|
||||||
return r.label.asPreservesText() +
|
return r.label.asPreservesText() +
|
||||||
'(' + r.map(f => {
|
'(' + r.map(f => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -198,18 +198,19 @@ describe('common test suite', () => {
|
||||||
back: 5 },
|
back: 5 },
|
||||||
annotation5: {
|
annotation5: {
|
||||||
forward: annotate<Pointer>(
|
forward: annotate<Pointer>(
|
||||||
Record<symbol, Pointer>(Symbol.for('R'),
|
Record<symbol, any, Pointer>(Symbol.for('R'),
|
||||||
[annotate<Pointer>(Symbol.for('f'),
|
[annotate<Pointer>(Symbol.for('f'),
|
||||||
Symbol.for('af'))]),
|
Symbol.for('af'))]),
|
||||||
Symbol.for('ar')),
|
Symbol.for('ar')),
|
||||||
back: Record<Value<Pointer>, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
back: Record<Value<Pointer>, any, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
||||||
},
|
},
|
||||||
annotation6: {
|
annotation6: {
|
||||||
forward: Record<Value<Pointer>, Pointer>(annotate<Pointer>(Symbol.for('R'),
|
forward: Record<Value<Pointer>, any, Pointer>(
|
||||||
Symbol.for('ar')),
|
annotate<Pointer>(Symbol.for('R'),
|
||||||
[annotate<Pointer>(Symbol.for('f'),
|
Symbol.for('ar')),
|
||||||
Symbol.for('af'))]),
|
[annotate<Pointer>(Symbol.for('f'),
|
||||||
back: Record<symbol, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
Symbol.for('af'))]),
|
||||||
|
back: Record<symbol, any, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
||||||
},
|
},
|
||||||
annotation7: {
|
annotation7: {
|
||||||
forward: annotate<Pointer>([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
|
forward: annotate<Pointer>([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
|
||||||
|
@ -259,7 +260,7 @@ describe('common test suite', () => {
|
||||||
const tests = peel(TestCases._.cases(peel(samples) as TestCases)) as Dictionary<Value<Pointer>, Pointer>;
|
const tests = peel(TestCases._.cases(peel(samples) as TestCases)) as Dictionary<Value<Pointer>, Pointer>;
|
||||||
tests.forEach((t0: Value<Pointer>, tName0: Value<Pointer>) => {
|
tests.forEach((t0: Value<Pointer>, tName0: Value<Pointer>) => {
|
||||||
const tName = Symbol.keyFor(strip(tName0) as symbol)!;
|
const tName = Symbol.keyFor(strip(tName0) as symbol)!;
|
||||||
const t = peel(t0) as Record<symbol, Pointer>;
|
const t = peel(t0) as Record<symbol, any, Pointer>;
|
||||||
switch (t.label) {
|
switch (t.label) {
|
||||||
case Symbol.for('Test'):
|
case Symbol.for('Test'):
|
||||||
runTestCase('normal', tName, strip(t[0]) as Bytes, t[1]);
|
runTestCase('normal', tName, strip(t[0]) as Bytes, t[1]);
|
||||||
|
|
Loading…
Reference in New Issue