Typed Records
This commit is contained in:
parent
074fc5db98
commit
993689356b
|
@ -72,10 +72,10 @@ 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<T>(v.item)) {
|
if (Record.isRecord<Value<T>, T>(v.item)) {
|
||||||
return new 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.map(walk);
|
return (v.item as Value<T>[]).map(walk);
|
||||||
} else if (Set.isSet<T>(v.item)) {
|
} else if (Set.isSet<T>(v.item)) {
|
||||||
return v.item.map(walk);
|
return v.item.map(walk);
|
||||||
} else if (Dictionary.isDictionary<Value<T>, T>(v.item)) {
|
} else if (Dictionary.isDictionary<Value<T>, T>(v.item)) {
|
||||||
|
|
|
@ -178,7 +178,7 @@ export class Decoder<T extends object> {
|
||||||
case Tag.Record: {
|
case Tag.Record: {
|
||||||
const vs = this.nextvalues();
|
const vs = this.nextvalues();
|
||||||
if (vs.length === 0) throw new DecodeError("Too few elements in encoded record");
|
if (vs.length === 0) throw new DecodeError("Too few elements in encoded record");
|
||||||
return this.wrap(new Record(vs[0], vs.slice(1)));
|
return this.wrap(Record(vs[0], vs.slice(1)));
|
||||||
}
|
}
|
||||||
case Tag.Sequence: return this.wrap(this.nextvalues());
|
case Tag.Sequence: return this.wrap(this.nextvalues());
|
||||||
case Tag.Set: return this.wrap(new Set(this.nextvalues()));
|
case Tag.Set: return this.wrap(new Set(this.nextvalues()));
|
||||||
|
@ -197,7 +197,7 @@ export class Decoder<T extends object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try_next() {
|
try_next(): Value<T> | undefined {
|
||||||
const start = this.index;
|
const start = this.index;
|
||||||
try {
|
try {
|
||||||
return this.next();
|
return this.next();
|
||||||
|
@ -379,6 +379,12 @@ export class Encoder<T extends object> {
|
||||||
this.encodebytes(Tag.ByteString, bs);
|
this.encodebytes(Tag.ByteString, bs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Record.isRecord<Value<T>, T>(v)) {
|
||||||
|
this.emitbyte(Tag.Record);
|
||||||
|
this.push(v.label);
|
||||||
|
for (let i of v) { this.push(i); }
|
||||||
|
this.emitbyte(Tag.End);
|
||||||
|
}
|
||||||
else if (Array.isArray(v)) {
|
else if (Array.isArray(v)) {
|
||||||
this.encodevalues(Tag.Sequence, v);
|
this.encodevalues(Tag.Sequence, v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<T>, k: Fold<T, R>): R;
|
record(r: Record<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,8 +43,8 @@ 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<T>, k: Fold<T, Value<R>>): Value<R> {
|
record(r: Record<Value<T>, T>, k: Fold<T, Value<R>>): Value<R> {
|
||||||
return new 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> {
|
||||||
return a.map(k);
|
return a.map(k);
|
||||||
|
@ -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<T>(v)) {
|
if (Record.isRecord<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);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { Record, Bytes, Annotated, Set, Dictionary } from './values';
|
import { Record, Bytes, Annotated, Set, Dictionary } from './values';
|
||||||
|
|
||||||
[Bytes, Annotated, Record, Set, Dictionary].forEach((C) => {
|
[Bytes, Annotated, Set, Dictionary].forEach((C) => {
|
||||||
(C as any).prototype[util.inspect.custom] =
|
(C as any).prototype[util.inspect.custom] =
|
||||||
function (_depth: any, _options: any) {
|
function (_depth: any, _options: any) {
|
||||||
return this.asPreservesText();
|
return this.asPreservesText();
|
||||||
|
|
|
@ -1,133 +1,71 @@
|
||||||
import { Tag } from "./constants";
|
|
||||||
import { Encoder } from "./codec";
|
|
||||||
import { PreserveOn } from "./symbols";
|
|
||||||
import { DefaultPointer, is, Value } from "./values";
|
import { DefaultPointer, is, Value } from "./values";
|
||||||
|
|
||||||
export const IsPreservesRecord = Symbol.for('IsPreservesRecord');
|
export type Tuple<T> = Array<T> | [T];
|
||||||
|
|
||||||
export class Record<T extends object = DefaultPointer> extends Array<Value<T>> {
|
export type Record<LabelType extends Value<T>, T extends object = DefaultPointer>
|
||||||
readonly label: Value<T>;
|
= Array<Value<T>> & { label: LabelType };
|
||||||
|
|
||||||
constructor(label: Value<T>, fields: Value<T>[]) {
|
export type RecordGetters<L extends Value<T>, T extends object, Fs> = {
|
||||||
if (arguments.length === 1) {
|
[K in string & keyof Fs]: (r: Record<L, T>) => Fs[K];
|
||||||
// Using things like someRecord.map() involves the runtime
|
};
|
||||||
// apparently instantiating instances of this.constructor
|
|
||||||
// as if it were just plain old Array, so we have to be
|
|
||||||
// somewhat calling-convention-compatible. This is
|
|
||||||
// something that isn't part of the user-facing API.
|
|
||||||
super(label);
|
|
||||||
this.label = label; // needed just to keep the typechecker happy
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super(fields.length);
|
export type CtorTypes<Fs, Names extends Tuple<keyof Fs>, T extends object> =
|
||||||
fields.forEach((f, i) => this[i] = f);
|
{ [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[];
|
||||||
this.label = label;
|
|
||||||
Object.freeze(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(index: number, defaultValue?: Value<T>): Value<T> | undefined {
|
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T extends object = DefaultPointer> {
|
||||||
return (index < this.length) ? this[index] : defaultValue;
|
(...fields: CtorTypes<Fs, Names, T>): Record<L, T>;
|
||||||
}
|
constructorInfo: RecordConstructorInfo<L, T>;
|
||||||
|
isClassOf(v: any): v is Record<L, T>;
|
||||||
|
_: RecordGetters<L, T, Fs>;
|
||||||
|
};
|
||||||
|
|
||||||
set(index: number, newValue: Value<T>): Record<T> {
|
export interface RecordConstructorInfo<L extends Value<T>, T extends object = DefaultPointer> {
|
||||||
return new Record(this.label, this.map((f, i) => (i === index) ? newValue : f));
|
label: L;
|
||||||
}
|
|
||||||
|
|
||||||
getConstructorInfo(): RecordConstructorInfo<T> {
|
|
||||||
return { label: this.label, arity: this.length };
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(other: any): boolean {
|
|
||||||
return Record.isRecord(other) &&
|
|
||||||
is(this.label, other.label) &&
|
|
||||||
this.every((f, i) => is(f, other.get(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashCode(): number {
|
|
||||||
// let h = hash(this.label);
|
|
||||||
// this.forEach((f) => h = ((31 * h) + hash(f)) | 0);
|
|
||||||
// return h;
|
|
||||||
// }
|
|
||||||
|
|
||||||
static fallbackToString: (f: Value<any>) => string = (_f) => '<unprintable_preserves_field_value>';
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return this.asPreservesText();
|
|
||||||
}
|
|
||||||
|
|
||||||
asPreservesText(): string {
|
|
||||||
if (!('label' in this)) {
|
|
||||||
// A quasi-Array from someRecord.map() or similar. See constructor.
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
return this.label.asPreservesText() +
|
|
||||||
'(' + this.map((f) => {
|
|
||||||
try {
|
|
||||||
return f.asPreservesText();
|
|
||||||
} catch (e) {
|
|
||||||
return Record.fallbackToString(f);
|
|
||||||
}
|
|
||||||
}).join(', ') + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
static makeConstructor<T extends object = any>(labelSymbolText: string, fieldNames: string[]): RecordConstructor<T> {
|
|
||||||
return Record.makeBasicConstructor<T>(Symbol.for(labelSymbolText), fieldNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
static makeBasicConstructor<T extends object = any>(label: Value<T>, fieldNames: string[]): RecordConstructor<T> {
|
|
||||||
const arity = fieldNames.length;
|
|
||||||
const ctor: RecordConstructor<T> = (...fields: Value<T>[]): Record<T> => {
|
|
||||||
if (fields.length !== arity) {
|
|
||||||
throw new Error("Record: cannot instantiate " + (label && label.toString()) +
|
|
||||||
" expecting " + arity + " fields with " + fields.length + " fields");
|
|
||||||
}
|
|
||||||
return new Record<T>(label, fields);
|
|
||||||
};
|
|
||||||
const constructorInfo = { label, arity };
|
|
||||||
ctor.constructorInfo = constructorInfo;
|
|
||||||
ctor.isClassOf = (v: any): v is Record<T> => Record.isClassOf(constructorInfo, v);
|
|
||||||
ctor._ = {};
|
|
||||||
fieldNames.forEach((name, i) => {
|
|
||||||
ctor._[name] = function (r: Value<T>): Value<T> | undefined {
|
|
||||||
if (!ctor.isClassOf(r)) {
|
|
||||||
throw new Error("Record: attempt to retrieve field "+label.toString()+"."+name+
|
|
||||||
" from non-"+label.toString()+": "+(r && r.toString()));
|
|
||||||
}
|
|
||||||
return r.get(i);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return ctor;
|
|
||||||
}
|
|
||||||
|
|
||||||
[PreserveOn](encoder: Encoder<T>) {
|
|
||||||
encoder.emitbyte(Tag.Record);
|
|
||||||
encoder.push(this.label);
|
|
||||||
this.forEach((f) => encoder.push(f));
|
|
||||||
encoder.emitbyte(Tag.End);
|
|
||||||
}
|
|
||||||
|
|
||||||
get [IsPreservesRecord](): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static isRecord<T extends object = DefaultPointer>(x: any): x is Record<T> {
|
|
||||||
return !!x?.[IsPreservesRecord];
|
|
||||||
}
|
|
||||||
|
|
||||||
static isClassOf<T extends object = DefaultPointer>(ci: RecordConstructorInfo<T>, v: any): v is Record<T> {
|
|
||||||
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RecordConstructor<T extends object = DefaultPointer> {
|
|
||||||
(...fields: Value<T>[]): Record<T>;
|
|
||||||
constructorInfo: RecordConstructorInfo<T>;
|
|
||||||
isClassOf(v: any): v is Record<T>;
|
|
||||||
_: { [getter: string]: (r: Value<T>) => Value<T> | undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RecordConstructorInfo<T extends object = DefaultPointer> {
|
|
||||||
label: Value<T>;
|
|
||||||
arity: number;
|
arity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Record<L extends Value<T>, T extends object = DefaultPointer>(
|
||||||
|
label: L, fields: Array<Value<T>>): Record<L, T>
|
||||||
|
{
|
||||||
|
(fields as any).label = label;
|
||||||
|
return fields as Record<L, T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Record {
|
||||||
|
export function isRecord<L extends Value<T>, T extends object = DefaultPointer>(x: any): x is Record<L, T> {
|
||||||
|
return Array.isArray(x) && 'label' in x;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fallbackToString (_f: Value<any>): string {
|
||||||
|
return '<unprintable_preserves_field_value>';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function constructorInfo<L extends Value<T>, T extends object = DefaultPointer>(
|
||||||
|
r: Record<L, T>): RecordConstructorInfo<L, T>
|
||||||
|
{
|
||||||
|
return { label: r.label, arity: r.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isClassOf<L extends Value<T>, T extends object = DefaultPointer>(
|
||||||
|
ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, T>
|
||||||
|
{
|
||||||
|
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeConstructor<Fs, T extends object = DefaultPointer>()
|
||||||
|
: (<L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) =>
|
||||||
|
RecordConstructor<L, Fs, Names, T>)
|
||||||
|
{
|
||||||
|
return <L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) => {
|
||||||
|
const ctor: RecordConstructor<L, Fs, Names, T> =
|
||||||
|
((...fields: CtorTypes<Fs, Names, T>) =>
|
||||||
|
Record(label, fields)) as RecordConstructor<L, Fs, Names, T>;
|
||||||
|
const constructorInfo = { label, arity: fieldNames.length };
|
||||||
|
ctor.constructorInfo = constructorInfo;
|
||||||
|
ctor.isClassOf = (v: any): v is Record<L, T> => Record.isClassOf(constructorInfo, v);
|
||||||
|
(ctor as any)._ = {};
|
||||||
|
fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record<L, T>) => r[i]);
|
||||||
|
return ctor;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ export * from './record';
|
||||||
export * from './annotated';
|
export * from './annotated';
|
||||||
export * from './dictionary';
|
export * from './dictionary';
|
||||||
|
|
||||||
export type DefaultPointer = object
|
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<T> | Array<Value<T>> | Set<T> | Dictionary<Value<T>, T>;
|
export type Compound<T extends object = DefaultPointer> = Record<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,11 +44,11 @@ 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<T>(x)) {
|
if (Record.isRecord<Value<T>, T>(x)) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
if (Array.isArray(x)) {
|
if (Array.isArray(x)) {
|
||||||
return (x as Array<Value<T>>).map<Value<T>>(fromJS);
|
return x.map<Value<T>>(fromJS);
|
||||||
}
|
}
|
||||||
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
||||||
return Bytes.from(x);
|
return Bytes.from(x);
|
||||||
|
@ -72,6 +72,9 @@ export function is(a: any, b: any): boolean {
|
||||||
if (a === null || b === null) return false;
|
if (a === null || b === null) return false;
|
||||||
if ('equals' in a && typeof a.equals === 'function') return a.equals(b, is);
|
if ('equals' in a && typeof a.equals === 'function') return a.equals(b, is);
|
||||||
if (Array.isArray(a) && Array.isArray(b)) {
|
if (Array.isArray(a) && Array.isArray(b)) {
|
||||||
|
const isRecord = 'label' in a;
|
||||||
|
if (isRecord !== 'label' in b) return false;
|
||||||
|
if (isRecord && !is((a as any).label, (b as any).label)) return false;
|
||||||
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;
|
||||||
return true;
|
return true;
|
||||||
|
@ -108,5 +111,17 @@ Symbol.prototype.asPreservesText = function (): string {
|
||||||
};
|
};
|
||||||
|
|
||||||
Array.prototype.asPreservesText = function (): string {
|
Array.prototype.asPreservesText = function (): string {
|
||||||
return '[' + this.map((i: Value<any>) => i.asPreservesText()).join(', ') + ']';
|
if ('label' in (this as any)) {
|
||||||
|
const r = this as Record<any, any>;
|
||||||
|
return r.label.asPreservesText() +
|
||||||
|
'(' + r.map(f => {
|
||||||
|
try {
|
||||||
|
return f.asPreservesText();
|
||||||
|
} catch (e) {
|
||||||
|
return Record.fallbackToString(f);
|
||||||
|
}
|
||||||
|
}).join(', ') + ')';
|
||||||
|
} else {
|
||||||
|
return '[' + this.map(i => i.asPreservesText()).join(', ') + ']';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,9 +35,12 @@ function encodePointer(w: Pointer): Value<Pointer> {
|
||||||
return w.v;
|
return w.v;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Discard = Record.makeConstructor<Pointer>('discard', []);
|
const _discard = Symbol.for('discard');
|
||||||
const Capture = Record.makeConstructor<Pointer>('capture', ['pattern']);
|
const _capture = Symbol.for('capture');
|
||||||
const Observe = Record.makeConstructor<Pointer>('observe', ['pattern']);
|
const _observe = Symbol.for('observe');
|
||||||
|
const Discard = Record.makeConstructor<{}, Pointer>()(_discard, []);
|
||||||
|
const Capture = Record.makeConstructor<{pattern: Value<Pointer>}, Pointer>()(_capture, ['pattern']);
|
||||||
|
const Observe = Record.makeConstructor<{pattern: Value<Pointer>}, Pointer>()(_observe, ['pattern']);
|
||||||
|
|
||||||
describe('record constructors', () => {
|
describe('record constructors', () => {
|
||||||
it('should have constructorInfo', () => {
|
it('should have constructorInfo', () => {
|
||||||
|
@ -51,8 +54,8 @@ describe('record constructors', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('RecordConstructorInfo', () => {
|
describe('RecordConstructorInfo', () => {
|
||||||
const C1 = Record.makeBasicConstructor([1], ['x', 'y']);
|
const C1 = Record.makeConstructor<{x: number, y: number}>()([1], ['x', 'y']);
|
||||||
const C2 = Record.makeBasicConstructor([1], ['z', 'w']);
|
const C2 = Record.makeConstructor<{z: number, w: number}>()([1], ['z', 'w']);
|
||||||
it('instance comparison should ignore pointer and fieldname differences', () => {
|
it('instance comparison should ignore pointer and fieldname differences', () => {
|
||||||
expect(C1(9,9)).is(C2(9,9));
|
expect(C1(9,9)).is(C2(9,9));
|
||||||
expect(C1(9,9)).not.is(C2(9,8));
|
expect(C1(9,9)).not.is(C2(9,8));
|
||||||
|
@ -67,9 +70,9 @@ describe('RecordConstructorInfo', () => {
|
||||||
|
|
||||||
describe('records', () => {
|
describe('records', () => {
|
||||||
it('should have correct getConstructorInfo', () => {
|
it('should have correct getConstructorInfo', () => {
|
||||||
expect(Discard().getConstructorInfo()).toEqual(Discard.constructorInfo);
|
expect(Record.constructorInfo(Discard())).toEqual(Discard.constructorInfo);
|
||||||
expect(Capture(Discard()).getConstructorInfo()).toEqual(Capture.constructorInfo);
|
expect(Record.constructorInfo(Capture(Discard()))).toEqual(Capture.constructorInfo);
|
||||||
expect(Observe(Capture(Discard())).getConstructorInfo()).toEqual(Observe.constructorInfo);
|
expect(Record.constructorInfo(Observe(Capture(Discard())))).toEqual(Observe.constructorInfo);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -167,7 +170,8 @@ describe('common test suite', () => {
|
||||||
const samples_bin = fs.readFileSync(__dirname + '/../../../tests/samples.bin');
|
const samples_bin = fs.readFileSync(__dirname + '/../../../tests/samples.bin');
|
||||||
const samples = decodeWithAnnotations(samples_bin, { decodePointer });
|
const samples = decodeWithAnnotations(samples_bin, { decodePointer });
|
||||||
|
|
||||||
const TestCases = Record.makeConstructor('TestCases', ['cases']);
|
const TestCases = Record.makeConstructor<{cases: Dictionary<Value<Pointer>, Pointer>}>()(Symbol.for('TestCases'), ['cases']);
|
||||||
|
type TestCases = ReturnType<typeof TestCases>;
|
||||||
|
|
||||||
function DS(bs: Bytes) {
|
function DS(bs: Bytes) {
|
||||||
return decode(bs, { decodePointer });
|
return decode(bs, { decodePointer });
|
||||||
|
@ -192,24 +196,35 @@ describe('common test suite', () => {
|
||||||
annotate<Pointer>(2, 1),
|
annotate<Pointer>(2, 1),
|
||||||
annotate<Pointer>(4, 3)),
|
annotate<Pointer>(4, 3)),
|
||||||
back: 5 },
|
back: 5 },
|
||||||
annotation5: { forward: annotate(new Record<Pointer>(Symbol.for('R'),
|
annotation5: {
|
||||||
[annotate(Symbol.for('f'),
|
forward: annotate<Pointer>(
|
||||||
Symbol.for('af'))]),
|
Record<symbol, Pointer>(Symbol.for('R'),
|
||||||
Symbol.for('ar')),
|
[annotate<Pointer>(Symbol.for('f'),
|
||||||
back: new Record<Pointer>(Symbol.for('R'), [Symbol.for('f')]) },
|
Symbol.for('af'))]),
|
||||||
annotation6: { forward: new Record<Pointer>(annotate<Pointer>(Symbol.for('R'),
|
Symbol.for('ar')),
|
||||||
Symbol.for('ar')),
|
back: Record<Value<Pointer>, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
||||||
[annotate(Symbol.for('f'),
|
},
|
||||||
Symbol.for('af'))]),
|
annotation6: {
|
||||||
back: new Record<Pointer>(Symbol.for('R'), [Symbol.for('f')]) },
|
forward: Record<Value<Pointer>, Pointer>(annotate<Pointer>(Symbol.for('R'),
|
||||||
annotation7: { forward: annotate<Pointer>([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
|
Symbol.for('ar')),
|
||||||
back: [] },
|
[annotate<Pointer>(Symbol.for('f'),
|
||||||
list1: { forward: [1, 2, 3, 4],
|
Symbol.for('af'))]),
|
||||||
back: [1, 2, 3, 4] },
|
back: Record<symbol, Pointer>(Symbol.for('R'), [Symbol.for('f')])
|
||||||
record2: { value: Observe(new Record(Symbol.for("speak"), [
|
},
|
||||||
Discard(),
|
annotation7: {
|
||||||
Capture(Discard())
|
forward: annotate<Pointer>([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
|
||||||
])) },
|
back: []
|
||||||
|
},
|
||||||
|
list1: {
|
||||||
|
forward: [1, 2, 3, 4],
|
||||||
|
back: [1, 2, 3, 4]
|
||||||
|
},
|
||||||
|
record2: {
|
||||||
|
value: Observe(Record(Symbol.for("speak"), [
|
||||||
|
Discard(),
|
||||||
|
Capture(Discard())
|
||||||
|
]))
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type Variety = 'normal' | 'nondeterministic' | 'decode';
|
type Variety = 'normal' | 'nondeterministic' | 'decode';
|
||||||
|
@ -241,10 +256,10 @@ describe('common test suite', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const tests = peel(TestCases._.cases(peel(samples))!) 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<Pointer>;
|
const t = peel(t0) as Record<symbol, 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